mirror of
https://github.com/haveno-dex/haveno-ts.git
synced 2025-02-22 07:29:49 -05:00
support and test taking trades within offer range
This commit is contained in:
parent
2980984850
commit
f256fafa7d
3
dist/HavenoClient.d.ts
vendored
3
dist/HavenoClient.d.ts
vendored
@ -437,9 +437,10 @@ export default class HavenoClient {
|
||||
*
|
||||
* @param {string} offerId - id of the offer to take
|
||||
* @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
|
||||
*/
|
||||
takeOffer(offerId: string, paymentAccountId: string): Promise<TradeInfo>;
|
||||
takeOffer(offerId: string, paymentAccountId: string, amount?: bigint): Promise<TradeInfo>;
|
||||
/**
|
||||
* Get a trade by id.
|
||||
*
|
||||
|
6
dist/HavenoClient.js
vendored
6
dist/HavenoClient.js
vendored
@ -1021,13 +1021,17 @@ class HavenoClient {
|
||||
*
|
||||
* @param {string} offerId - id of the offer to take
|
||||
* @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
|
||||
*/
|
||||
async takeOffer(offerId, paymentAccountId) {
|
||||
async takeOffer(offerId, paymentAccountId, amount) {
|
||||
try {
|
||||
const request = new grpc_pb_1.TakeOfferRequest()
|
||||
.setOfferId(offerId)
|
||||
.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();
|
||||
}
|
||||
catch (e) {
|
||||
|
2
dist/HavenoClient.js.map
vendored
2
dist/HavenoClient.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/protobuf/grpc_pb.d.ts
vendored
4
dist/protobuf/grpc_pb.d.ts
vendored
@ -2284,6 +2284,9 @@ export class TakeOfferRequest extends jspb.Message {
|
||||
getPaymentAccountId(): string;
|
||||
setPaymentAccountId(value: string): TakeOfferRequest;
|
||||
|
||||
getAmount(): string;
|
||||
setAmount(value: string): TakeOfferRequest;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): TakeOfferRequest.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: TakeOfferRequest): TakeOfferRequest.AsObject;
|
||||
@ -2296,6 +2299,7 @@ export namespace TakeOfferRequest {
|
||||
export type AsObject = {
|
||||
offerId: string,
|
||||
paymentAccountId: string,
|
||||
amount: string,
|
||||
}
|
||||
}
|
||||
|
||||
|
32
dist/protobuf/grpc_pb.js
vendored
32
dist/protobuf/grpc_pb.js
vendored
@ -20540,7 +20540,8 @@ proto.io.haveno.protobuffer.TakeOfferRequest.prototype.toObject = function(opt_i
|
||||
proto.io.haveno.protobuffer.TakeOfferRequest.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
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) {
|
||||
@ -20585,6 +20586,10 @@ proto.io.haveno.protobuffer.TakeOfferRequest.deserializeBinaryFromReader = funct
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setPaymentAccountId(value);
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {string} */ (reader.readUint64String());
|
||||
msg.setAmount(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@ -20628,6 +20633,13 @@ proto.io.haveno.protobuffer.TakeOfferRequest.serializeBinaryToWriter = function(
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
2
dist/utils/HavenoUtils.d.ts
vendored
2
dist/utils/HavenoUtils.d.ts
vendored
@ -86,7 +86,7 @@ export default class HavenoUtils {
|
||||
* @param {number|string} amountXmr - amount in XMR to convert to 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.
|
||||
*
|
||||
|
@ -157,8 +157,8 @@ const TestConfig = {
|
||||
takeOffer: true,
|
||||
awaitFundsToMakeOffer: true,
|
||||
direction: "buy", // buy or sell xmr
|
||||
amount: BigInt("200000000000"), // amount of xmr to trade (0.2 XMR)
|
||||
minAmount: undefined,
|
||||
offerAmount: BigInt("200000000000"), // amount of xmr to trade (0.2 XMR)
|
||||
offerMinAmount: undefined,
|
||||
assetCode: "usd", // counter asset to trade
|
||||
makerPaymentAccountId: undefined,
|
||||
buyerSecurityDepositPct: 0.15,
|
||||
@ -219,8 +219,9 @@ interface TradeContext {
|
||||
awaitFundsToMakeOffer?: boolean
|
||||
direction?: string,
|
||||
assetCode?: string,
|
||||
amount?: bigint,
|
||||
minAmount?: bigint,
|
||||
offerAmount?: bigint, // offer amount or max
|
||||
offerMinAmount?: bigint,
|
||||
tradeAmount?: bigint, // trade amount within offer range
|
||||
makerPaymentAccountId?: string,
|
||||
buyerSecurityDepositPct?: number,
|
||||
price?: number,
|
||||
@ -924,13 +925,13 @@ test("Can get market depth (CI, sanity check)", async () => {
|
||||
expect(marketDepth.getSellDepthList().length).toEqual(0);
|
||||
|
||||
// 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", amount: 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", amount: 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", amount: 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: "buy", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.0});
|
||||
await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("150000000000"), assetCode: assetCode, price: 17.2});
|
||||
await makeOffer({maker: user1, direction: "buy", offerAmount: BigInt("200000000000"), 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", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.00});
|
||||
await makeOffer({maker: user1, direction: "sell", offerAmount: BigInt("300000000000"), assetCode: assetCode, priceMargin: 0.02});
|
||||
await makeOffer({maker: user1, direction: "sell", offerAmount: BigInt("400000000000"), assetCode: assetCode, priceMargin: 0.05});
|
||||
|
||||
// get user2's market depth
|
||||
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 () => {
|
||||
const revolutAccount = await createRevolutPaymentAccount(user1);
|
||||
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")
|
||||
} catch (err) {
|
||||
assert(err.message.indexOf("amount is larger than") === 0);
|
||||
}
|
||||
});
|
||||
|
||||
test("Can complete a trade", async () => {
|
||||
await executeTrade();
|
||||
test("Can complete a trade within a range", async () => {
|
||||
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 () => {
|
||||
@ -1548,7 +1554,7 @@ test("Cannot make or take offer with insufficient unlocked funds (CI, sanity che
|
||||
else {
|
||||
const tradeAmount = BigInt("250000000000");
|
||||
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");
|
||||
await wait(TestConfig.trade.walletSyncPeriodMs * 2);
|
||||
}
|
||||
@ -1598,7 +1604,7 @@ test("Invalidates offers when reserved funds are spent (CI)", async () => {
|
||||
// post offer
|
||||
await wait(1000);
|
||||
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
|
||||
const reservedKeyImages: any[] = [];
|
||||
@ -1660,7 +1666,7 @@ test("Can handle unexpected errors during trade initialization", async () => {
|
||||
|
||||
// trader 0 posts 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());
|
||||
assert.equal(offer.getState(), "AVAILABLE");
|
||||
|
||||
@ -1875,8 +1881,9 @@ function getTradeContexts(numConfigs: number): TradeContext[] {
|
||||
|
||||
function tradeContextToString(ctx: TradeContext) {
|
||||
return JSON.stringify(Object.assign({}, ctx, {
|
||||
amount: ctx.amount ? ctx.amount.toString() : undefined,
|
||||
minAmount: ctx.minAmount ? ctx.minAmount.toString() : undefined,
|
||||
offerAmount: ctx.offerAmount ? ctx.offerAmount.toString() : undefined,
|
||||
offerMinAmount: ctx.offerMinAmount ? ctx.offerMinAmount.toString() : undefined,
|
||||
tradeAmount: ctx.tradeAmount ? ctx.tradeAmount.toString() : undefined,
|
||||
disputeWinnerAmount: ctx.disputeWinnerAmount ? ctx.disputeWinnerAmount.toString() : undefined,
|
||||
arbitrator: ctx.arbitrator ? ctx.arbitrator.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;
|
||||
const outputCounts = new Map<any, number>();
|
||||
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) {
|
||||
const wallet = await getWallet(ctx.maker!);
|
||||
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.awaitFundsToMakeOffer && makingOffer && !ctx.offerId) clientsToFund.push(ctx.maker!);
|
||||
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
|
||||
@ -2207,8 +2214,8 @@ async function executeTrade(ctx?: TradeContext): Promise<string> {
|
||||
if (!ctx.concurrentTrades) {
|
||||
const buyerBalancesAfter = await getBuyer(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 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 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.tradeAmount!) - (BigInt(sellerBalancesAfter.getBalance()) + BigInt(sellerBalancesAfter.getReservedOfferBalance())); // seller fee = total balance before - trade amount - total balance after
|
||||
expect(buyerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
||||
expect(buyerFee).toBeGreaterThan(BigInt("0"));
|
||||
expect(sellerFee).toBeLessThanOrEqual(TestConfig.maxFee);
|
||||
@ -2270,7 +2277,7 @@ async function makeOffer(ctx?: TradeContext): Promise<OfferInfo> {
|
||||
Object.assign(ctx, TestConfig.trade, Object.assign({}, ctx));
|
||||
|
||||
// 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
|
||||
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
|
||||
const offer: OfferInfo = await ctx.maker!.postOffer(
|
||||
ctx.direction!,
|
||||
ctx.amount!,
|
||||
ctx.offerAmount!,
|
||||
ctx.assetCode!,
|
||||
ctx.makerPaymentAccountId!,
|
||||
ctx.buyerSecurityDepositPct!,
|
||||
ctx.price,
|
||||
ctx.priceMargin,
|
||||
ctx.triggerPrice,
|
||||
ctx.minAmount);
|
||||
ctx.offerMinAmount);
|
||||
testOffer(offer, ctx);
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
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 startTime = Date.now();
|
||||
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");
|
||||
|
||||
// assign expected trade amount
|
||||
if (!ctx.tradeAmount) ctx.tradeAmount = ctx.offerAmount;
|
||||
|
||||
// test taker's balances after taking trade
|
||||
if (!ctx.concurrentTrades) {
|
||||
const takerBalancesAfter: XmrBalanceInfo = await ctx.taker!.getBalances();
|
||||
@ -2374,6 +2384,7 @@ async function takeOffer(ctx: TradeContext): Promise<TradeInfo> {
|
||||
// taker can get trade
|
||||
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());
|
||||
expect(BigInt(fetchedTrade.getAmount())).toEqual(ctx.tradeAmount ? ctx.tradeAmount : ctx.offerAmount);
|
||||
// TODO: test fetched trade
|
||||
|
||||
// taker is notified of balance change
|
||||
|
@ -1046,13 +1046,18 @@ export default class HavenoClient {
|
||||
*
|
||||
* @param {string} offerId - id of the offer to take
|
||||
* @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
|
||||
*/
|
||||
async takeOffer(offerId: string, paymentAccountId: string): Promise<TradeInfo> {
|
||||
async takeOffer(offerId: string,
|
||||
paymentAccountId: string,
|
||||
amount?: bigint): Promise<TradeInfo> {
|
||||
try {
|
||||
const request = new TakeOfferRequest()
|
||||
.setOfferId(offerId)
|
||||
.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()!;
|
||||
} catch (e: any) {
|
||||
throw new HavenoError(e.message, e.code);
|
||||
|
@ -147,7 +147,7 @@ export default class HavenoUtils {
|
||||
* @param {number|string} amountXmr - amount in XMR to convert to 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;
|
||||
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user