From a3fc5d4491d30129a6547adaed0f6858e2a491db Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Thu, 19 Feb 2026 09:27:27 -0500 Subject: [PATCH 1/2] test editing and cloning offers with different taker fees --- src/HavenoClient.test.ts | 36 ++++++++++++++++++++++++++++-------- src/HavenoClient.ts | 33 +++++++++++++++++---------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/HavenoClient.test.ts b/src/HavenoClient.test.ts index 5d09fc39..f7611e86 100644 --- a/src/HavenoClient.test.ts +++ b/src/HavenoClient.test.ts @@ -1644,20 +1644,24 @@ test("Can post, deactivate, activate, edit, and remove an offer (Test, CI, sanit ctx.extraInfo = "My edited extra info"; ctx.price = undefined; ctx.marketPriceMarginPct = 0.15; + const editedAssetCode = "BCH"; + let paymentAccountId = (await createPaymentAccount(user1, editedAssetCode)).getId(); offer = await user1.editOffer({ offerId: offer.getId(), price: ctx.price, marketPriceMarginPct: ctx.marketPriceMarginPct, - extraInfo: ctx.extraInfo + extraInfo: ctx.extraInfo, + paymentAccountId: paymentAccountId }); assert.equal(offer.getState(), "AVAILABLE"); + assert.equal(offer.getCounterCurrencyCode(), editedAssetCode); if (ctx.marketPriceMarginPct) assert.equal(offer.getMarketPriceMarginPct(), ctx.marketPriceMarginPct); if (ctx.price) expect(parseFloat(offer.getPrice())).toEqual(ctx.price); expect(offer.getExtraInfo()).toContain("My edited extra info"); // peer sees edited offer await wait(TestConfig.trade.maxTimePeerNoticeMs); - peerOffer = getOffer(await user2.getOffers(assetCode, TestConfig.trade.direction), offer.getId()); + peerOffer = getOffer(await user2.getOffers(editedAssetCode, TestConfig.trade.direction), offer.getId()); if (!peerOffer) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after edited"); testOffer(peerOffer, ctx, false); expect(peerOffer.getExtraInfo()).toContain("My edited extra info"); @@ -1687,15 +1691,14 @@ test("Can clone offers (Test, CI, sanity check)", async () => { const availableBalanceBefore = BigInt((await user1.getBalances()).getAvailableBalance()); // post offer - let assetCode = "BCH"; - let ctx: Partial = {maker: {havenod: user1}, direction: OfferDirection.SELL, isPrivateOffer: true, buyerAsTakerWithoutDeposit: true, assetCode: assetCode, extraInfo: "My extra info"}; + let assetCode = "USD"; + let ctx: Partial = {maker: {havenod: user1}, direction: OfferDirection.SELL, assetCode: assetCode, extraInfo: "My extra info"}; let offer: OfferInfo = await makeOffer(ctx); assert.equal(offer.getState(), "AVAILABLE"); - // clone offer - const clonedOffer = await makeOffer({ + // clone offer with same fiat account + let clonedOffer = await user1.postOffer({ sourceOfferId: offer.getId(), - assetCode: "BCH" }); assert.notEqual(clonedOffer.getId(), offer.getId()); assert.equal(clonedOffer.getState(), "DEACTIVATED"); // deactivated if same payment method and currency @@ -1704,8 +1707,25 @@ test("Can clone offers (Test, CI, sanity check)", async () => { assert.equal(clonedOffer.getAmount(), offer.getAmount()); assert.equal(clonedOffer.getMinAmount(), offer.getMinAmount()); assert.equal(clonedOffer.getIsPrivateOffer(), offer.getIsPrivateOffer()); + + // clone offer with crypto account + const cryptoAssetCode = "BCH"; + let paymentAccountId = (await createPaymentAccount(user1, cryptoAssetCode)).getId(); + clonedOffer = await user1.postOffer({ + sourceOfferId: offer.getId(), + paymentAccountId: paymentAccountId + }); + assert.notEqual(clonedOffer.getId(), offer.getId()); + assert.equal(clonedOffer.getState(), "AVAILABLE"); + assert.equal(clonedOffer.getCounterCurrencyCode(), cryptoAssetCode); + assert.equal(clonedOffer.getBaseCurrencyCode(), "XMR"); + assert.equal(clonedOffer.getAmount(), offer.getAmount()); + assert.equal(clonedOffer.getMinAmount(), offer.getMinAmount()); + assert.equal(clonedOffer.getIsPrivateOffer(), offer.getIsPrivateOffer()); - // TODO: test edited fields on clone, etc + // TODO: test edited fields on clone + + // TODO: test peer seeing cloned offers // remove offers await user1.removeOffer(offer.getId()); diff --git a/src/HavenoClient.ts b/src/HavenoClient.ts index ef2a3106..f30952df 100644 --- a/src/HavenoClient.ts +++ b/src/HavenoClient.ts @@ -1134,23 +1134,24 @@ export default class HavenoClient { */ async postOffer(config: PostOfferConfig): Promise { console.log("Posting offer with security deposit %: " + config.securityDepositPct) + const request = new PostOfferRequest(); + if (config.direction) request.setDirection(config.direction === OfferDirection.BUY ? "buy" : "sell"); + if (config.amount) request.setAmount(config.amount.toString()); + if (config.minAmount) request.setMinAmount(config.minAmount.toString()); + else if (config.amount) request.setMinAmount(config.amount.toString()); + if (config.assetCode) request.setCurrencyCode(config.assetCode); + if (config.paymentAccountId) request.setPaymentAccountId(config.paymentAccountId); + if (config.securityDepositPct) request.setSecurityDepositPct(config.securityDepositPct); + request.setUseMarketBasedPrice(config.price === undefined); + if (config.price) request.setPrice(config.price?.toString()) + if (config.marketPriceMarginPct) request.setMarketPriceMarginPct(config.marketPriceMarginPct); + if (config.triggerPrice) request.setTriggerPrice(config.triggerPrice.toString()); + if (config.reserveExactAmount) request.setReserveExactAmount(true); + if (config.isPrivateOffer) request.setIsPrivateOffer(true); + if (config.buyerAsTakerWithoutDeposit) request.setBuyerAsTakerWithoutDeposit(true); + if (config.extraInfo) request.setExtraInfo(config.extraInfo); + if (config.sourceOfferId) request.setSourceOfferId(config.sourceOfferId); try { - const request = new PostOfferRequest(); - if (config.direction) request.setDirection(config.direction === OfferDirection.BUY ? "buy" : "sell"); - if (config.amount) request.setAmount(config.amount.toString()); - request.setMinAmount(config.minAmount ? config.minAmount.toString() : config.amount!.toString()); - if (config.assetCode) request.setCurrencyCode(config.assetCode); - if (config.paymentAccountId) request.setPaymentAccountId(config.paymentAccountId); - if (config.securityDepositPct) request.setSecurityDepositPct(config.securityDepositPct); - request.setUseMarketBasedPrice(config.price === undefined); - if (config.price) request.setPrice(config.price?.toString()) - if (config.marketPriceMarginPct) request.setMarketPriceMarginPct(config.marketPriceMarginPct); - if (config.triggerPrice) request.setTriggerPrice(config.triggerPrice.toString()); - if (config.reserveExactAmount) request.setReserveExactAmount(true); - if (config.isPrivateOffer) request.setIsPrivateOffer(true); - if (config.buyerAsTakerWithoutDeposit) request.setBuyerAsTakerWithoutDeposit(true); - if (config.extraInfo) request.setExtraInfo(config.extraInfo); - if (config.sourceOfferId) request.setSourceOfferId(config.sourceOfferId); return (await this._offersClient.postOffer(request, {password: this._password})).getOffer()!; } catch (e: any) { throw new HavenoError(e.message, e.code); From ef663f778a93f3ef4a272adbc26bb8a70227169d Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:18:02 -0500 Subject: [PATCH 2/2] insufficient funds test uses first available offer --- src/HavenoClient.test.ts | 51 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/HavenoClient.test.ts b/src/HavenoClient.test.ts index f7611e86..ba4491b2 100644 --- a/src/HavenoClient.test.ts +++ b/src/HavenoClient.test.ts @@ -1697,31 +1697,31 @@ test("Can clone offers (Test, CI, sanity check)", async () => { assert.equal(offer.getState(), "AVAILABLE"); // clone offer with same fiat account - let clonedOffer = await user1.postOffer({ + const clonedOffer1 = await user1.postOffer({ sourceOfferId: offer.getId(), }); - assert.notEqual(clonedOffer.getId(), offer.getId()); - assert.equal(clonedOffer.getState(), "DEACTIVATED"); // deactivated if same payment method and currency - assert.equal(clonedOffer.getCounterCurrencyCode(), assetCode); - assert.equal(clonedOffer.getBaseCurrencyCode(), "XMR"); - assert.equal(clonedOffer.getAmount(), offer.getAmount()); - assert.equal(clonedOffer.getMinAmount(), offer.getMinAmount()); - assert.equal(clonedOffer.getIsPrivateOffer(), offer.getIsPrivateOffer()); + assert.notEqual(clonedOffer1.getId(), offer.getId()); + assert.equal(clonedOffer1.getState(), "DEACTIVATED"); // deactivated if same payment method and currency + assert.equal(clonedOffer1.getCounterCurrencyCode(), assetCode); + assert.equal(clonedOffer1.getBaseCurrencyCode(), "XMR"); + assert.equal(clonedOffer1.getAmount(), offer.getAmount()); + assert.equal(clonedOffer1.getMinAmount(), offer.getMinAmount()); + assert.equal(clonedOffer1.getIsPrivateOffer(), offer.getIsPrivateOffer()); // clone offer with crypto account const cryptoAssetCode = "BCH"; let paymentAccountId = (await createPaymentAccount(user1, cryptoAssetCode)).getId(); - clonedOffer = await user1.postOffer({ + const clonedOffer2 = await user1.postOffer({ sourceOfferId: offer.getId(), paymentAccountId: paymentAccountId }); - assert.notEqual(clonedOffer.getId(), offer.getId()); - assert.equal(clonedOffer.getState(), "AVAILABLE"); - assert.equal(clonedOffer.getCounterCurrencyCode(), cryptoAssetCode); - assert.equal(clonedOffer.getBaseCurrencyCode(), "XMR"); - assert.equal(clonedOffer.getAmount(), offer.getAmount()); - assert.equal(clonedOffer.getMinAmount(), offer.getMinAmount()); - assert.equal(clonedOffer.getIsPrivateOffer(), offer.getIsPrivateOffer()); + assert.notEqual(clonedOffer2.getId(), offer.getId()); + assert.equal(clonedOffer2.getState(), "AVAILABLE"); + assert.equal(clonedOffer2.getCounterCurrencyCode(), cryptoAssetCode); + assert.equal(clonedOffer2.getBaseCurrencyCode(), "XMR"); + assert.equal(clonedOffer2.getAmount(), offer.getAmount()); + assert.equal(clonedOffer2.getMinAmount(), offer.getMinAmount()); + assert.equal(clonedOffer2.getIsPrivateOffer(), offer.getIsPrivateOffer()); // TODO: test edited fields on clone @@ -1729,7 +1729,8 @@ test("Can clone offers (Test, CI, sanity check)", async () => { // remove offers await user1.removeOffer(offer.getId()); - await user1.removeOffer(clonedOffer.getId()); + await user1.removeOffer(clonedOffer1.getId()); + await user1.removeOffer(clonedOffer2.getId()); }); // TODO: provide number of confirmations in offer status @@ -2295,11 +2296,16 @@ test("Cannot make or take offer with insufficient funds (Test, CI, sanity check) assert.equal(errTyped.code, 2); } - // user1 gets or posts offer + // user1 gets or posts available offer + let offer: OfferInfo | undefined = undefined; const offers: OfferInfo[] = await user1.getMyOffers(TestConfig.trade.assetCode); - let offer: OfferInfo; - if (offers.length) offer = offers[0]; - else { + for (const anOffer of offers) { + if (anOffer.getState() === "AVAILABLE") { + offer = anOffer; + break; + } + } + if (offer === undefined) { const tradeAmount = 250000000000n; await waitForAvailableBalance(tradeAmount * 2n, user1); offer = await makeOffer({maker: {havenod: user1}, offerAmount: tradeAmount, awaitFundsToMakeOffer: false}); @@ -2307,6 +2313,9 @@ test("Cannot make or take offer with insufficient funds (Test, CI, sanity check) await wait(TestConfig.trade.walletSyncPeriodMs * 2); } + // user3 sees offer + if (!getOffer(await user3.getOffers(offer.getCounterCurrencyCode()), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in user3's offers"); + // user3 cannot take offer with insufficient funds try { await user3.takeOffer(offer.getId(), paymentAccount.getId());