support and test taking trades within offer range

This commit is contained in:
woodser 2023-05-30 18:08:04 -04:00
parent 2980984850
commit f256fafa7d
9 changed files with 90 additions and 35 deletions

View File

@ -437,9 +437,10 @@ export default class HavenoClient {
* *
* @param {string} offerId - id of the offer to take * @param {string} offerId - id of the offer to take
* @param {string} paymentAccountId - id of the payment account * @param {string} paymentAccountId - id of the payment account
* @param {bigint|undefined} amount - amount the taker chooses to buy or sell within the offer range (default is max offer amount)
* @return {TradeInfo} the initialized trade * @return {TradeInfo} the initialized trade
*/ */
takeOffer(offerId: string, paymentAccountId: string): Promise<TradeInfo>; takeOffer(offerId: string, paymentAccountId: string, amount?: bigint): Promise<TradeInfo>;
/** /**
* Get a trade by id. * Get a trade by id.
* *

View File

@ -1021,13 +1021,17 @@ class HavenoClient {
* *
* @param {string} offerId - id of the offer to take * @param {string} offerId - id of the offer to take
* @param {string} paymentAccountId - id of the payment account * @param {string} paymentAccountId - id of the payment account
* @param {bigint|undefined} amount - amount the taker chooses to buy or sell within the offer range (default is max offer amount)
* @return {TradeInfo} the initialized trade * @return {TradeInfo} the initialized trade
*/ */
async takeOffer(offerId, paymentAccountId) { async takeOffer(offerId, paymentAccountId, amount) {
try { try {
const request = new grpc_pb_1.TakeOfferRequest() const request = new grpc_pb_1.TakeOfferRequest()
.setOfferId(offerId) .setOfferId(offerId)
.setPaymentAccountId(paymentAccountId); .setPaymentAccountId(paymentAccountId);
if (amount)
request.setAmount(amount.toString());
HavenoUtils_1.default.log(0, "Taking offer with taker amount: " + amount);
return (await this._tradesClient.takeOffer(request, { password: this._password })).getTrade(); return (await this._tradesClient.takeOffer(request, { password: this._password })).getTrade();
} }
catch (e) { catch (e) {

File diff suppressed because one or more lines are too long

View File

@ -2284,6 +2284,9 @@ export class TakeOfferRequest extends jspb.Message {
getPaymentAccountId(): string; getPaymentAccountId(): string;
setPaymentAccountId(value: string): TakeOfferRequest; setPaymentAccountId(value: string): TakeOfferRequest;
getAmount(): string;
setAmount(value: string): TakeOfferRequest;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): TakeOfferRequest.AsObject; toObject(includeInstance?: boolean): TakeOfferRequest.AsObject;
static toObject(includeInstance: boolean, msg: TakeOfferRequest): TakeOfferRequest.AsObject; static toObject(includeInstance: boolean, msg: TakeOfferRequest): TakeOfferRequest.AsObject;
@ -2296,6 +2299,7 @@ export namespace TakeOfferRequest {
export type AsObject = { export type AsObject = {
offerId: string, offerId: string,
paymentAccountId: string, paymentAccountId: string,
amount: string,
} }
} }

View File

@ -20540,7 +20540,8 @@ proto.io.haveno.protobuffer.TakeOfferRequest.prototype.toObject = function(opt_i
proto.io.haveno.protobuffer.TakeOfferRequest.toObject = function(includeInstance, msg) { proto.io.haveno.protobuffer.TakeOfferRequest.toObject = function(includeInstance, msg) {
var f, obj = { var f, obj = {
offerId: jspb.Message.getFieldWithDefault(msg, 1, ""), offerId: jspb.Message.getFieldWithDefault(msg, 1, ""),
paymentAccountId: jspb.Message.getFieldWithDefault(msg, 2, "") paymentAccountId: jspb.Message.getFieldWithDefault(msg, 2, ""),
amount: jspb.Message.getFieldWithDefault(msg, 3, "0")
}; };
if (includeInstance) { if (includeInstance) {
@ -20585,6 +20586,10 @@ proto.io.haveno.protobuffer.TakeOfferRequest.deserializeBinaryFromReader = funct
var value = /** @type {string} */ (reader.readString()); var value = /** @type {string} */ (reader.readString());
msg.setPaymentAccountId(value); msg.setPaymentAccountId(value);
break; break;
case 3:
var value = /** @type {string} */ (reader.readUint64String());
msg.setAmount(value);
break;
default: default:
reader.skipField(); reader.skipField();
break; break;
@ -20628,6 +20633,13 @@ proto.io.haveno.protobuffer.TakeOfferRequest.serializeBinaryToWriter = function(
f f
); );
} }
f = message.getAmount();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
3,
f
);
}
}; };
@ -20667,6 +20679,24 @@ proto.io.haveno.protobuffer.TakeOfferRequest.prototype.setPaymentAccountId = fun
}; };
/**
* optional uint64 amount = 3;
* @return {string}
*/
proto.io.haveno.protobuffer.TakeOfferRequest.prototype.getAmount = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "0"));
};
/**
* @param {string} value
* @return {!proto.io.haveno.protobuffer.TakeOfferRequest} returns this
*/
proto.io.haveno.protobuffer.TakeOfferRequest.prototype.setAmount = function(value) {
return jspb.Message.setProto3StringIntField(this, 3, value);
};

