From 967d036216cf01696f36c934cf81417718ec3585 Mon Sep 17 00:00:00 2001 From: woodser Date: Fri, 22 Oct 2021 13:51:57 -0400 Subject: [PATCH] havenod supports crypto account creation and getPrice(currencyCode) disable account creation with instant trades --- README.md | 2 +- src/HavenoDaemon.test.tsx | 112 +++++++++++++++++++++++++++----------- src/HavenoDaemon.tsx | 28 ++++++++-- 3 files changed, 103 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 92a33b04..a31a5510 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Running the [top-level API tests](./src/HavenoDaemon.test.tsx) is a great way to Example: `docker run --rm -it -v ~/git/haveno-ui-poc/config/envoy.test.yaml:/envoy.test.yaml -p 8080:8080 -p 8081:8081 envoyproxy/envoy-dev:8a2143613d43d17d1eb35a24b4a4a4c432215606 -c /envoy.test.yaml` 5. `npm install` 6. Modify test config as needed in [HavenoDaemon.test.tsx](./src/HavenoDaemon.test.tsx). -7. `npm test` +7. `npm test` to run all tests or `npm run test -- -t 'my test'` to run tests by name ## How to Update the Protobuf Client diff --git a/src/HavenoDaemon.test.tsx b/src/HavenoDaemon.test.tsx index 91bb1c47..bfd04e0b 100644 --- a/src/HavenoDaemon.test.tsx +++ b/src/HavenoDaemon.test.tsx @@ -5,6 +5,7 @@ import {PaymentAccount} from './protobuf/pb_pb'; // import monero-javascript const monerojs = require("monero-javascript"); // TODO (woodser): support typescript and `npm install @types/monero-javascript` in monero-javascript +const GenUtils = monerojs.GenUtils; const MoneroTxConfig = monerojs.MoneroTxConfig; const TaskLooper = monerojs.TaskLooper; @@ -16,7 +17,7 @@ const havenoVersion = "1.6.2"; const aliceDaemonUrl = "http://localhost:8080"; const aliceDaemonPassword = "apitest"; const alice: HavenoDaemon = new HavenoDaemon(aliceDaemonUrl, aliceDaemonPassword); -const aliceWalletUrl = "http://127.0.0.1:56627"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated +const aliceWalletUrl = "http://127.0.0.1:51743"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated const aliceWalletUsername = "rpc_user"; const aliceWalletPassword = "abc123"; let aliceWallet: any; @@ -41,6 +42,16 @@ let fundingWallet: any; // other test config const WALLET_SYNC_PERIOD = 5000; const MAX_TIME_PEER_NOTICE = 3000; +const TEST_CRYPTO_ACCOUNTS = [ // TODO (woodser): test other cryptos, fiat + { + currencyCode: "eth", + address: "0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f" + }, + { + currencyCode: "btc", + address: "bcrt1q6j90vywv8x7eyevcnn2tn2wrlg3vsjlsvt46qz" + } +]; beforeAll(async () => { @@ -63,6 +74,22 @@ test("Can get the version", async () => { expect(version).toEqual(havenoVersion); }); +test("Can get market prices", async () => { + + // test crypto prices + for (let testAccount of TEST_CRYPTO_ACCOUNTS) { + let price = await alice.getPrice(testAccount.currencyCode); + console.log(testAccount.currencyCode + " price: " + price); + expect(price).toBeGreaterThan(0); + await GenUtils.waitFor(1000); // wait before next request // TODO (woodser): disable price throttle? + } + + // test fiat price + let price = await alice.getPrice("USD"); + console.log("usd price: " + price); + expect(price).toBeGreaterThan(0); +}); + test("Can get balances", async () => { let balances: XmrBalanceInfo = await alice.getBalances(); expect(balances.getUnlockedBalance()); @@ -88,30 +115,49 @@ test("Can get my offers", async () => { test("Can get payment accounts", async () => { let paymentAccounts: PaymentAccount[] = await alice.getPaymentAccounts(); for (let paymentAccount of paymentAccounts) { - testPaymentAccount(paymentAccount); + testCryptoPaymentAccount(paymentAccount); } }); -test("Can create a crypto payment account", async () => { - const ethAddress = "0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f"; - let ethPaymentAccount: PaymentAccount = await alice.createCryptoPaymentAccount( - "my eth account", - "eth", - "0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f", - true); - expect(ethPaymentAccount.getPaymentAccountPayload()!.getInstantCryptoCurrencyAccountPayload()!.getAddress()).toEqual(ethAddress); - let found = false; - for (let paymentAccount of await alice.getPaymentAccounts()) { - if (paymentAccount.getId() === ethPaymentAccount.getId()) { - found = true; - ethPaymentAccount = paymentAccount; - break; +test("Can create crypto payment accounts", async () => { + + // test each stagenet crypto account + for (let testAccount of TEST_CRYPTO_ACCOUNTS) { + + // create payment account + let name = testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(); + let paymentAccount: PaymentAccount = await alice.createCryptoPaymentAccount( + name, + testAccount.currencyCode, + testAccount.address); + testCryptoPaymentAccount(paymentAccount); + testCryptoPaymentAccountEquals(paymentAccount, testAccount, name); + + // fetch and test payment account + let fetchedAccount: PaymentAccount | undefined; + for (let account of await alice.getPaymentAccounts()) { + if (paymentAccount.getId() === account.getId()) { + fetchedAccount = account; + break; + } } + if (!fetchedAccount) throw new Error("Payment account not found after being added"); + testCryptoPaymentAccount(paymentAccount); + testCryptoPaymentAccountEquals(fetchedAccount, testAccount, name); + + // wait before creating next account + await GenUtils.waitFor(1000); + + // TODO (woodser): test rejecting account with invalid currency code + // TODO (woodser): test rejecting account with invalid address + // TODO (woodser): test rejecting account with duplicate name + } + + function testCryptoPaymentAccountEquals(paymentAccount: PaymentAccount, testAccount: any, name: string) { + expect(paymentAccount.getAccountName()).toEqual(name); + expect(paymentAccount.getPaymentAccountPayload()!.getCryptoCurrencyAccountPayload()!.getAddress()).toEqual(testAccount.address); + expect(paymentAccount.getSelectedTradeCurrency()!.getCode()).toEqual(testAccount.currencyCode.toUpperCase()); } - if (!found) throw new Error("Payment account not found after being added"); - expect(ethPaymentAccount.getAccountName()).toEqual("my eth account"); - expect(ethPaymentAccount.getPaymentAccountPayload()!.getInstantCryptoCurrencyAccountPayload()!.getAddress()).toEqual("0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f"); - //expect(ethPaymentAccount.getSelectedTradeCurrency()!.getCode()).toEqual("ETH"); // TODO: selected trade currency missing or interferes with other tests }); test("Can post and remove an offer", async () => { @@ -197,16 +243,12 @@ test("Can complete a trade", async () => { let tradeAmount: bigint = BigInt("250000000000"); await waitForUnlockedBalance(tradeAmount, alice, bob); - // get bob's ethereum payment account - let ethPaymentAccount: PaymentAccount | undefined; - console.log("Getting bob's payment accounts"); - for (let paymentAccount of await bob.getPaymentAccounts()) { - if (paymentAccount.getSelectedTradeCurrency()?.getCode() === "ETH") { - ethPaymentAccount = paymentAccount; - break; - } - } - if (!ethPaymentAccount) throw new Error("Bob must have ethereum payment account to take offer"); + // create bob's ethereum payment account + let testAccount = TEST_CRYPTO_ACCOUNTS[0]; + let ethPaymentAccount: PaymentAccount = await bob.createCryptoPaymentAccount( + testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(), + testAccount.currencyCode, + testAccount.address); // alice posts offer to buy xmr console.log("Alice posting offer"); @@ -325,9 +367,15 @@ function getOffer(offers: OfferInfo[], id: string): OfferInfo | undefined { return offers.find(offer => offer.getId() === id); } -function testPaymentAccount(paymentAccount: PaymentAccount) { +function testCryptoPaymentAccount(paymentAccount: PaymentAccount) { expect(paymentAccount.getId()).toHaveLength; - // TODO: test rest of payment account + expect(paymentAccount.getAccountName()).toHaveLength; + expect(paymentAccount.getPaymentAccountPayload()!.getCryptoCurrencyAccountPayload()!.getAddress()).toHaveLength; + expect(paymentAccount.getSelectedTradeCurrency()!.getCode()).toHaveLength; + expect(paymentAccount.getTradeCurrenciesList().length).toEqual(1); + let tradeCurrency = paymentAccount.getTradeCurrenciesList()[0]; + expect(tradeCurrency.getName()).toHaveLength; + expect(tradeCurrency.getCode()).toEqual(paymentAccount.getSelectedTradeCurrency()!.getCode()); } function testOffer(offer: OfferInfo) { diff --git a/src/HavenoDaemon.tsx b/src/HavenoDaemon.tsx index e1807e9e..b3198e22 100644 --- a/src/HavenoDaemon.tsx +++ b/src/HavenoDaemon.tsx @@ -1,6 +1,6 @@ import * as grpcWeb from 'grpc-web'; -import {GetVersionClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb'; -import {GetVersionRequest, GetVersionReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest} from './protobuf/grpc_pb'; +import {GetVersionClient, PriceClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb'; +import {GetVersionRequest, GetVersionReply, MarketPriceRequest, MarketPriceReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest} from './protobuf/grpc_pb'; import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb'; /** @@ -12,6 +12,7 @@ class HavenoDaemon { _url: string; _password: string; _getVersionClient: GetVersionClient; + _priceClient: PriceClient; _walletsClient: WalletsClient; _paymentAccountsClient: PaymentAccountsClient; _offersClient: OffersClient; @@ -27,6 +28,7 @@ class HavenoDaemon { this._url = url; this._password = password; this._getVersionClient = new GetVersionClient(this._url); + this._priceClient = new PriceClient(this._url); this._walletsClient = new WalletsClient(this._url); this._paymentAccountsClient = new PaymentAccountsClient(this._url); this._offersClient = new OffersClient(this._url); @@ -48,6 +50,22 @@ class HavenoDaemon { }); } + /** + * Get the current market price of the given currency code as a ratio, e.g. ETH/XMR. + * + * @param {string} currencyCode - currency code to get the price of + * @return {number} the current market price of the given currency code as a ratio, e.g. XMR/ETH + */ + async getPrice(currencyCode: string) { + let that = this; + return new Promise(function(resolve, reject) { + that._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.Error, response: MarketPriceReply) { + if (err) reject(err); + else resolve(response.getPrice()); + }); + }); + } + /** * Get the user's balances. * @@ -99,19 +117,17 @@ class HavenoDaemon { * @param {string} accountName - description of the account * @param {string} currencyCode - currency code of the account * @param {string} address - payment address of the account - * @param {boolean} tradeInstant - whether or not trade can be completed quickly * @return {PaymentAccount} the created payment account */ async createCryptoPaymentAccount(accountName: string, currencyCode: string, - address: string, - tradeInstant: boolean): Promise { + address: string): Promise { let that = this; let request = new CreateCryptoCurrencyPaymentAccountRequest() .setAccountName(accountName) .setCurrencyCode(currencyCode) .setAddress(address) - .setTradeInstant(tradeInstant); + false; // not using instant trades return new Promise(function(resolve, reject) { that._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: that._password}, function(err: grpcWeb.Error, response: CreateCryptoCurrencyPaymentAccountReply) { if (err) reject(err);