mirror of
https://github.com/haveno-dex/haveno-ts.git
synced 2024-10-01 01:35:42 -04:00
Add monero connections manager
This commit is contained in:
parent
be86aafeff
commit
a27fa770ec
@ -4,9 +4,11 @@
|
||||
import {HavenoDaemon} from "./HavenoDaemon";
|
||||
import {HavenoUtils} from "./utils/HavenoUtils";
|
||||
import * as grpcWeb from 'grpc-web';
|
||||
import {MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, XmrBalanceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
|
||||
import {MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UriConnection, XmrBalanceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
|
||||
import {PaymentAccount} from './protobuf/pb_pb';
|
||||
import {XmrDestination, XmrTx, XmrIncomingTransfer, XmrOutgoingTransfer} from './protobuf/grpc_pb';
|
||||
import AuthenticationStatus = UriConnection.AuthenticationStatus;
|
||||
import OnlineStatus = UriConnection.OnlineStatus;
|
||||
|
||||
// import monero-javascript
|
||||
const monerojs = require("monero-javascript"); // TODO (woodser): support typescript and `npm install @types/monero-javascript` in monero-javascript
|
||||
@ -29,17 +31,24 @@ const TestConfig = {
|
||||
level: 0, // set log level (gets more verbose increasing from 0)
|
||||
logProcessOutput: false // enable or disable logging process output
|
||||
},
|
||||
moneroBinsDir: "../haveno/.localnet",
|
||||
networkType: monerojs.MoneroNetworkType.STAGENET,
|
||||
haveno: {
|
||||
path: "../haveno",
|
||||
version: "1.6.2"
|
||||
},
|
||||
monerod: {
|
||||
url: "http://localhost:38081",
|
||||
uri: "http://localhost:38081",
|
||||
username: "superuser",
|
||||
password: "abctesting123"
|
||||
},
|
||||
monerod2: {
|
||||
uri: "http://localhost:58081",
|
||||
username: "superuser",
|
||||
password: "abctesting123"
|
||||
},
|
||||
fundingWallet: {
|
||||
url: "http://localhost:38084",
|
||||
uri: "http://localhost:38084",
|
||||
username: "rpc_user",
|
||||
password: "abc123",
|
||||
defaultPath: "test_funding_wallet",
|
||||
@ -48,7 +57,7 @@ const TestConfig = {
|
||||
arbitrator: {
|
||||
logProcessOutput: false,
|
||||
appName: "haveno-XMR_STAGENET_arbitrator",
|
||||
url: "http://localhost:8079",
|
||||
uri: "http://localhost:8079",
|
||||
password: "apitest",
|
||||
walletUsername: "rpc_user",
|
||||
walletPassword: "abc123"
|
||||
@ -56,20 +65,21 @@ const TestConfig = {
|
||||
alice: {
|
||||
logProcessOutput: false,
|
||||
appName: "haveno-XMR_STAGENET_alice",
|
||||
url: "http://localhost:8080",
|
||||
uri: "http://localhost:8080",
|
||||
password: "apitest",
|
||||
walletUrl: "http://127.0.0.1:38091",
|
||||
walletUri: "http://127.0.0.1:38091",
|
||||
walletUsername: "rpc_user",
|
||||
walletPassword: "abc123"
|
||||
},
|
||||
bob: {
|
||||
logProcessOutput: false,
|
||||
appName: "haveno-XMR_STAGENET_bob",
|
||||
url: "http://localhost:8081",
|
||||
uri: "http://localhost:8081",
|
||||
password: "apitest",
|
||||
},
|
||||
maxFee: BigInt("75000000000"),
|
||||
walletSyncPeriodMs: 5000,
|
||||
walletSyncPeriodMs: 5000, // TODO (woodser): auto adjust higher if using remote connection
|
||||
daemonPollPeriodMs: 15000,
|
||||
maxTimePeerNoticeMs: 3000,
|
||||
cryptoAccounts: [{ // TODO (woodser): test other cryptos, fiat
|
||||
currencyCode: "ETH",
|
||||
@ -133,8 +143,8 @@ beforeAll(async () => {
|
||||
await arbitrator.registerDisputeAgent("refundagent", TestConfig.devPrivilegePrivKey);
|
||||
|
||||
// connect monero clients
|
||||
monerod = await monerojs.connectToDaemonRpc(TestConfig.monerod.url, TestConfig.monerod.username, TestConfig.monerod.password);
|
||||
aliceWallet = await monerojs.connectToWalletRpc(TestConfig.alice.walletUrl, TestConfig.alice.walletUsername, TestConfig.alice.walletPassword);
|
||||
monerod = await monerojs.connectToDaemonRpc(TestConfig.monerod.uri, TestConfig.monerod.username, TestConfig.monerod.password);
|
||||
aliceWallet = await monerojs.connectToWalletRpc(TestConfig.alice.walletUri, TestConfig.alice.walletUsername, TestConfig.alice.walletPassword);
|
||||
|
||||
// initialize funding wallet
|
||||
await initFundingWallet();
|
||||
@ -158,7 +168,7 @@ afterAll(async () => {
|
||||
return Promise.all(stopPromises);
|
||||
});
|
||||
|
||||
jest.setTimeout(400000);
|
||||
jest.setTimeout(500000);
|
||||
|
||||
// ----------------------------------- TESTS ----------------------------------
|
||||
|
||||
@ -189,13 +199,13 @@ test("Can register as dispute agents", async () => {
|
||||
});
|
||||
|
||||
test("Can receive push notifications", async () => {
|
||||
|
||||
|
||||
// add notification listener
|
||||
let notifications: NotificationMessage[] = [];
|
||||
await alice.addNotificationListener(notification => {
|
||||
notifications.push(notification);
|
||||
});
|
||||
|
||||
|
||||
// send test notification
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await alice._sendNotification(new NotificationMessage()
|
||||
@ -203,7 +213,7 @@ test("Can receive push notifications", async () => {
|
||||
.setTitle("Test title")
|
||||
.setMessage("Test message"));
|
||||
}
|
||||
|
||||
|
||||
// test notification
|
||||
await wait(1000);
|
||||
assert.equal(3, notifications.length);
|
||||
@ -214,37 +224,152 @@ test("Can receive push notifications", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("Can get market prices", async () => {
|
||||
|
||||
// get all market prices
|
||||
let prices: MarketPriceInfo[] = await alice.getPrices();
|
||||
expect(prices.length).toBeGreaterThan(1);
|
||||
for (let price of prices) {
|
||||
expect(price.getCurrencyCode().length).toBeGreaterThan(0);
|
||||
expect(price.getPrice()).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
// get market prices of specific currencies
|
||||
for (let testAccount of TestConfig.cryptoAccounts) {
|
||||
let price = await alice.getPrice(testAccount.currencyCode);
|
||||
expect(price).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
// test that prices are reasonable
|
||||
let usd = await alice.getPrice("USD");
|
||||
expect(usd).toBeGreaterThan(50);
|
||||
expect(usd).toBeLessThan(5000);
|
||||
let doge = await alice.getPrice("DOGE");
|
||||
expect(doge).toBeGreaterThan(200)
|
||||
expect(doge).toBeLessThan(20000);
|
||||
let btc = await alice.getPrice("BTC");
|
||||
expect(btc).toBeGreaterThan(0.0004)
|
||||
expect(btc).toBeLessThan(0.4);
|
||||
test("Can manage Monero daemon connections", async () => {
|
||||
let monerod2: any;
|
||||
let charlie: HavenoDaemon | undefined;
|
||||
let err: any;
|
||||
try {
|
||||
|
||||
// test invalid currency
|
||||
await expect(async () => {await alice.getPrice("INVALID_CURRENCY")})
|
||||
.rejects
|
||||
.toThrow('Currency not found: INVALID_CURRENCY');
|
||||
// start charlie
|
||||
charlie = await startHavenoProcess(undefined, TestConfig.logging.logProcessOutput);
|
||||
|
||||
// test default connections
|
||||
let monerodUri1 = "http://localhost:38081"; // TODO: (woodser): move to config
|
||||
let monerodUri2 = "http://haveno.exchange:38081";
|
||||
let connections: UriConnection[] = await charlie.getMoneroConnections();
|
||||
testConnection(getConnection(connections, monerodUri1)!, monerodUri1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
|
||||
testConnection(getConnection(connections, monerodUri2)!, monerodUri2, OnlineStatus.UNKNOWN, AuthenticationStatus.NO_AUTHENTICATION, 2);
|
||||
|
||||
// test default connection
|
||||
let connection: UriConnection | undefined = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, monerodUri1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
|
||||
//assert(await charlie.isMoneroConnected()); // TODO (woodser): support havenod.isConnected()?
|
||||
|
||||
// add a new connection
|
||||
let fooBarUri = "http://foo.bar";
|
||||
await charlie.addMoneroConnection(fooBarUri);
|
||||
connections = await charlie.getMoneroConnections();
|
||||
connection = getConnection(connections, fooBarUri);
|
||||
testConnection(connection!, fooBarUri, OnlineStatus.UNKNOWN, AuthenticationStatus.NO_AUTHENTICATION, 0);
|
||||
//connection = await charlie.getMoneroConnection(uri); TODO (woodser): allow getting connection by uri?
|
||||
|
||||
// set prioritized connection without credentials
|
||||
await charlie.setMoneroConnection(new UriConnection()
|
||||
.setUri(TestConfig.monerod2.uri)
|
||||
.setPriority(1));
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, undefined, undefined, 1); // status may or may not be known due to periodic connection checking
|
||||
|
||||
// connection is offline
|
||||
connection = await charlie.checkMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, OnlineStatus.OFFLINE, AuthenticationStatus.NO_AUTHENTICATION, 1);
|
||||
|
||||
// start monerod2
|
||||
let cmd = [
|
||||
TestConfig.moneroBinsDir + "/monerod",
|
||||
"--" + monerojs.MoneroNetworkType.toString(TestConfig.networkType).toLowerCase(),
|
||||
"--no-igd",
|
||||
"--hide-my-port",
|
||||
"--data-dir", TestConfig.moneroBinsDir + "/node1",
|
||||
"--p2p-bind-port", "58080",
|
||||
"--rpc-bind-port", "58081",
|
||||
"--rpc-login", "superuser:abctesting123",
|
||||
"--zmq-rpc-bind-port", "58082"
|
||||
];
|
||||
monerod2 = await monerojs.connectToDaemonRpc(cmd);
|
||||
|
||||
// connection is online and not authenticated
|
||||
connection = await charlie.checkMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, OnlineStatus.ONLINE, AuthenticationStatus.NOT_AUTHENTICATED, 1);
|
||||
|
||||
// set connection credentials
|
||||
await charlie.setMoneroConnection(new UriConnection()
|
||||
.setUri(TestConfig.monerod2.uri)
|
||||
.setUsername(TestConfig.monerod2.username)
|
||||
.setPassword(TestConfig.monerod2.password)
|
||||
.setPriority(1));
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, undefined, undefined, 1);
|
||||
|
||||
// connection is online and authenticated
|
||||
connection = await charlie.checkMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
|
||||
|
||||
// restart charlie
|
||||
let appName = charlie.getAppName();
|
||||
await stopHavenoProcess(charlie);
|
||||
charlie = await startHavenoProcess(appName, TestConfig.logging.logProcessOutput);
|
||||
|
||||
// connection is restored, online, and authenticated
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod2.uri, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
|
||||
connections = await charlie.getMoneroConnections();
|
||||
testConnection(getConnection(connections, monerodUri1)!, monerodUri1, OnlineStatus.UNKNOWN, AuthenticationStatus.NO_AUTHENTICATION, 1);
|
||||
|
||||
// enable auto switch
|
||||
await charlie.setAutoSwitch(true);
|
||||
|
||||
// stop monerod
|
||||
await monerod2.stopProcess();
|
||||
|
||||
// test auto switch after periodic connection check
|
||||
await wait(TestConfig.daemonPollPeriodMs);
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, monerodUri1, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 1);
|
||||
|
||||
// stop checking connection periodically
|
||||
await charlie.stopCheckingConnection();
|
||||
|
||||
// remove current connection
|
||||
await charlie.removeMoneroConnection(monerodUri1);
|
||||
|
||||
// check current connection
|
||||
connection = await charlie.checkMoneroConnection();
|
||||
assert.equal(undefined, connection);
|
||||
|
||||
// check all connections
|
||||
await charlie.checkMoneroConnections();
|
||||
connections = await charlie.getMoneroConnections();
|
||||
testConnection(getConnection(connections, fooBarUri)!, fooBarUri, OnlineStatus.OFFLINE, AuthenticationStatus.NO_AUTHENTICATION, 0);
|
||||
for (let connection of connections) testConnection(connection!, connection.getUri(), OnlineStatus.OFFLINE, AuthenticationStatus.NO_AUTHENTICATION);
|
||||
|
||||
// set connection to previous uri
|
||||
await charlie.setMoneroConnection(fooBarUri);
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, fooBarUri, OnlineStatus.OFFLINE, AuthenticationStatus.NO_AUTHENTICATION, 0);
|
||||
|
||||
// set connection to new uri
|
||||
let fooBarUri2 = "http://foo.bar.xyz";
|
||||
await charlie.setMoneroConnection(fooBarUri2);
|
||||
connections = await charlie.getMoneroConnections();
|
||||
connection = getConnection(connections, fooBarUri2);
|
||||
testConnection(connection!, fooBarUri2, OnlineStatus.UNKNOWN, AuthenticationStatus.NO_AUTHENTICATION, 0);
|
||||
|
||||
// reset connection
|
||||
await charlie.setMoneroConnection();
|
||||
assert.equal(undefined, await charlie.getMoneroConnection());
|
||||
|
||||
// test auto switch after start checking connection
|
||||
await charlie.setAutoSwitch(false);
|
||||
await charlie.startCheckingConnection(5000); // checks the connection
|
||||
await charlie.setAutoSwitch(true);
|
||||
await charlie.addMoneroConnection(new UriConnection()
|
||||
.setUri(TestConfig.monerod.uri)
|
||||
.setUsername(TestConfig.monerod.username)
|
||||
.setPassword(TestConfig.monerod.password)
|
||||
.setPriority(2));
|
||||
await wait(10000);
|
||||
connection = await charlie.getMoneroConnection();
|
||||
testConnection(connection!, TestConfig.monerod.uri, OnlineStatus.ONLINE, AuthenticationStatus.AUTHENTICATED, 2);
|
||||
} catch (err2) {
|
||||
err = err2;
|
||||
}
|
||||
|
||||
// stop processes
|
||||
if (charlie) await stopHavenoProcess(charlie);
|
||||
if (monerod2) await monerod2.stopProcess();
|
||||
// TODO: how to delete trader app folder at end of test?
|
||||
if (err) throw err;
|
||||
});
|
||||
|
||||
// test wallet balances, transactions, deposit addresses, create and relay txs
|
||||
@ -307,6 +432,39 @@ test("Can get balances", async () => {
|
||||
expect(BigInt(balances.getReservedTradeBalance())).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test("Can get market prices", async () => {
|
||||
|
||||
// get all market prices
|
||||
let prices: MarketPriceInfo[] = await alice.getPrices();
|
||||
expect(prices.length).toBeGreaterThan(1);
|
||||
for (let price of prices) {
|
||||
expect(price.getCurrencyCode().length).toBeGreaterThan(0);
|
||||
expect(price.getPrice()).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
// get market prices of specific currencies
|
||||
for (let testAccount of TestConfig.cryptoAccounts) {
|
||||
let price = await alice.getPrice(testAccount.currencyCode);
|
||||
expect(price).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
// test that prices are reasonable
|
||||
let usd = await alice.getPrice("USD");
|
||||
expect(usd).toBeGreaterThan(50);
|
||||
expect(usd).toBeLessThan(5000);
|
||||
let doge = await alice.getPrice("DOGE");
|
||||
expect(doge).toBeGreaterThan(200)
|
||||
expect(doge).toBeLessThan(20000);
|
||||
let btc = await alice.getPrice("BTC");
|
||||
expect(btc).toBeGreaterThan(0.0004)
|
||||
expect(btc).toBeLessThan(0.4);
|
||||
|
||||
// test invalid currency
|
||||
await expect(async () => {await alice.getPrice("INVALID_CURRENCY")})
|
||||
.rejects
|
||||
.toThrow('Currency not found: INVALID_CURRENCY');
|
||||
});
|
||||
|
||||
test("Can get offers", async () => {
|
||||
let offers: OfferInfo[] = await alice.getOffers("BUY");
|
||||
for (let offer of offers) {
|
||||
@ -627,7 +785,7 @@ test("Can complete a trade", async () => {
|
||||
let bobNotifications: NotificationMessage[] = [];
|
||||
await alice.addNotificationListener(notification => { aliceNotifications.push(notification); });
|
||||
await bob.addNotificationListener(notification => { bobNotifications.push(notification); });
|
||||
|
||||
|
||||
// alice posts offer to buy xmr
|
||||
console.log("Alice posting offer");
|
||||
let direction = "buy";
|
||||
@ -675,7 +833,7 @@ test("Can complete a trade", async () => {
|
||||
expect(tradeNotifications[0].getTrade()!.getPhase()).toEqual("DEPOSIT_PUBLISHED");
|
||||
expect(tradeNotifications[0].getTitle()).toEqual("Offer Taken");
|
||||
expect(tradeNotifications[0].getMessage()).toEqual("Your offer " + offer.getId() + " has been accepted");
|
||||
|
||||
|
||||
// alice is notified of balance change
|
||||
|
||||
// bob can get trade
|
||||
@ -689,7 +847,7 @@ test("Can complete a trade", async () => {
|
||||
expect(BigInt(bobBalancesAfter.getReservedOfferBalance()) + BigInt(bobBalancesAfter.getReservedTradeBalance())).toBeGreaterThan(BigInt(bobBalancesBefore.getReservedOfferBalance()) + BigInt(bobBalancesBefore.getReservedTradeBalance()));
|
||||
|
||||
// bob is notified of balance change
|
||||
|
||||
|
||||
// alice can get trade
|
||||
fetchedTrade = await alice.getTrade(trade.getTradeId());
|
||||
expect(fetchedTrade.getPhase()).toEqual("DEPOSIT_PUBLISHED");
|
||||
@ -741,6 +899,20 @@ test("Can complete a trade", async () => {
|
||||
|
||||
// ------------------------------- HELPERS ------------------------------------
|
||||
|
||||
function getConnection(connections: UriConnection[], uri: string): UriConnection | undefined {
|
||||
for (let connection of connections) if (connection.getUri() === uri) return connection;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function testConnection(connection: UriConnection, uri?: string, onlineStatus?: OnlineStatus, authenticationStatus?: AuthenticationStatus, priority?: number) {
|
||||
if (uri) assert.equal(connection.getUri(), uri);
|
||||
assert.equal(connection.getPassword(), ""); // TODO (woodser): undefined instead of ""?
|
||||
assert.equal(connection.getUsername(), "");
|
||||
if (onlineStatus !== undefined) assert.equal(connection.getOnlineStatus(), onlineStatus);
|
||||
if (authenticationStatus !== undefined) assert.equal(connection.getAuthenticationStatus(), authenticationStatus);
|
||||
if (priority !== undefined) assert.equal(connection.getPriority(), priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize arbitrator, alice, or bob by their configuration.
|
||||
*
|
||||
@ -749,7 +921,7 @@ test("Can complete a trade", async () => {
|
||||
*/
|
||||
async function initHavenoDaemon(config: any): Promise<HavenoDaemon> {
|
||||
try {
|
||||
let havenod = new HavenoDaemon(config.url, config.password);
|
||||
let havenod = new HavenoDaemon(config.uri, config.password);
|
||||
await havenod.getVersion();
|
||||
return havenod;
|
||||
} catch (err) {
|
||||
@ -809,7 +981,7 @@ async function startHavenoProcess(appName: string|undefined, enableLogging: bool
|
||||
"--appName", appName,
|
||||
"--apiPassword", "apitest",
|
||||
"--apiPort", TestConfig.proxyPorts.get(proxyPort)![0],
|
||||
"--walletRpcBindPort", (proxyPort === "8080" ? new URL(TestConfig.alice.walletUrl).port : await getFreePort()) + "" // use alice's configured wallet rpc port
|
||||
"--walletRpcBindPort", (proxyPort === "8080" ? new URL(TestConfig.alice.walletUri).port : await getFreePort()) + "" // use alice's configured wallet rpc port
|
||||
];
|
||||
let havenod = await HavenoDaemon.startProcess(TestConfig.haveno.path, cmd, "http://localhost:" + proxyPort, enableLogging);
|
||||
HAVENO_PROCESSES.push(havenod);
|
||||
@ -837,7 +1009,7 @@ async function getFreePort(): Promise<number> {
|
||||
async function stopHavenoProcess(havenod: HavenoDaemon) {
|
||||
await havenod.stopProcess();
|
||||
GenUtils.remove(HAVENO_PROCESSES, havenod);
|
||||
GenUtils.remove(HAVENO_PROCESS_PORTS, new URL(havenod.getUrl()).port);
|
||||
GenUtils.remove(HAVENO_PROCESS_PORTS, new URL(havenod.getUrl()).port); // TODO (woodser): standardize to uri
|
||||
}
|
||||
|
||||
/**
|
||||
@ -846,7 +1018,7 @@ async function stopHavenoProcess(havenod: HavenoDaemon) {
|
||||
async function initFundingWallet() {
|
||||
|
||||
// init client connected to monero-wallet-rpc
|
||||
fundingWallet = await monerojs.connectToWalletRpc(TestConfig.fundingWallet.url, TestConfig.fundingWallet.username, TestConfig.fundingWallet.password);
|
||||
fundingWallet = await monerojs.connectToWalletRpc(TestConfig.fundingWallet.uri, TestConfig.fundingWallet.username, TestConfig.fundingWallet.password);
|
||||
|
||||
// check if wallet is open
|
||||
let walletIsOpen = false
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {HavenoUtils} from "./utils/HavenoUtils";
|
||||
import {TaskLooper} from "./utils/TaskLooper";
|
||||
import * as grpcWeb from 'grpc-web';
|
||||
import {DisputeAgentsClient, GetVersionClient, NotificationsClient, PriceClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb';
|
||||
import {CancelOfferRequest, ConfirmPaymentReceivedRequest, ConfirmPaymentStartedRequest, CreateCryptoCurrencyPaymentAccountReply, CreateCryptoCurrencyPaymentAccountRequest, CreateOfferReply, CreateOfferRequest, CreateXmrTxReply, CreateXmrTxRequest, GetBalancesReply, GetBalancesRequest, GetNewDepositSubaddressReply, GetNewDepositSubaddressRequest, GetOffersReply, GetOffersRequest, GetPaymentAccountsReply, GetPaymentAccountsRequest, GetTradeReply, GetTradeRequest, GetTradesReply, GetTradesRequest, GetVersionReply, GetVersionRequest, GetXmrTxsReply, GetXmrTxsRequest, MarketPriceInfo, MarketPriceReply, MarketPriceRequest, MarketPricesReply, MarketPricesRequest, NotificationMessage, OfferInfo, RegisterDisputeAgentRequest, RegisterNotificationListenerRequest, RelayXmrTxReply, RelayXmrTxRequest, SendNotificationRequest, TakeOfferReply, TakeOfferRequest, TradeInfo, XmrBalanceInfo, XmrDestination, XmrTx} from './protobuf/grpc_pb';
|
||||
import {DisputeAgentsClient, GetVersionClient, NotificationsClient, PriceClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient, MoneroConnectionsClient} from './protobuf/GrpcServiceClientPb';
|
||||
import {CancelOfferRequest, ConfirmPaymentReceivedRequest, ConfirmPaymentStartedRequest, CreateCryptoCurrencyPaymentAccountReply, CreateCryptoCurrencyPaymentAccountRequest, CreateOfferReply, CreateOfferRequest, CreateXmrTxReply, CreateXmrTxRequest, GetBalancesReply, GetBalancesRequest, GetNewDepositSubaddressReply, GetNewDepositSubaddressRequest, GetOffersReply, GetOffersRequest, GetPaymentAccountsReply, GetPaymentAccountsRequest, GetTradeReply, GetTradeRequest, GetTradesReply, GetTradesRequest, GetVersionReply, GetVersionRequest, GetXmrTxsReply, GetXmrTxsRequest, MarketPriceInfo, MarketPriceReply, MarketPriceRequest, MarketPricesReply, MarketPricesRequest, NotificationMessage, OfferInfo, RegisterDisputeAgentRequest, RegisterNotificationListenerRequest, RelayXmrTxReply, RelayXmrTxRequest, SendNotificationRequest, TakeOfferReply, TakeOfferRequest, TradeInfo, XmrBalanceInfo, XmrDestination, XmrTx, UriConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply} from './protobuf/grpc_pb';
|
||||
import {AvailabilityResult, PaymentAccount} from './protobuf/pb_pb';
|
||||
const console = require('console');
|
||||
|
||||
@ -15,8 +15,9 @@ class HavenoDaemon {
|
||||
_getVersionClient: GetVersionClient;
|
||||
_disputeAgentsClient: DisputeAgentsClient;
|
||||
_notificationsClient: NotificationsClient;
|
||||
_priceClient: PriceClient;
|
||||
_moneroConnectionsClient: MoneroConnectionsClient;
|
||||
_walletsClient: WalletsClient;
|
||||
_priceClient: PriceClient;
|
||||
_paymentAccountsClient: PaymentAccountsClient;
|
||||
_offersClient: OffersClient;
|
||||
_tradesClient: TradesClient;
|
||||
@ -29,7 +30,8 @@ class HavenoDaemon {
|
||||
_walletRpcPort: number|undefined;
|
||||
_notificationListeners: ((notification: NotificationMessage) => void)[] = [];
|
||||
_keepAlivePeriodMs: number = 60000;
|
||||
|
||||
_appName: string|undefined;
|
||||
|
||||
/**
|
||||
* Construct a client connected to a Haveno daemon.
|
||||
*
|
||||
@ -44,8 +46,9 @@ class HavenoDaemon {
|
||||
this._password = password;
|
||||
this._getVersionClient = new GetVersionClient(this._url);
|
||||
this._disputeAgentsClient = new DisputeAgentsClient(this._url);
|
||||
this._priceClient = new PriceClient(this._url);
|
||||
this._moneroConnectionsClient = new MoneroConnectionsClient(this._url)
|
||||
this._walletsClient = new WalletsClient(this._url);
|
||||
this._priceClient = new PriceClient(this._url);
|
||||
this._paymentAccountsClient = new PaymentAccountsClient(this._url);
|
||||
this._offersClient = new OffersClient(this._url);
|
||||
this._tradesClient = new TradesClient(this._url);
|
||||
@ -98,7 +101,8 @@ class HavenoDaemon {
|
||||
daemon = new HavenoDaemon(url, password);
|
||||
daemon._process = childProcess;
|
||||
daemon._processLogging = enableLogging;
|
||||
|
||||
daemon._appName = cmd[cmd.indexOf("--appName") + 1];
|
||||
|
||||
// get wallet rpc port
|
||||
let walletRpcPortIdx = cmd.indexOf("--walletRpcBindPort");
|
||||
if (walletRpcPortIdx >= 0) daemon._walletRpcPort = parseInt(cmd[walletRpcPortIdx + 1]);
|
||||
@ -193,6 +197,13 @@ class HavenoDaemon {
|
||||
return this._walletRpcPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the Haveno application folder.
|
||||
*/
|
||||
getAppName(): string|undefined {
|
||||
return this._appName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Haveno version.
|
||||
*
|
||||
@ -238,32 +249,174 @@ class HavenoDaemon {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current market price per 1 XMR in the given currency.
|
||||
*
|
||||
* @param {string} currencyCode - currency code (fiat or crypto) to get the price of
|
||||
* @return {number} the current market price per 1 XMR in the given currency
|
||||
* Add a Monero daemon connection.
|
||||
*
|
||||
* @param {string | UriConnection} connection - daemon uri or connection to add
|
||||
*/
|
||||
async getPrice(currencyCode: string): Promise<number> {
|
||||
async addMoneroConnection(connection: string | UriConnection): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
|
||||
that._moneroConnectionsClient.addConnection(new AddConnectionRequest().setConnection(typeof connection === "string" ? new UriConnection().setUri(connection) : connection), {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getPrice());
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current market prices of all the currencies.
|
||||
*
|
||||
* @return {MarketPrice[]} price per 1 XMR in all supported currencies (fiat & crypto)
|
||||
* Remove a Monero daemon connection.
|
||||
*
|
||||
* @param {string} uri - uri of the daemon connection to remove
|
||||
*/
|
||||
async getPrices(): Promise<MarketPriceInfo[]> {
|
||||
async removeMoneroConnection(uri: string): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._priceClient.getMarketPrices(new MarketPricesRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPricesReply) {
|
||||
that._moneroConnectionsClient.removeConnection(new RemoveConnectionRequest().setUri(uri), {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getMarketPriceList());
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Monero daemon connection.
|
||||
*
|
||||
* @return {UriConnection | undefined} the current daemon connection, undefined if no current connection
|
||||
*/
|
||||
async getMoneroConnection(): Promise<UriConnection | undefined> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.getConnection(new GetConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetConnectionReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getConnection());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Monero daemon connections.
|
||||
*
|
||||
* @return {UriConnection[]} all daemon connections
|
||||
*/
|
||||
async getMoneroConnections(): Promise<UriConnection[]> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.getConnections(new GetConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetConnectionsReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getConnectionsList());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current Monero daemon connection.
|
||||
*
|
||||
* Add the connection if not previously seen.
|
||||
* If the connection is provided as string, connect to the URI with any previously set credentials and priority.
|
||||
* If the connection is provided as UriConnection, overwrite any previously set credentials and priority.
|
||||
* If undefined connection provided, disconnect the client.
|
||||
*
|
||||
* @param {string | UriConnection} connection - connection to set as current
|
||||
*/
|
||||
async setMoneroConnection(connection?: string | UriConnection): Promise<void> {
|
||||
let that = this;
|
||||
let request = new SetConnectionRequest();
|
||||
if (typeof connection === "string") request.setUri(connection);
|
||||
else request.setConnection(connection);
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.setConnection(request, {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current Monero daemon connection.
|
||||
*
|
||||
* If disconnected and auto switch enabled, switch to the best available connection and return its status.
|
||||
*
|
||||
* @return {UriConnection | undefined} the current daemon connection status, undefined if no current connection
|
||||
*/
|
||||
async checkMoneroConnection(): Promise<UriConnection | undefined> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.checkConnection(new CheckConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: CheckConnectionReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getConnection());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all Monero daemon connections.
|
||||
*
|
||||
* @return {UriConnection[]} status of all managed connections.
|
||||
*/
|
||||
async checkMoneroConnections(): Promise<UriConnection[]> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.checkConnections(new CheckConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: CheckConnectionsReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getConnectionsList());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the connection and start checking the connection periodically.
|
||||
*
|
||||
* @param {number} refreshPeriod - time between checks in milliseconds (default 15000 ms or 15 seconds)
|
||||
*/
|
||||
async startCheckingConnection(refreshPeriod: number): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.startCheckingConnections(new StartCheckingConnectionsRequest().setRefreshPeriod(refreshPeriod), {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop checking the connection status periodically.
|
||||
*/
|
||||
async stopCheckingConnection(): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.stopCheckingConnections(new StopCheckingConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best available connection in order of priority then response time.
|
||||
*
|
||||
* @return {UriConnection | undefined} the best available connection in order of priority then response time, undefined if no connections available
|
||||
*/
|
||||
async getBestAvailableConnection(): Promise<UriConnection | undefined> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.getBestAvailableConnection(new GetBestAvailableConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetBestAvailableConnectionReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getConnection());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically switch to the best available connection if current connection is disconnected after being checked.
|
||||
*
|
||||
* @param {boolean} autoSwitch - whether auto switch is enabled or disabled
|
||||
*/
|
||||
async setAutoSwitch(autoSwitch: boolean): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._moneroConnectionsClient.setAutoSwitch(new SetAutoSwitchRequest().setAutoSwitch(autoSwitch), {password: that._password}, function(err: grpcWeb.RpcError) {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -357,6 +510,37 @@ class HavenoDaemon {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current market price per 1 XMR in the given currency.
|
||||
*
|
||||
* @param {string} currencyCode - currency code (fiat or crypto) to get the price of
|
||||
* @return {number} the current market price per 1 XMR in the given currency
|
||||
*/
|
||||
async getPrice(currencyCode: string): Promise<number> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getPrice());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current market prices of all the currencies.
|
||||
*
|
||||
* @return {MarketPrice[]} price per 1 XMR in all supported currencies (fiat & crypto)
|
||||
*/
|
||||
async getPrices(): Promise<MarketPriceInfo[]> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
that._priceClient.getMarketPrices(new MarketPricesRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPricesReply) {
|
||||
if (err) reject(err);
|
||||
else resolve(response.getMarketPriceList());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment accounts.
|
||||
*
|
||||
@ -596,7 +780,7 @@ class HavenoDaemon {
|
||||
async _registerNotificationListener(): Promise<void> {
|
||||
let that = this;
|
||||
return new Promise(function(resolve) {
|
||||
|
||||
|
||||
// send request to register client listener
|
||||
that._notificationsClient.registerNotificationListener(new RegisterNotificationListenerRequest(), {password: that._password})
|
||||
.on("data", (data) => {
|
||||
@ -604,7 +788,7 @@ class HavenoDaemon {
|
||||
for (let listener of that._notificationListeners) listener(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// periodically send keep alive requests // TODO (woodser): better way to keep notification stream alive?
|
||||
let firstRequest = true;
|
||||
let taskLooper = new TaskLooper(async function() {
|
||||
@ -617,7 +801,7 @@ class HavenoDaemon {
|
||||
.setTimestamp(Date.now()));
|
||||
});
|
||||
taskLooper.start(that._keepAlivePeriodMs);
|
||||
|
||||
|
||||
// TODO: call returns before listener registered
|
||||
setTimeout(function() { resolve(); }, 1000);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user