View File

@ -86,7 +86,7 @@ export default class HavenoUtils {
* @param {number|string} amountXmr - amount in XMR to convert to atomic units * @param {number|string} amountXmr - amount in XMR to convert to atomic units
* @return {BigInt} amount in atomic units * @return {BigInt} amount in atomic units
*/ */
static xmrToAtomicUnits(amountXmr: number | string): BigInt; static xmrToAtomicUnits(amountXmr: number | string): bigint;
/** /**
* Convert atomic units to XMR. * Convert atomic units to XMR.
* *

View File

@ -157,8 +157,8 @@ const TestConfig = {
takeOffer: true, takeOffer: true,
awaitFundsToMakeOffer: true, awaitFundsToMakeOffer: true,
direction: "buy", // buy or sell xmr direction: "buy", // buy or sell xmr
amount: BigInt("200000000000"), // amount of xmr to trade (0.2 XMR) offerAmount: BigInt("200000000000"), // amount of xmr to trade (0.2 XMR)
minAmount: undefined, offerMinAmount: undefined,
assetCode: "usd", // counter asset to trade assetCode: "usd", // counter asset to trade
makerPaymentAccountId: undefined, makerPaymentAccountId: undefined,
buyerSecurityDepositPct: 0.15, buyerSecurityDepositPct: 0.15,
@ -219,8 +219,9 @@ interface TradeContext {
awaitFundsToMakeOffer?: boolean awaitFundsToMakeOffer?: boolean
direction?: string, direction?: string,
assetCode?: string, assetCode?: string,
amount?: bigint, offerAmount?: bigint, // offer amount or max
minAmount?: bigint, offerMinAmount?: bigint,
tradeAmount?: bigint, // trade amount within offer range
makerPaymentAccountId?: string, makerPaymentAccountId?: string,
buyerSecurityDepositPct?: number, buyerSecurityDepositPct?: number,
price?: number, price?: number,
@ -924,13 +925,13 @@ test("Can get market depth (CI, sanity check)", async () => {
expect(marketDepth.getSellDepthList().length).toEqual(0); expect(marketDepth.getSellDepthList().length).toEqual(0);
// post offers to buy and sell // post offers to buy and sell
await makeOffer({maker: user1, direction: "buy", amount: BigInt("150000000000"), assetCode: assetCode, price: 17.0}); await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.0});
await makeOffer({maker: user1, direction: "buy", amount: BigInt("150000000000"), assetCode: assetCode, price: 17.2}); await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.2});
await makeOffer({maker: user1, direction: "buy", amount: BigInt("200000000000"), assetCode: assetCode, price: 17.3}); await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("200000000000"), assetCode: assetCode, price: 17.3});
await makeOffer({maker: user1, direction: "buy", amount: BigInt("150000000000"), assetCode: assetCode, price: 17.3}); await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.3});
await makeOffer({maker: user1, direction: "sell", amount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.00}); await makeOffer({maker: user1, direction: "sell", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.00});
await makeOffer({maker: user1, direction: "sell", amount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.02}); await makeOffer({maker: user1, direction: "sell", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.02});
await makeOffer({maker: user1, direction: "sell", amount: BigInt("400000000000"), assetCode: assetCode, priceMargin: 0.05}); await makeOffer({maker: user1, direction: "sell", offerAmount: BigInt("400000000000"), assetCode: assetCode, priceMargin: 0.05});
// get user2's market depth // get user2's market depth
await wait(TestConfig.trade.maxTimePeerNoticeMs); await wait(TestConfig.trade.maxTimePeerNoticeMs);
@ -1340,15 +1341,20 @@ test("Can schedule offers with locked funds (CI)", async () => {
test("Cannot post offer exceeding trade limit (CI, sanity check)", async () => { test("Cannot post offer exceeding trade limit (CI, sanity check)", async () => {
const revolutAccount = await createRevolutPaymentAccount(user1); const revolutAccount = await createRevolutPaymentAccount(user1);
try { try {
await executeTrade({amount: BigInt("2100000000000"), assetCode: "USD", makerPaymentAccountId: revolutAccount.getId(), takeOffer: false}); await executeTrade({offerAmount: BigInt("2100000000000"), assetCode: "USD", makerPaymentAccountId: revolutAccount.getId(), takeOffer: false});
throw new Error("Should have rejected posting offer above trade limit") throw new Error("Should have rejected posting offer above trade limit")
} catch (err) { } catch (err) {
assert(err.message.indexOf("amount is larger than") === 0); assert(err.message.indexOf("amount is larger than") === 0);
} }
}); });
test("Can complete a trade", async () => { test("Can complete a trade within a range", async () => {
await executeTrade(); await executeTrade({
price: 150,
offerAmount: HavenoUtils.xmrToAtomicUnits(1),
offerMinAmount: HavenoUtils.xmrToAtomicUnits(.15),
tradeAmount: HavenoUtils.xmrToAtomicUnits(.18)
});
}); });
test("Can complete trades at the same time (CI, sanity check)", async () => { test("Can complete trades at the same time (CI, sanity check)", async () => {
@ -1548,7 +1554,7 @@ test("Cannot make or take offer with insufficient unlocked funds (CI, sanity che
else { else {
const tradeAmount = BigInt("250000000000"); const tradeAmount = BigInt("250000000000");
await waitForAvailableBalance(tradeAmount * BigInt("2"), user1); await waitForAvailableBalance(tradeAmount * BigInt("2"), user1);
offer = await makeOffer({maker: user1, amount: tradeAmount, awaitFundsToMakeOffer: false}); offer = await makeOffer({maker: user1, offerAmount: tradeAmount, awaitFundsToMakeOffer: false});
assert.equal(offer.getState(), "AVAILABLE"); assert.equal(offer.getState(), "AVAILABLE");
await wait(TestConfig.trade.walletSyncPeriodMs * 2); await wait(TestConfig.trade.walletSyncPeriodMs * 2);
} }
@ -1598,7 +1604,7 @@ test("Invalidates offers when reserved funds are spent (CI)", async () => {
// post offer // post offer
await wait(1000); await wait(1000);
const assetCode = getRandomAssetCode(); const assetCode = getRandomAssetCode();
const offer: OfferInfo = await makeOffer({maker: user1, assetCode: assetCode, amount: tradeAmount}); const offer: OfferInfo = await makeOffer({maker: user1, assetCode: assetCode, offerAmount: tradeAmount});
// get key images reserved by offer // get key images reserved by offer
const reservedKeyImages: any[] = []; const reservedKeyImages: any[] = [];
@ -1660,7 +1666,7 @@ test("Can handle unexpected errors during trade initialization", async () => {
// trader 0 posts offer // trader 0 posts offer
HavenoUtils.log(1, "Posting offer"); HavenoUtils.log(1, "Posting offer");
let offer = await makeOffer({maker: traders[0], amount: tradeAmount}); let offer = await makeOffer({maker: traders[0], offerAmount: tradeAmount});
offer = await traders[0].getMyOffer(offer.getId()); offer = await traders[0].getMyOffer(offer.getId());
assert.equal(offer.getState(), "AVAILABLE"); assert.equal(offer.getState(), "AVAILABLE");
@ -1875,8 +1881,9 @@ function getTradeContexts(numConfigs: number): TradeContext[] {
function tradeContextToString(ctx: TradeContext) { function tradeContextToString(ctx: TradeContext) {
return JSON.stringify(Object.assign({}, ctx, { return JSON.stringify(Object.assign({}, ctx, {
amount: ctx.amount ? ctx.amount.toString() : undefined, offerAmount: ctx.offerAmount ? ctx.offerAmount.toString() : undefined,
minAmount: ctx.minAmount ? ctx.minAmount.toString() : undefined, offerMinAmount: ctx.offerMinAmount ? ctx.offerMinAmount.toString() : undefined,
tradeAmount: ctx.tradeAmount ? ctx.tradeAmount.toString() : undefined,
disputeWinnerAmount: ctx.disputeWinnerAmount ? ctx.disputeWinnerAmount.toString() : undefined, disputeWinnerAmount: ctx.disputeWinnerAmount ? ctx.disputeWinnerAmount.toString() : undefined,
arbitrator: ctx.arbitrator ? ctx.arbitrator.getUrl() : undefined, arbitrator: ctx.arbitrator ? ctx.arbitrator.getUrl() : undefined,
maker: ctx.maker ? ctx.maker.getUrl() : undefined, maker: ctx.maker ? ctx.maker.getUrl() : undefined,
@ -1911,7 +1918,7 @@ async function executeTrades(ctxs: TradeContext[], executionCtx?: TradeContext):
let tradeAmount: bigint|undefined = undefined; let tradeAmount: bigint|undefined = undefined;
const outputCounts = new Map<any, number>(); const outputCounts = new Map<any, number>();
for (const ctx of ctxs) { for (const ctx of ctxs) {
if (!tradeAmount || tradeAmount < ctx.amount!) tradeAmount = ctx.amount; // use max amount if (!tradeAmount || tradeAmount < ctx.offerAmount!) tradeAmount = ctx.offerAmount; // use max amount
if (ctx.awaitFundsToMakeOffer && ctx.makeOffer && !ctx.offerId) { if (ctx.awaitFundsToMakeOffer && ctx.makeOffer && !ctx.offerId) {
const wallet = await getWallet(ctx.maker!); const wallet = await getWallet(ctx.maker!);
if (outputCounts.has(wallet)) outputCounts.set(wallet, outputCounts.get(wallet)! + 1); if (outputCounts.has(wallet)) outputCounts.set(wallet, outputCounts.get(wallet)! + 1);
@ -1971,7 +1978,7 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
if (!ctx.concurrentTrades) { // already funded if (!ctx.concurrentTrades) { // already funded
if (ctx.awaitFundsToMakeOffer && makingOffer && !ctx.offerId) clientsToFund.push(ctx.maker!); if (ctx.awaitFundsToMakeOffer && makingOffer && !ctx.offerId) clientsToFund.push(ctx.maker!);
if (ctx.awaitFundsToTakeOffer && ctx.takeOffer && !ctx.isOfferTaken) clientsToFund.push(ctx.taker!); if (ctx.awaitFundsToTakeOffer && ctx.takeOffer && !ctx.isOfferTaken) clientsToFund.push(ctx.taker!);
await waitForAvailableBalance(ctx.amount! * BigInt("2"), ...clientsToFund); await waitForAvailableBalance(ctx.offerAmount! * BigInt("2"), ...clientsToFund);
} }
// get info before trade // get info before trade
@ -2207,8 +2214,8 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
if (!ctx.concurrentTrades) { if (!ctx.concurrentTrades) {
const buyerBalancesAfter = await getBuyer(ctx)!.getBalances(); const buyerBalancesAfter = await getBuyer(ctx)!.getBalances();
const sellerBalancesAfter = await getSeller(ctx)!.getBalances(); const sellerBalancesAfter = await getSeller(ctx)!.getBalances();
const buyerFee = BigInt(buyerBalancesBefore.getBalance()) + BigInt(buyerBalancesBefore.getReservedOfferBalance()) + BigInt(ctx.offer!.getAmount()) - (BigInt(buyerBalancesAfter.getBalance()) + BigInt(buyerBalancesAfter.getReservedOfferBalance())); // buyer fee = total balance before + offer amount - total balance after const buyerFee = BigInt(buyerBalancesBefore.getBalance()) + BigInt(buyerBalancesBefore.getReservedOfferBalance()) + BigInt(ctx.tradeAmount!) - (BigInt(buyerBalancesAfter.getBalance()) + BigInt(buyerBalancesAfter.getReservedOfferBalance())); // buyer fee = total balance before + trade amount - total balance after
const sellerFee = BigInt(sellerBalancesBefore.getBalance()) + BigInt(sellerBalancesBefore.getReservedOfferBalance()) - BigInt(ctx.offer!.getAmount()) - (BigInt(sellerBalancesAfter.getBalance()) + BigInt(sellerBalancesAfter.getReservedOfferBalance())); // seller fee = total balance before - offer amount - total balance after const sellerFee = BigInt(sellerBalancesBefore.getBalance()) + BigInt(sellerBalancesBefore.getReservedOfferBalance()) - BigInt(ctx.tradeAmount!) - (BigInt(sellerBalancesAfter.getBalance()) + BigInt(sellerBalancesAfter.getReservedOfferBalance())); // seller fee = total balance before - trade amount - total balance after
expect(buyerFee).toBeLessThanOrEqual(TestConfig.maxFee); expect(buyerFee).toBeLessThanOrEqual(TestConfig.maxFee);
expect(buyerFee).toBeGreaterThan(BigInt("0")); expect(buyerFee).toBeGreaterThan(BigInt("0"));
expect(sellerFee).toBeLessThanOrEqual(TestConfig.maxFee); expect(sellerFee).toBeLessThanOrEqual(TestConfig.maxFee);
@ -2270,7 +2277,7 @@ async function makeOffer(ctx?: TradeContext): Promise<OfferInfo> {
Object.assign(ctx, TestConfig.trade, Object.assign({}, ctx)); Object.assign(ctx, TestConfig.trade, Object.assign({}, ctx));
// wait for unlocked balance // wait for unlocked balance
if (!ctx.concurrentTrades && ctx.awaitFundsToMakeOffer) await waitForAvailableBalance(ctx.amount! * BigInt("2"), ctx.maker); if (!ctx.concurrentTrades && ctx.awaitFundsToMakeOffer) await waitForAvailableBalance(ctx.offerAmount! * BigInt("2"), ctx.maker);
// create payment account if not given // TODO: re-use existing payment account // create payment account if not given // TODO: re-use existing payment account
if (!ctx.makerPaymentAccountId) ctx.makerPaymentAccountId = (await createPaymentAccount(ctx.maker!, ctx.assetCode!)).getId(); if (!ctx.makerPaymentAccountId) ctx.makerPaymentAccountId = (await createPaymentAccount(ctx.maker!, ctx.assetCode!)).getId();
@ -2287,14 +2294,14 @@ async function makeOffer(ctx?: TradeContext): Promise<OfferInfo> {
// post offer // post offer
const offer: OfferInfo = await ctx.maker!.postOffer( const offer: OfferInfo = await ctx.maker!.postOffer(
ctx.direction!, ctx.direction!,
ctx.amount!, ctx.offerAmount!,
ctx.assetCode!, ctx.assetCode!,
ctx.makerPaymentAccountId!, ctx.makerPaymentAccountId!,
ctx.buyerSecurityDepositPct!, ctx.buyerSecurityDepositPct!,
ctx.price, ctx.price,
ctx.priceMargin, ctx.priceMargin,
ctx.triggerPrice, ctx.triggerPrice,
ctx.minAmount); ctx.offerMinAmount);
testOffer(offer, ctx); testOffer(offer, ctx);
// offer is included in my offers only // offer is included in my offers only
@ -2336,7 +2343,7 @@ async function takeOffer(ctx: TradeContext): Promise<TradeInfo> {
expect(takerOffer.getState()).toEqual("UNKNOWN"); // TODO: offer state should be known expect(takerOffer.getState()).toEqual("UNKNOWN"); // TODO: offer state should be known
// wait for unlocked balance // wait for unlocked balance
if (ctx.awaitFundsToTakeOffer) await waitForAvailableBalance(ctx.amount! * BigInt("2"), ctx.taker); if (ctx.awaitFundsToTakeOffer) await waitForAvailableBalance(ctx.offerAmount! * BigInt("2"), ctx.taker);
// create payment account if not given // TODO: re-use existing payment account // create payment account if not given // TODO: re-use existing payment account
if (!ctx.takerPaymentAccountId) ctx.takerPaymentAccountId = (await createPaymentAccount(ctx.taker!, ctx.assetCode!)).getId(); if (!ctx.takerPaymentAccountId) ctx.takerPaymentAccountId = (await createPaymentAccount(ctx.taker!, ctx.assetCode!)).getId();
@ -2351,9 +2358,12 @@ async function takeOffer(ctx: TradeContext): Promise<TradeInfo> {
const takerBalancesBefore: XmrBalanceInfo = await ctx.taker!.getBalances(); const takerBalancesBefore: XmrBalanceInfo = await ctx.taker!.getBalances();
const startTime = Date.now(); const startTime = Date.now();
HavenoUtils.log(1, "Taking offer " + ctx.offerId); HavenoUtils.log(1, "Taking offer " + ctx.offerId);
const trade = await ctx.taker!.takeOffer(ctx.offerId, ctx.takerPaymentAccountId!); const trade = await ctx.taker!.takeOffer(ctx.offerId, ctx.takerPaymentAccountId!, ctx.tradeAmount);
HavenoUtils.log(1, "Done taking offer " + ctx.offerId + " in " + (Date.now() - startTime) + " ms"); HavenoUtils.log(1, "Done taking offer " + ctx.offerId + " in " + (Date.now() - startTime) + " ms");
// assign expected trade amount
if (!ctx.tradeAmount) ctx.tradeAmount = ctx.offerAmount;
// test taker's balances after taking trade // test taker's balances after taking trade
if (!ctx.concurrentTrades) { if (!ctx.concurrentTrades) {
const takerBalancesAfter: XmrBalanceInfo = await ctx.taker!.getBalances(); const takerBalancesAfter: XmrBalanceInfo = await ctx.taker!.getBalances();
@ -2374,6 +2384,7 @@ async function takeOffer(ctx: TradeContext): Promise<TradeInfo> {
// taker can get trade // taker can get trade
let fetchedTrade: TradeInfo = await ctx.taker!.getTrade(trade.getTradeId()); let fetchedTrade: TradeInfo = await ctx.taker!.getTrade(trade.getTradeId());
assert(GenUtils.arrayContains(["DEPOSITS_PUBLISHED", "DEPOSITS_CONFIRMED", "DEPOSITS_UNLOCKED"], fetchedTrade.getPhase()), "Unexpected trade phase: " + fetchedTrade.getPhase()); assert(GenUtils.arrayContains(["DEPOSITS_PUBLISHED", "DEPOSITS_CONFIRMED", "DEPOSITS_UNLOCKED"], fetchedTrade.getPhase()), "Unexpected trade phase: " + fetchedTrade.getPhase());
expect(BigInt(fetchedTrade.getAmount())).toEqual(ctx.tradeAmount ? ctx.tradeAmount : ctx.offerAmount);
// TODO: test fetched trade // TODO: test fetched trade
// taker is notified of balance change // taker is notified of balance change

View File

@ -1046,13 +1046,18 @@ export default class HavenoClient {
* *
* @param {string} offerId - id of the offer to take * @param {string} offerId - id of the offer to take
* @param {string} paymentAccountId - id of the payment account * @param {string} paymentAccountId - id of the payment account
* @param {bigint|undefined} amount - amount the taker chooses to buy or sell within the offer range (default is max offer amount)
* @return {TradeInfo} the initialized trade * @return {TradeInfo} the initialized trade
*/ */
async takeOffer(offerId: string, paymentAccountId: string): Promise<TradeInfo> { async takeOffer(offerId: string,
paymentAccountId: string,
amount?: bigint): Promise<TradeInfo> {
try { try {
const request = new TakeOfferRequest() const request = new TakeOfferRequest()
.setOfferId(offerId) .setOfferId(offerId)
.setPaymentAccountId(paymentAccountId); .setPaymentAccountId(paymentAccountId);
if (amount) request.setAmount(amount.toString());
HavenoUtils.log(0, "Taking offer with taker amount: " + amount);
return (await this._tradesClient.takeOffer(request, {password: this._password})).getTrade()!; return (await this._tradesClient.takeOffer(request, {password: this._password})).getTrade()!;
} catch (e: any) { } catch (e: any) {
throw new HavenoError(e.message, e.code); throw new HavenoError(e.message, e.code);

View File

@ -147,7 +147,7 @@ export default class HavenoUtils {
* @param {number|string} amountXmr - amount in XMR to convert to atomic units * @param {number|string} amountXmr - amount in XMR to convert to atomic units
* @return {BigInt} amount in atomic units * @return {BigInt} amount in atomic units
*/ */
static xmrToAtomicUnits(amountXmr: number|string): BigInt { static xmrToAtomicUnits(amountXmr: number|string): bigint {
if (typeof amountXmr === "number") amountXmr = "" + amountXmr; if (typeof amountXmr === "number") amountXmr = "" + amountXmr;
else if (typeof amountXmr !== "string") throw new Error("Must provide XMR amount as a string or js number to convert to atomic units"); else if (typeof amountXmr !== "string") throw new Error("Must provide XMR amount as a string or js number to convert to atomic units");
let decimalDivisor = 1; let decimalDivisor = 1;