From f89ed9d3bbb130d7559050e9a1a834f471865d97 Mon Sep 17 00:00:00 2001 From: Randall B <39175191+Randall-Coding@users.noreply.github.com> Date: Fri, 11 Feb 2022 17:13:56 -0600 Subject: [PATCH] Add market depth API call (#47) --- bin/build_protobuf.sh | 2 +- src/HavenoDaemon.test.ts | 175 ++++++++++++++++++++++++++++++--------- src/HavenoDaemon.ts | 55 +++++++----- 3 files changed, 171 insertions(+), 61 deletions(-) diff --git a/bin/build_protobuf.sh b/bin/build_protobuf.sh index c89c7e6c..a6325044 100755 --- a/bin/build_protobuf.sh +++ b/bin/build_protobuf.sh @@ -3,4 +3,4 @@ # generate imports for haveno services and types using grpc-web mkdir -p ./src/protobuf cd ./src/protobuf || exit 1 -protoc -I=../../../haveno/proto/src/main/proto/ ../../../haveno/proto/src/main/proto/*.proto --js_out=import_style=commonjs,binary:./ --grpc-web_out=import_style=typescript,mode=grpcwebtext:./ || exit 1 \ No newline at end of file +protoc -I=../../../haveno/proto/src/main/proto/ ../../../haveno/proto/src/main/proto/*.proto --js_out=import_style=commonjs,binary:./ --grpc-web_out=import_style=typescript,mode=grpcwebtext:./ || exit 1 diff --git a/src/HavenoDaemon.test.ts b/src/HavenoDaemon.test.ts index 90aa5155..45a1aaf6 100644 --- a/src/HavenoDaemon.test.ts +++ b/src/HavenoDaemon.test.ts @@ -1,5 +1,5 @@ // --------------------------------- IMPORTS ---------------------------------- - + // import haveno types import {HavenoDaemon} from "./HavenoDaemon"; import {HavenoUtils} from "./utils/HavenoUtils"; @@ -104,7 +104,19 @@ const TestConfig = { ["8086", ["10005", "7781"]], ]), devPrivilegePrivKey: "6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a", // from DEV_PRIVILEGE_PRIV_KEY - timeout: 900000 // timeout in ms for all tests to complete (15 minutes) + timeout: 900000, // timeout in ms for all tests to complete (15 minutes), + postOffer: { + direction: "buy", // buy or sell xmr + amount: BigInt("200000000000"), + counterCurrency: "eth", + price: undefined, // use market price if undefined // TODO: converted to long on backend + paymentAcountId: undefined, + priceMargin: 0.0, + minAmount: BigInt("150000000000"), // TODO: disable by default + buyerSecurityDeposit: 0.15, + awaitUnlockedBalance: false, + triggerPrice: undefined // TODO: fails if there is a decimal, converted to long on backend + } }; interface TxContext { @@ -562,6 +574,76 @@ test("Can get market prices", async () => { .toThrow('Currency not found: INVALID_CURRENCY'); }); +test("Can get market depth", async () => { + let counterCurrency = "eth"; + + // clear offers + await clearOffers(alice, counterCurrency); + await clearOffers(bob, counterCurrency); + async function clearOffers(havenod: HavenoDaemon, counterCurrency: string) { + for (let offer of await havenod.getMyOffers()) { + if (offer.getBaseCurrencyCode().toLowerCase() === counterCurrency.toLowerCase()) { // TODO (woodser): offer base currency and counter currency are switched + await havenod.removeOffer(offer.getId()); + } + } + } + + // market depth has no data + await wait(TestConfig.maxTimePeerNoticeMs); + let marketDepth = await alice.getMarketDepth(counterCurrency); + expect(marketDepth.getBuyPricesList().length).toEqual(0); + expect(marketDepth.getBuyDepthList().length).toEqual(0); + expect(marketDepth.getSellPricesList().length).toEqual(0); + expect(marketDepth.getSellDepthList().length).toEqual(0); + + // post offers to buy and sell + await postOffer(alice, {direction: "buy", amount: BigInt("150000000000"), counterCurrency: counterCurrency, priceMargin: 0.00, awaitUnlockedBalance: true, price: 17.0}); // TODO: offer price is reversed. fix everywhere + await postOffer(alice, {direction: "buy", amount: BigInt("150000000000"), counterCurrency: counterCurrency, priceMargin: 0.02, awaitUnlockedBalance: true, price: 17.2}); + await postOffer(alice, {direction: "buy", amount: BigInt("200000000000"), counterCurrency: counterCurrency, priceMargin: 0.05, awaitUnlockedBalance: true, price: 17.3}); + await postOffer(alice, {direction: "buy", amount: BigInt("150000000000"), counterCurrency: counterCurrency, priceMargin: 0.02, awaitUnlockedBalance: true, price: 17.3}); + await postOffer(alice, {direction: "sell", amount: BigInt("300000000000"), counterCurrency: counterCurrency, priceMargin: 0.00, awaitUnlockedBalance: true}); + await postOffer(alice, {direction: "sell", amount: BigInt("300000000000"), counterCurrency: counterCurrency, priceMargin: 0.02, awaitUnlockedBalance: true}); + await postOffer(alice, {direction: "sell", amount: BigInt("400000000000"), counterCurrency: counterCurrency, priceMargin: 0.05, awaitUnlockedBalance: true}); + + // get bob's market depth + await wait(TestConfig.maxTimePeerNoticeMs); + marketDepth = await alice.getMarketDepth(counterCurrency); + + // each unique price has a depth + expect(marketDepth.getBuyPricesList().length).toEqual(3); + expect(marketDepth.getSellPricesList().length).toEqual(3); + expect(marketDepth.getBuyPricesList().length).toEqual(marketDepth.getBuyDepthList().length); + expect(marketDepth.getSellPricesList().length).toEqual(marketDepth.getSellDepthList().length); + + // test buy prices and depths + const priceDivisor = 100000000; // TODO: offer price = price * 100000000 + let buyOffers = (await alice.getOffers("buy")).concat(await alice.getMyOffers("buy")).sort(function(a, b) { return a.getPrice() - b.getPrice() }); + expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / (buyOffers[0].getPrice() / priceDivisor)); // TODO: price when posting offer is reversed. this assumes crypto counter currency + expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / (buyOffers[1].getPrice() / priceDivisor)); + expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / (buyOffers[2].getPrice() / priceDivisor)); + expect(marketDepth.getBuyDepthList()[0]).toEqual(0.15); + expect(marketDepth.getBuyDepthList()[1]).toEqual(0.30); + expect(marketDepth.getBuyDepthList()[2]).toEqual(0.65); + + // test sell prices and depths + let sellOffers = (await alice.getOffers("sell")).concat(await alice.getMyOffers("sell")).sort(function(a, b) { return b.getPrice() - a.getPrice() }); + expect(marketDepth.getSellPricesList()[0]).toEqual(1 / (sellOffers[0].getPrice() / priceDivisor)); + expect(marketDepth.getSellPricesList()[1]).toEqual(1 / (sellOffers[1].getPrice() / priceDivisor)); + expect(marketDepth.getSellPricesList()[2]).toEqual(1 / (sellOffers[2].getPrice() / priceDivisor)); + expect(marketDepth.getSellDepthList()[0]).toEqual(0.3); + expect(marketDepth.getSellDepthList()[1]).toEqual(0.6); + expect(marketDepth.getSellDepthList()[2]).toEqual(1); + + // clear offers + await clearOffers(alice, counterCurrency); + await clearOffers(bob, counterCurrency); + + // test invalid currency + await expect(async () => {await alice.getMarketDepth("INVALID_CURRENCY")}) + .rejects + .toThrow('Currency not found: INVALID_CURRENCY'); +}); + test("Can register as dispute agents", async () => { await arbitrator.registerDisputeAgent("mediator", TestConfig.devPrivilegePrivKey); // TODO: bisq mediator = haveno arbitrator await arbitrator.registerDisputeAgent("refundagent", TestConfig.devPrivilegePrivKey); // TODO: no refund agent in haveno @@ -607,7 +689,7 @@ test("Can get payment accounts", async () => { }); test("Can create crypto payment accounts", async () => { - + // test each stagenet crypto account for (let testAccount of TestConfig.cryptoAccounts) { @@ -648,7 +730,7 @@ test("Can create crypto payment accounts", async () => { }); test("Can post and remove an offer", async () => { - + // wait for alice to have unlocked balance to post offer let tradeAmount: bigint = BigInt("250000000000"); await waitForUnlockedBalance(tradeAmount * BigInt("2"), alice); @@ -657,7 +739,7 @@ test("Can post and remove an offer", async () => { let unlockedBalanceBefore: bigint = BigInt((await alice.getBalances()).getUnlockedBalance()); // post offer - let offer: OfferInfo = await postOffer(alice, "buy", BigInt("200000000000"), undefined); + let offer: OfferInfo = await postOffer(alice); assert.equal(offer.getState(), "AVAILABLE"); // has offer @@ -688,7 +770,7 @@ test("Invalidates offers when reserved funds are spent", async () => { // post offer await wait(1000); - let offer: OfferInfo = await postOffer(alice, "buy", tradeAmount, undefined); + let offer: OfferInfo = await postOffer(alice, {amount: tradeAmount}); // get key images reserved by offer let reservedKeyImages = []; @@ -749,7 +831,7 @@ test("Handles unexpected errors during trade initialization", async () => { // trader 0 posts offer console.log("Posting offer"); - let offer = await postOffer(traders[0], "buy", tradeAmount, undefined); + let offer = await postOffer(traders[0], {amount: tradeAmount}); offer = await traders[0].getMyOffer(offer.getId()); assert.equal(offer.getState(), "AVAILABLE"); @@ -841,7 +923,7 @@ test("Cannot make or take offer with insufficient unlocked funds", async () => { // charlie cannot make offer with insufficient funds try { - await postOffer(charlie, "buy", BigInt("200000000000"), paymentAccount.getId()); + await postOffer(charlie, {paymentAccountId: paymentAccount.getId()}); throw new Error("Should have failed making offer with insufficient funds") } catch (err) { let errTyped = err as grpcWeb.RpcError; @@ -856,7 +938,7 @@ test("Cannot make or take offer with insufficient unlocked funds", async () => { else { let tradeAmount: bigint = BigInt("250000000000"); await waitForUnlockedBalance(tradeAmount * BigInt("2"), alice); - offer = await postOffer(alice, "buy", tradeAmount, undefined); + offer = await postOffer(alice, {amount: tradeAmount}); assert.equal(offer.getState(), "AVAILABLE"); await wait(TestConfig.walletSyncPeriodMs * 2); } @@ -907,7 +989,7 @@ test("Can complete a trade", async () => { // alice posts offer to buy xmr console.log("Alice posting offer"); let direction = "buy"; - let offer: OfferInfo = await postOffer(alice, direction, tradeAmount, undefined); + let offer: OfferInfo = await postOffer(alice, {direction: direction, amount: tradeAmount}); expect(offer.getState()).toEqual("AVAILABLE"); console.log("Alice done posting offer"); @@ -1073,7 +1155,7 @@ async function initHavenoDaemon(config?: any): Promise { return havenod; async function getAvailablePort(): Promise { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { let srv = net.createServer(); srv.listen(0, function() { let port = srv.address().port; @@ -1204,7 +1286,7 @@ async function waitForUnlockedBalance(amount: bigint, ...wallets: any[]) { await startMining(); let promises: Promise[] = []; for (let wallet of wallets) { - promises.push(new Promise(async function(resolve, reject) { + promises.push(new Promise(async function(resolve) { let taskLooper: any = new TaskLooper(async function() { if (await wallet.getUnlockedBalance() >= amount) { taskLooper.stop(); @@ -1224,7 +1306,7 @@ async function waitForUnlockedTxs(...txHashes: string[]) { let promises: Promise[] = [] for (let txHash of txHashes) { promises.push(new Promise(async function(resolve, reject) { - let taskLooper: any = new TaskLooper(async function() { + let taskLooper = new TaskLooper(async function() { let tx = await monerod.getTx(txHash); if (tx.isConfirmed() && tx.getBlock().getHeight() <= await monerod.getHeight() - 10) { taskLooper.stop(); @@ -1286,24 +1368,24 @@ function testTx(tx: XmrTx, ctx: TxContext) { assert(tx.getOutgoingTransfer() || tx.getIncomingTransfersList().length); // TODO (woodser): test transfers for (let incomingTransfer of tx.getIncomingTransfersList()) testTransfer(incomingTransfer, ctx); if (tx.getOutgoingTransfer()) testTransfer(tx.getOutgoingTransfer()!, ctx); - if (ctx.isCreatedTx) testCreatedTx(tx, ctx); + if (ctx.isCreatedTx) testCreatedTx(tx); } -function testCreatedTx(tx: XmrTx, ctx: TxContext) { +function testCreatedTx(tx: XmrTx) { assert.equal(tx.getTimestamp(), 0); assert.equal(tx.getIsConfirmed(), false); assert.equal(tx.getIsLocked(), true); assert(tx.getMetadata() && tx.getMetadata().length > 0); } -function testTransfer(transfer: XmrIncomingTransfer|XmrOutgoingTransfer, ctx: TxContext) { +function testTransfer(transfer: XmrIncomingTransfer | XmrOutgoingTransfer, ctx: TxContext) { expect(BigInt(transfer.getAmount())).toBeGreaterThanOrEqual(BigInt("0")); assert(transfer.getAccountIndex() >= 0); - if (transfer instanceof XmrIncomingTransfer) testIncomingTransfer(transfer, ctx); + if (transfer instanceof XmrIncomingTransfer) testIncomingTransfer(transfer); else testOutgoingTransfer(transfer, ctx); } -function testIncomingTransfer(transfer: XmrIncomingTransfer, ctx: TxContext) { +function testIncomingTransfer(transfer: XmrIncomingTransfer) { assert(transfer.getAddress()); assert(transfer.getSubaddressIndex() >= 0); assert(transfer.getNumSuggestedConfirmations() > 0); @@ -1330,34 +1412,45 @@ function testDestination(destination: XmrDestination) { expect(BigInt(destination.getAmount())).toBeGreaterThan(BigInt("0")); } -async function createCryptoPaymentAccount(trader: HavenoDaemon): Promise { - let testAccount = TestConfig.cryptoAccounts[0]; - let paymentAccount: PaymentAccount = await trader.createCryptoPaymentAccount( - testAccount.currencyCode + " " + testAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(), - testAccount.currencyCode, - testAccount.address); - return paymentAccount; +async function createCryptoPaymentAccount(trader: HavenoDaemon, currencyCode = "eth"): Promise { + for (let cryptoAccount of TestConfig.cryptoAccounts) { + if (cryptoAccount.currencyCode.toLowerCase() !== currencyCode.toLowerCase()) continue; + return trader.createCryptoPaymentAccount( + cryptoAccount.currencyCode + " " + cryptoAccount.address.substr(0, 8) + "... " + GenUtils.getUUID(), + cryptoAccount.currencyCode, + cryptoAccount.address); + } + throw new Error("No test config for crypto: " + currencyCode); } -async function postOffer(maker: HavenoDaemon, direction: string, amount: bigint, paymentAccountId: string|undefined) { - +// TODO: specify counter currency code +async function postOffer(maker: HavenoDaemon, config?: any) { + + // assign default options + config = Object.assign({}, TestConfig.postOffer, config); + + // wait for unlocked balance + if (config.awaitUnlockedBalance) await waitForUnlockedBalance(config.amount * BigInt("2"), maker); + // create payment account if not given - if (!paymentAccountId) paymentAccountId = (await createCryptoPaymentAccount(maker)).getId(); + if (!config.paymentAccountId) config.paymentAccountId = (await createCryptoPaymentAccount(maker, config.counterCurrency)).getId(); // get unlocked balance before reserving offer let unlockedBalanceBefore: bigint = BigInt((await maker.getBalances()).getUnlockedBalance()); // post offer - let offer: OfferInfo = await maker.postOffer("eth", - direction, // buy or sell xmr for eth - 12.378981, // price TODO: price is optional? price string gets converted to long? - true, // use market price - 0.02, // market price margin, e.g. within 2% - amount, // amount - BigInt("150000000000"), // min amount - 0.15, // buyer security deposit, e.g. 15% - paymentAccountId, // payment account id - undefined); // trigger price // TODO: fails if there is a decimal, gets converted to long? + // TODO: re-arrange post offer parameters like this postOffer() or use config interface? + let offer: OfferInfo = await maker.postOffer( + config.counterCurrency, + config.direction, + config.price, + config.price ? false : true, // TODO: redundant with price field? + config.priceMargin, + config.amount, + config.minAmount, + config.buyerSecurityDeposit, + config.paymentAccountId, + config.triggerPrice); testOffer(offer); // unlocked balance has decreased @@ -1365,12 +1458,12 @@ async function postOffer(maker: HavenoDaemon, direction: string, amount: bigint, if (unlockedBalanceAfter === unlockedBalanceBefore) throw new Error("unlocked balance did not change after posting offer"); // offer is included in my offers only - if (!getOffer(await maker.getMyOffers(direction), offer.getId())) { + if (!getOffer(await maker.getMyOffers(config.amountdirection), offer.getId())) { await wait(10000); - if (!getOffer(await maker.getMyOffers(direction), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in my offers"); + if (!getOffer(await maker.getMyOffers(config.amountdirection), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in my offers"); else console.log("The offer finally posted!"); } - if (getOffer(await maker.getOffers(direction), offer.getId())) throw new Error("My offer " + offer.getId() + " should not appear in available offers"); + if (getOffer(await maker.getOffers(config.amountdirection), offer.getId())) throw new Error("My offer " + offer.getId() + " should not appear in available offers"); return offer; } diff --git a/src/HavenoDaemon.ts b/src/HavenoDaemon.ts index 84f4250f..f0ddb4f2 100644 --- a/src/HavenoDaemon.ts +++ b/src/HavenoDaemon.ts @@ -2,7 +2,7 @@ import {HavenoUtils} from "./utils/HavenoUtils"; import {TaskLooper} from "./utils/TaskLooper"; import * as grpcWeb from 'grpc-web'; import {GetVersionClient, AccountClient, MoneroConnectionsClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient} from './protobuf/GrpcServiceClientPb'; -import {GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply} from './protobuf/grpc_pb'; +import {GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply} from './protobuf/grpc_pb'; import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb'; const console = require('console'); @@ -205,11 +205,11 @@ class HavenoDaemon { getAppName(): string|undefined { return this._appName; } - + /** * Get the Haveno version. * - * @return {string} the Haveno daemon version + * @return {string} the Haveno daemon version */ async getVersion(): Promise { let that = this; @@ -575,7 +575,7 @@ class HavenoDaemon { }); }); } - + /** * Automatically switch to the best available connection if current connection is disconnected after being checked. * @@ -668,7 +668,7 @@ class HavenoDaemon { } throw new Error("No transaction with hash " + txHash); } - + /** * Create but do not relay a transaction to send funds from the Monero wallet. * @@ -683,7 +683,7 @@ class HavenoDaemon { }); }); } - + /** * Relay a previously created transaction to send funds from the Monero wallet. * @@ -698,7 +698,7 @@ class HavenoDaemon { }); }); } - + /** * Get the current market price per 1 XMR in the given currency. * @@ -714,9 +714,9 @@ class HavenoDaemon { }); }); } - + /** - * Get the current market prices of all the currencies. + * Get the current market prices of all currencies. * * @return {MarketPrice[]} price per 1 XMR in all supported currencies (fiat & crypto) */ @@ -729,7 +729,22 @@ class HavenoDaemon { }); }); } - + + /** + * Get the market depth of a currency. + * + * @return {MarketDepthInfo} market depth of the given currency + */ + async getMarketDepth(currencyCode: string): Promise { + let that = this; + return new Promise(function(resolve, reject) { + that._priceClient.getMarketDepth(new MarketDepthRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketDepthReply) { + if (err) reject(err); + else resolve(response.getMarketDepth()); + }); + }); + } + /** * Get payment accounts. * @@ -773,10 +788,11 @@ class HavenoDaemon { /** * Get available offers to buy or sell XMR. * - * @param {string} direction - one of "BUY" or "SELL" // TODO (woodser): make optional - * @return {OfferInfo[]} available offers + * @param {string|undefined} direction - "buy" or "sell" (default all) + * @return {OfferInfo[]} the available offers */ - async getOffers(direction: string): Promise { + async getOffers(direction?: string): Promise { + if (!direction) return (await this.getOffers("buy")).concat(await this.getOffers("sell")); // TODO: implement in backend let that = this; return new Promise(function(resolve, reject) { that._offersClient.getOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.RpcError, response: GetOffersReply) { @@ -787,12 +803,13 @@ class HavenoDaemon { } /** - * Get user's created offers to buy or sell XMR. + * Get the user's posted offers to buy or sell XMR. * - * @param {string} direction - one of "BUY" or "SELL" // TODO (woodser): make optional + * @param {string|undefined} direction - "buy" or "sell" (default all) * @return {OfferInfo[]} the user's created offers */ - async getMyOffers(direction: string): Promise { + async getMyOffers(direction?: string): Promise { + if (!direction) return (await this.getMyOffers("buy")).concat(await this.getMyOffers("sell")); // TODO: implement in backend let that = this; return new Promise(function(resolve, reject) { that._offersClient.getMyOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.RpcError, response: GetOffersReply) { @@ -823,7 +840,7 @@ class HavenoDaemon { * @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 {bool} useMarketBasedPrice - base trade on market price // TODO: this field redundant with price * @param {number} marketPriceMargin - % from market price to tolerate * @param {bigint} amount - amount to trade * @param {bigint} minAmount - minimum amount to trade @@ -846,8 +863,8 @@ class HavenoDaemon { let request = new CreateOfferRequest() .setCurrencyCode(currencyCode) .setDirection(direction) - .setPrice(price.toString()) .setUseMarketBasedPrice(useMarketBasedPrice) + .setPrice(useMarketBasedPrice ? "1.0" : price.toString()) // TODO: positive price required even if using market price .setMarketPriceMargin(marketPriceMargin) .setAmount(amount.toString()) .setMinAmount(minAmount.toString()) @@ -973,7 +990,7 @@ class HavenoDaemon { }); if (this._process) return HavenoUtils.kill(this._process); } - + // ------------------------------- HELPERS ---------------------------------- /**