From febe4d75266abdd0d171e179b0ae42177dfcbd69 Mon Sep 17 00:00:00 2001 From: woodser Date: Tue, 14 Sep 2021 08:30:22 -0400 Subject: [PATCH] can post and remove an offer implement createCryptoPaymentAccount() (unsupported on server) --- src/HavenoDaemon.test.tsx | 69 ++++++++++++++++++++++++++++++ src/HavenoDaemon.tsx | 90 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/HavenoDaemon.test.tsx b/src/HavenoDaemon.test.tsx index b09bd42c..1c313bc1 100644 --- a/src/HavenoDaemon.test.tsx +++ b/src/HavenoDaemon.test.tsx @@ -42,6 +42,75 @@ test("Can get payment accounts", async () => { } }); +test("Can create crypto payment account", async () => { + let ethPaymentAccount: PaymentAccount = await daemon.createCryptoPaymentAccount( + "my eth account", + "eth", + "0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f", + true); + testPaymentAccount(ethPaymentAccount); +}); + +test("Can post and remove an offer", async () => { + + // test requires ethereum payment account + let ethPaymentAccount: PaymentAccount | undefined; + for (let paymentAccount of await daemon.getPaymentAccounts()) { + if (paymentAccount.getSelectedTradeCurrency()?.getCode() === "ETH") { + ethPaymentAccount = paymentAccount; + break; + } + } + if (!ethPaymentAccount) throw new Error("Test requires ethereum payment account to post offer"); + + // get unlocked balance before reserving offer + let unlockedBalanceBefore: bigint = BigInt((await daemon.getBalances()).getUnlockedbalance()); // TODO: correct camelcase + + // post offer + let amount: bigint = BigInt("250000000000"); + let minAmount: bigint = BigInt("150000000000"); + let price: number = 12.378981; // TODO: price is optional? price string gets converted to long? + let useMarketBasedPrice: boolean = true; + let marketPriceMargin: number = 0.02; // within 2% + let buyerSecurityDeposit: number = 0.15; // 15% + let triggerPrice: number = 12; // TODO: fails if there is decimal, gets converted to long? + let paymentAccountId: string = ethPaymentAccount.getId(); + let offer: OfferInfo = await daemon.postOffer("eth", + "buy", // buy xmr for eth + price, + useMarketBasedPrice, + marketPriceMargin, + amount, + minAmount, + buyerSecurityDeposit, + paymentAccountId, + triggerPrice); + testOffer(offer); + + // unlocked balance has decreased + let unlockedBalanceAfter: bigint = BigInt((await daemon.getBalances()).getUnlockedbalance()); + expect(unlockedBalanceAfter).toBeLessThan(unlockedBalanceBefore); + + // offer is included in my offers only + if (!getOffer(await daemon.getMyOffers("buy"), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in my offers"); + if (getOffer(await daemon.getOffers("buy"), offer.getId())) throw new Error("My offer " + offer.getId() + " should not appear in available offers"); + + // cancel the offer + await daemon.cancelOffer(offer.getId()); + + // offer is removed from my offers + if (getOffer(await daemon.getOffers("buy"), offer.getId())) throw new Error("Offer " + offer.getId() + " was found in my offers after removal"); + + // reserved balance restored + expect(unlockedBalanceBefore).toEqual(BigInt((await daemon.getBalances()).getUnlockedbalance())); +}); + +// ------------------------------- HELPERS ------------------------------------ + +function getOffer(offers: OfferInfo[], id: string): OfferInfo | undefined { + return offers.find(offer => offer.getId() === id); +} + function testPaymentAccount(paymentAccount: PaymentAccount) { expect(paymentAccount.getId()).toHaveLength; // TODO: test rest of offer diff --git a/src/HavenoDaemon.tsx b/src/HavenoDaemon.tsx index 59fb13e5..2e71e271 100644 --- a/src/HavenoDaemon.tsx +++ b/src/HavenoDaemon.tsx @@ -1,6 +1,6 @@ import * as grpcWeb from 'grpc-web'; import {GetVersionClient, WalletsClient, OffersClient, PaymentAccountsClient} from './protobuf/GrpcServiceClientPb'; -import {GetVersionRequest, GetVersionReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply} from './protobuf/grpc_pb'; +import {GetVersionRequest, GetVersionReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest} from './protobuf/grpc_pb'; import {PaymentAccount} from './protobuf/pb_pb'; /** @@ -117,6 +117,94 @@ class HavenoDaemon { }); }); } + + /** + * Create a crypto payment account. + * + * @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 { + let that = this; + let request = new CreateCryptoCurrencyPaymentAccountRequest() + .setAccountname(accountName) + .setCurrencycode(currencyCode) + .setAddress(address) + .setTradeinstant(tradeInstant); + return new Promise(function(resolve, reject) { + that._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: that._password}, function(err: grpcWeb.Error, response: CreateCryptoCurrencyPaymentAccountReply) { + if (err) reject(err); + else resolve(response.getPaymentaccount()); + }); + }); + } + + /** + * Post an offer. + * + * @param {string} currencyCode - currency code of traded pair + * @param {string} direction - one of "BUY" or "SELL" + * @param {number} price - trade price + * @param {bool} useMarketBasedPrice - base trade on market price + * @param {number} marketPriceMargin - % from market price to tolerate + * @param {bigint} amount - amount to trade + * @param {bigint} minAmount - minimum amount to trade + * @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount + * @param {string} paymentAccountId - payment account id + * @param {number} triggerPrice - price to remove offer + * @return {HavenoOffer[]} created offers + */ + async postOffer(currencyCode: string, + direction: string, + price: number, + useMarketBasedPrice: boolean, + marketPriceMargin: number, + amount: bigint, + minAmount: bigint, + buyerSecurityDeposit: number, + paymentAccountId: string, + triggerPrice?: number): Promise { + let that = this; + let request = new CreateOfferRequest() + .setCurrencycode(currencyCode) + .setDirection(direction) + .setPrice(price.toString()) + .setUsemarketbasedprice(useMarketBasedPrice) + .setMarketpricemargin(marketPriceMargin) + .setAmount(amount.toString()) + .setMinamount(minAmount.toString()) + .setBuyersecuritydeposit(buyerSecurityDeposit) + .setPaymentaccountid(paymentAccountId) + .setMakerfeecurrencycode("XMR"); + if (triggerPrice) request.setTriggerprice(BigInt(triggerPrice.toString()).toString()); + return new Promise(function(resolve, reject) { + that._offersClient.createOffer(request, {password: that._password}, function(err: grpcWeb.Error, response: CreateOfferReply) { + if (err) reject(err); + else resolve(response.getOffer()); + }); + }); + } + + /** + * Remove a posted offer, unreserving its funds. + * + * @param {string} id - the offer id to cancel + */ + async cancelOffer(id: string) { + let that = this; + return new Promise(function(resolve, reject) { + that._offersClient.cancelOffer(new CancelOfferRequest().setId(id), {password: that._password}, function(err: grpcWeb.Error) { + if (err) reject(err); + else resolve(); + }); + }); + } } export {HavenoDaemon}; \ No newline at end of file