diff --git a/README.md b/README.md index 7f334321..74e172cb 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ This application is a lightly modified [create-react-app](https://github.com/fac ## Run Tests -Running the [top-level API tests](./src/HavenoDaemon.test.ts) is a great way to develop and test Haveno end-to-end. +Running the [API tests](./src/haveno.test.ts) is the best way to develop and test Haveno end-to-end. -[`HavenoDaemon`](./src/HavenoDaemon.ts) provides the interface to the Haveno daemon's gRPC API. +[`haveno.ts`](./src/haveno.ts) provides the interface to Haveno's backend daemon. 1. [Run a local Haveno test network](https://github.com/haveno-dex/haveno/blob/master/docs/installing.md) and then shut down the arbitrator, Alice, and Bob or run them as daemons, e.g. `make alice-daemon`. You may omit the arbitrator registration steps since it is done automatically in the tests. 2. Clone this project to the same parent directory as the haveno project: `git clone https://github.com/haveno-dex/haveno-ui-poc` diff --git a/src/App.tsx b/src/App.tsx index 71bfa681..d3fbe69f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,19 @@ import React from 'react'; import logo from './logo.png'; import './App.css'; -import {HavenoDaemon} from './HavenoDaemon'; +import {haveno} from './haveno'; const HAVENO_DAEMON_URL = "http://localhost:8080"; const HAVENO_DAEMON_PASSWORD = "apitest"; class App extends React.Component<{}, {daemonVersion: string}> { - daemon: HavenoDaemon; + daemon: haveno; constructor(props: any) { super(props); this.state = {daemonVersion: ""}; - this.daemon = new HavenoDaemon(HAVENO_DAEMON_URL, HAVENO_DAEMON_PASSWORD); + this.daemon = new haveno(HAVENO_DAEMON_URL, HAVENO_DAEMON_PASSWORD); } render() { diff --git a/src/HavenoDaemon.test.ts b/src/haveno.test.ts similarity index 97% rename from src/HavenoDaemon.test.ts rename to src/haveno.test.ts index e7e50277..cbbd1af0 100644 --- a/src/HavenoDaemon.test.ts +++ b/src/haveno.test.ts @@ -1,7 +1,7 @@ // --------------------------------- IMPORTS ---------------------------------- // import haveno types -import {HavenoDaemon} from "./HavenoDaemon"; +import {haveno} from "./haveno"; import {HavenoUtils} from "./utils/HavenoUtils"; import * as grpcWeb from 'grpc-web'; import {MarketPriceInfo, NotificationMessage, OfferInfo, TradeInfo, UrlConnection, XmrBalanceInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb @@ -140,17 +140,17 @@ interface TxContext { } // clients -let startupHavenods: HavenoDaemon[] = []; -let arbitrator: HavenoDaemon; -let alice: HavenoDaemon; -let bob: HavenoDaemon; +let startupHavenods: haveno[] = []; +let arbitrator: haveno; +let alice: haveno; +let bob: haveno; let monerod: any; let fundingWallet: any; let aliceWallet: any; let bobWallet: any; // track started haveno processes -const HAVENO_PROCESSES: HavenoDaemon[] = []; +const HAVENO_PROCESSES: haveno[] = []; const HAVENO_PROCESS_PORTS: string[] = []; // other config @@ -167,10 +167,10 @@ beforeAll(async () => { // start configured haveno daemons let promises = []; - for (let config of TestConfig.startupHavenods) promises.push(initHavenoDaemon(config)); + for (let config of TestConfig.startupHavenods) promises.push(initHaveno(config)); for (let settledPromise of await Promise.allSettled(promises)) { if (settledPromise.status !== "fulfilled") throw new Error((settledPromise as PromiseRejectedResult).reason); - startupHavenods.push((settledPromise as PromiseFulfilledResult).value); + startupHavenods.push((settledPromise as PromiseFulfilledResult).value); } // assign arbitrator alice, bob @@ -209,12 +209,12 @@ test("Can get the version", async () => { }); test("Can manage an account", async () => { - let charlie: HavenoDaemon | undefined; + let charlie: haveno | undefined; let err: any; try { // start charlie without opening account - charlie = await initHavenoDaemon({autoLogin: false}); + charlie = await initHaveno({autoLogin: false}); assert(!await charlie.accountExists()); // test errors when account not open @@ -249,7 +249,7 @@ test("Can manage an account", async () => { // restart charlie let charlieConfig = {appName: charlie.getAppName(), autoLogin: false} await releaseHavenoProcess(charlie); - charlie = await initHavenoDaemon(charlieConfig); + charlie = await initHaveno(charlieConfig); assert(await charlie.accountExists()); assert(!await charlie.isAccountOpen()); @@ -266,7 +266,7 @@ test("Can manage an account", async () => { // restart charlie await releaseHavenoProcess(charlie); - charlie = await initHavenoDaemon(charlieConfig); + charlie = await initHaveno(charlieConfig); await testAccountNotOpen(charlie); // open account @@ -287,14 +287,14 @@ test("Can manage an account", async () => { await releaseHavenoProcess(charlie); // restore account which shuts down server - charlie = await initHavenoDaemon(charlieConfig); + charlie = await initHaveno(charlieConfig); let zipBytes: Uint8Array = new Uint8Array(fs.readFileSync(zipFile)); await charlie.restoreAccount(zipBytes); assert(!await charlie.isConnectedToDaemon()); await releaseHavenoProcess(charlie); // open restored account - charlie = await initHavenoDaemon(charlieConfig); + charlie = await initHaveno(charlieConfig); assert(await charlie.accountExists()); await charlie.openAccount(password); assert(await charlie.isAccountOpen()); @@ -308,7 +308,7 @@ test("Can manage an account", async () => { // TODO: how to delete trader app folder at end of test? if (err) throw err; - async function testAccountNotOpen(havenod: HavenoDaemon): Promise { // TODO: generalize this? + async function testAccountNotOpen(havenod: haveno): Promise { // TODO: generalize this? try { await havenod.getMoneroConnections(); throw new Error("Should have thrown"); } catch (err) { assert.equal(err.message, "Account not open"); } try { await havenod.getXmrTxs(); throw new Error("Should have thrown"); } @@ -320,12 +320,12 @@ test("Can manage an account", async () => { test("Can manage Monero daemon connections", async () => { let monerod2: any; - let charlie: HavenoDaemon | undefined; + let charlie: haveno | undefined; let err: any; try { // start charlie - charlie = await initHavenoDaemon(); + charlie = await initHaveno(); // test default connections let monerodUrl1 = "http://127.0.0.1:38081"; // TODO: (woodser): move to config @@ -398,7 +398,7 @@ test("Can manage Monero daemon connections", async () => { // restart charlie let appName = charlie.getAppName(); await releaseHavenoProcess(charlie); - charlie = await initHavenoDaemon({appName: appName, accountPassword: password}); + charlie = await initHaveno({appName: appName, accountPassword: password}); // connection is restored, online, and authenticated connection = await charlie.getMoneroConnection(); @@ -687,7 +687,7 @@ test("Can get market depth", async () => { // clear offers await clearOffers(alice, assetCode); await clearOffers(bob, assetCode); - async function clearOffers(havenod: HavenoDaemon, assetCode: string) { + async function clearOffers(havenod: haveno, assetCode: string) { for (let offer of await havenod.getMyOffers(assetCode)) { if (offer.getBaseCurrencyCode().toLowerCase() === assetCode.toLowerCase()) { // TODO (woodser): offer base currency and counter currency are switched for cryptos await havenod.removeOffer(offer.getId()); @@ -1282,12 +1282,12 @@ test("Can resolve disputes", async () => { }); test("Cannot make or take offer with insufficient unlocked funds", async () => { - let charlie: HavenoDaemon | undefined; + let charlie: haveno | undefined; let err: any; try { // start charlie - charlie = await initHavenoDaemon(); + charlie = await initHaveno(); // charlie creates ethereum payment account let paymentAccount = await createCryptoPaymentAccount(charlie); @@ -1405,13 +1405,13 @@ test("Invalidates offers when reserved funds are spent", async () => { // TODO (woodser): test arbitrator state too // TODO (woodser): test breaking protocol after depositing to multisig (e.g. don't send payment account payload by deleting it) test("Handles unexpected errors during trade initialization", async () => { - let traders: HavenoDaemon[] = []; + let traders: haveno[] = []; let err: any; try { // start and fund 3 trader processes HavenoUtils.log(1, "Starting trader processes"); - traders = await initHavenoDaemons(3); + traders = await initHavenos(3); HavenoUtils.log(1, "Funding traders"); let tradeAmount: bigint = BigInt("250000000000"); await waitForUnlockedBalance(tradeAmount * BigInt("2"), traders[0], traders[1], traders[2]); @@ -1498,13 +1498,13 @@ test("Handles unexpected errors during trade initialization", async () => { // ------------------------------- HELPERS ------------------------------------ -async function initHavenoDaemons(numDaemons: number, config?: any) { - let traderPromises: Promise[] = []; - for (let i = 0; i < numDaemons; i++) traderPromises.push(initHavenoDaemon(config)); +async function initHavenos(numDaemons: number, config?: any) { + let traderPromises: Promise[] = []; + for (let i = 0; i < numDaemons; i++) traderPromises.push(initHaveno(config)); return Promise.all(traderPromises); } -async function initHavenoDaemon(config?: any): Promise { +async function initHaveno(config?: any): Promise { config = Object.assign({}, TestConfig.defaultHavenod, config); if (!config.appName) config.appName = "haveno-XMR_STAGENET_instance_" + GenUtils.getUUID(); @@ -1513,7 +1513,7 @@ async function initHavenoDaemon(config?: any): Promise { try { // try to connect to existing server - havenod = new HavenoDaemon(config.url, config.apiPassword); + havenod = new haveno(config.url, config.apiPassword); await havenod.getVersion(); } catch (err) { @@ -1545,7 +1545,7 @@ async function initHavenoDaemon(config?: any): Promise { "--walletRpcBindPort", config.walletUrl ? new URL(config.walletUrl).port : "" + await getAvailablePort(), // use configured port if given "--passwordRequired", (config.accountPasswordRequired ? "true" : "false") ]; - havenod = await HavenoDaemon.startProcess(TestConfig.haveno.path, cmd, "http://localhost:" + proxyPort, config.logProcessOutput); + havenod = await haveno.startProcess(TestConfig.haveno.path, cmd, "http://localhost:" + proxyPort, config.logProcessOutput); HAVENO_PROCESSES.push(havenod); } @@ -1570,7 +1570,7 @@ async function initHavenoDaemon(config?: any): Promise { /** * Release a Haveno process for reuse and try to shutdown. */ -async function releaseHavenoProcess(havenod: HavenoDaemon) { +async function releaseHavenoProcess(havenod: haveno) { GenUtils.remove(HAVENO_PROCESSES, havenod); GenUtils.remove(HAVENO_PROCESS_PORTS, new URL(havenod.getUrl()).port); // TODO (woodser): standardize to url try { @@ -1583,7 +1583,7 @@ async function releaseHavenoProcess(havenod: HavenoDaemon) { /** * Create or open an account with the given daemon and password. */ -async function initHavenoAccount(havenod: HavenoDaemon, password: string) { +async function initHavenoAccount(havenod: haveno, password: string) { if (await havenod.isAccountOpen()) return; if (await havenod.accountExists()) return havenod.openAccount(password); await havenod.createAccount(password); @@ -1649,17 +1649,17 @@ async function waitForUnlockedBalance(amount: bigint, ...wallets: any[]) { } async getUnlockedBalance(): Promise { - if (this._wallet instanceof HavenoDaemon) return BigInt((await this._wallet.getBalances()).getUnlockedBalance()); + if (this._wallet instanceof haveno) return BigInt((await this._wallet.getBalances()).getUnlockedBalance()); else return BigInt((await this._wallet.getUnlockedBalance()).toString()); } async getLockedBalance(): Promise { - if (this._wallet instanceof HavenoDaemon) return BigInt((await this._wallet.getBalances()).getLockedBalance()); + if (this._wallet instanceof haveno) return BigInt((await this._wallet.getBalances()).getLockedBalance()); else return BigInt((await this._wallet.getBalance()).toString()) - await this.getUnlockedBalance(); } async getDepositAddress(): Promise { - if (this._wallet instanceof HavenoDaemon) return await this._wallet.getNewDepositSubaddress(); + if (this._wallet instanceof haveno) return await this._wallet.getNewDepositSubaddress(); else return (await this._wallet.createSubaddress()).getAddress(); } } @@ -1880,7 +1880,7 @@ function getRandomAssetCode() { return TestConfig.assetCodes[GenUtils.getRandomInt(0, TestConfig.assetCodes.length - 1)]; } -async function createPaymentAccount(trader: HavenoDaemon, assetCode: string): Promise { +async function createPaymentAccount(trader: haveno, assetCode: string): Promise { return isCrypto(assetCode) ? createCryptoPaymentAccount(trader, assetCode) : createRevolutPaymentAccount(trader); } @@ -1894,14 +1894,14 @@ function getCryptoAddress(currencyCode: string): string | undefined { } } -async function createRevolutPaymentAccount(trader: HavenoDaemon): Promise { +async function createRevolutPaymentAccount(trader: haveno): Promise { let accountForm = await trader.getPaymentAccountForm('REVOLUT'); accountForm.accountName = "Revolut account " + GenUtils.getUUID(); accountForm.userName = "user123"; return trader.createPaymentAccount(accountForm); } -async function createCryptoPaymentAccount(trader: HavenoDaemon, currencyCode = "eth"): Promise { +async function createCryptoPaymentAccount(trader: haveno, currencyCode = "eth"): Promise { for (let cryptoAddress of TestConfig.cryptoAddresses) { if (cryptoAddress.currencyCode.toLowerCase() !== currencyCode.toLowerCase()) continue; return trader.createCryptoPaymentAccount( @@ -1913,7 +1913,7 @@ async function createCryptoPaymentAccount(trader: HavenoDaemon, currencyCode = " } // TODO: specify counter currency code -async function postOffer(maker: HavenoDaemon, config?: any) { +async function postOffer(maker: haveno, config?: any) { // assign default options config = Object.assign({}, TestConfig.postOffer, config); @@ -1984,7 +1984,7 @@ function testOffer(offer: OfferInfo, config?: any) { /** * Tests trade chat functionality. Must be called during an open trade. */ -async function testTradeChat(tradeId: string, alice: HavenoDaemon, bob: HavenoDaemon) { +async function testTradeChat(tradeId: string, alice: haveno, bob: haveno) { HavenoUtils.log(1, "Testing trade chat"); // invalid trade should throw error diff --git a/src/HavenoDaemon.ts b/src/haveno.ts similarity index 98% rename from src/HavenoDaemon.ts rename to src/haveno.ts index d65a4db9..ddedc64c 100644 --- a/src/HavenoDaemon.ts +++ b/src/haveno.ts @@ -10,7 +10,7 @@ const console = require('console'); /** * Haveno daemon client using gRPC. */ -class HavenoDaemon { +class haveno { // grpc clients _appName: string|undefined; @@ -40,8 +40,8 @@ class HavenoDaemon { _keepAlivePeriodMs: number = 60000; // constants - static readonly _fullyInitializedMessage = "AppStartupState: Application fully initialized"; - static readonly _loginRequiredMessage = "HavenoDaemonMain: Interactive login required"; + static readonly _fullyInitializedMessage = "Application fully initialized"; + static readonly _loginRequiredMessage = "Interactive login required"; /** * Construct a client connected to a Haveno daemon. @@ -52,7 +52,7 @@ class HavenoDaemon { constructor(url: string, password: string) { if (!url) throw new Error("Must provide URL of Haveno daemon"); if (!password) throw new Error("Must provide password of Haveno daemon"); - HavenoUtils.log(2, "Creating HavenoDaemon(" + url + ", " + password + ")"); + HavenoUtils.log(2, "Creating Haveno client connected to " + url); this._url = url; this._password = password; this._getVersionClient = new GetVersionClient(this._url); @@ -77,9 +77,9 @@ class HavenoDaemon { * @param {string[]} cmd - command to start the process * @param {string} url - Haveno daemon url (must proxy to api port) * @param {boolean} enableLogging - specifies if logging is enabled or disabled at log level 3 - * @return {HavenoDaemon} a client connected to the newly started Haveno process + * @return {haveno} a client connected to the newly started Haveno process */ - static async startProcess(havenoPath: string, cmd: string[], url: string, enableLogging: boolean): Promise { + static async startProcess(havenoPath: string, cmd: string[], url: string, enableLogging: boolean): Promise { // return promise which resolves after starting havenod return new Promise(function(resolve, reject) { @@ -88,7 +88,7 @@ class HavenoDaemon { // state variables let output = ""; let isStarted = false; - let daemon: HavenoDaemon|undefined = undefined; + let daemon: haveno|undefined = undefined; // start process let childProcess = require('child_process').spawn(cmd[0], cmd.slice(1), {cwd: havenoPath}); @@ -102,7 +102,7 @@ class HavenoDaemon { output += line + '\n'; // capture output in case of error // initialize daemon on success or login required message - if (!daemon && (line.indexOf(HavenoDaemon._fullyInitializedMessage) >= 0 || line.indexOf(HavenoDaemon._loginRequiredMessage) >= 0)) { + if (!daemon && (line.indexOf(haveno._fullyInitializedMessage) >= 0 || line.indexOf(haveno._loginRequiredMessage) >= 0)) { // get api password let passwordIdx = cmd.indexOf("--apiPassword"); @@ -113,7 +113,7 @@ class HavenoDaemon { let password = cmd[passwordIdx + 1]; // create client connected to internal process - daemon = new HavenoDaemon(url, password); + daemon = new haveno(url, password); daemon._process = childProcess; daemon._processLogging = enableLogging; daemon._appName = cmd[cmd.indexOf("--appName") + 1]; @@ -182,7 +182,7 @@ class HavenoDaemon { * @param {boolean} enabled - specifies if logging is enabled or disabled */ setProcessLogging(enabled: boolean) { - if (this._process === undefined) throw new Error("HavenoDaemon instance not created from new process"); + if (this._process === undefined) throw new Error("haveno instance not created from new process"); this._processLogging = enabled; } @@ -393,7 +393,7 @@ class HavenoDaemon { /** * Add a listener to receive notifications from the Haveno daemon. * - * @param {HavenoDaemonListener} listener - the notification listener to add + * @param {(notification: NotificationMessage) => void} listener - the notification listener to add */ async addNotificationListener(listener: (notification: NotificationMessage) => void): Promise { this._notificationListeners.push(listener); @@ -403,7 +403,7 @@ class HavenoDaemon { /** * Remove a notification listener. * - * @param {HavenoDaemonListener} listener - the notification listener to remove + * @param {(notification: NotificationMessage) => void} listener - the notification listener to remove */ async removeNotificationListener(listener: (notification: NotificationMessage) => void): Promise { let idx = this._notificationListeners.indexOf(listener); @@ -1339,4 +1339,4 @@ class HavenoDaemon { } } -export {HavenoDaemon}; +export {haveno};