@atomiqlabs/lp-lib 17.4.1 → 17.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,6 +17,17 @@ const AmountAssertions_1 = require("../assertions/AmountAssertions");
|
|
|
17
17
|
const IPlugin_1 = require("../../plugins/IPlugin");
|
|
18
18
|
const StickyAddress_1 = require("./StickyAddress");
|
|
19
19
|
const TX_MAX_VSIZE = 16 * 1024;
|
|
20
|
+
function parseAmountAdjustUtxos(amountAdjustUtxos) {
|
|
21
|
+
if (!Array.isArray(amountAdjustUtxos))
|
|
22
|
+
return null;
|
|
23
|
+
if (amountAdjustUtxos.length > 250)
|
|
24
|
+
return null;
|
|
25
|
+
const validArray = amountAdjustUtxos.every(value => value != null && typeof (value) === "object" && typeof (value.value) === "number" && typeof (value.vSize) === "number" &&
|
|
26
|
+
(value.cpfp == null || (typeof (value.cpfp) === "object" && typeof (value.cpfp.effectiveVSize) === "number" && typeof (value.cpfp.effectiveFeeRate) === "number")));
|
|
27
|
+
if (!validArray)
|
|
28
|
+
return null;
|
|
29
|
+
return amountAdjustUtxos;
|
|
30
|
+
}
|
|
20
31
|
class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
21
32
|
constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config, stickyAddresses) {
|
|
22
33
|
super(storageDirectory, path, chainsData, swapPricing);
|
|
@@ -309,6 +320,23 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
309
320
|
code: 20100,
|
|
310
321
|
msg: "Invalid request body"
|
|
311
322
|
};
|
|
323
|
+
const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
|
|
324
|
+
amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
|
|
325
|
+
amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberOptional
|
|
326
|
+
});
|
|
327
|
+
if (inputAmountAdjustments == null)
|
|
328
|
+
throw {
|
|
329
|
+
code: 20100,
|
|
330
|
+
msg: "Invalid request body"
|
|
331
|
+
};
|
|
332
|
+
const clientInputUtxos = inputAmountAdjustments?.amountUtxos != null
|
|
333
|
+
? parseAmountAdjustUtxos(inputAmountAdjustments.amountUtxos)
|
|
334
|
+
: null;
|
|
335
|
+
if (inputAmountAdjustments?.amountUtxos != null && clientInputUtxos == null)
|
|
336
|
+
throw {
|
|
337
|
+
code: 20100,
|
|
338
|
+
msg: "Invalid request body (amountUtxos)"
|
|
339
|
+
};
|
|
312
340
|
const parsedBody = { ...preFetchParsedBody, ...actualParsedBody };
|
|
313
341
|
metadata.request = parsedBody;
|
|
314
342
|
if (parsedBody.gasToken !== chainInterface.getNativeCurrencyAddress())
|
|
@@ -333,6 +361,44 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
333
361
|
parsedBody.amount,
|
|
334
362
|
token: parsedBody.token
|
|
335
363
|
};
|
|
364
|
+
if (clientInputUtxos != null) {
|
|
365
|
+
if (parsedBody.exactOut)
|
|
366
|
+
throw {
|
|
367
|
+
code: 20193,
|
|
368
|
+
msg: "amountAdjustUtxos cannot be specified for exactOut swaps!"
|
|
369
|
+
};
|
|
370
|
+
let btcFeeRate = await btcFeeRatePrefetch;
|
|
371
|
+
if (inputAmountAdjustments.amountFeeRate != null && inputAmountAdjustments.amountFeeRate > btcFeeRate)
|
|
372
|
+
btcFeeRate = inputAmountAdjustments.amountFeeRate;
|
|
373
|
+
let feeAccumulator = 0;
|
|
374
|
+
let valueAccumulator = 0;
|
|
375
|
+
for (let utxo of clientInputUtxos) {
|
|
376
|
+
const cpfpAdditionalFee = utxo.cpfp == null ? 0 : Math.ceil(utxo.cpfp.effectiveVSize * Math.max(0, btcFeeRate - utxo.cpfp.effectiveFeeRate));
|
|
377
|
+
const spendFee = utxo.vSize * btcFeeRate;
|
|
378
|
+
const totalFee = cpfpAdditionalFee + spendFee;
|
|
379
|
+
if (totalFee > utxo.value)
|
|
380
|
+
continue; //Skip detrimental UTXO
|
|
381
|
+
feeAccumulator += totalFee;
|
|
382
|
+
valueAccumulator += utxo.value;
|
|
383
|
+
}
|
|
384
|
+
let baseTxVSize = 10.5; // 4b version, 1b inputs, 1b outputs, 4b locktime, 0.5vB witness flag + witness elements count
|
|
385
|
+
//vault input and output
|
|
386
|
+
baseTxVSize += 32 + 4 + 1 + 4; //Input base
|
|
387
|
+
baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? (1 + 1 + 65) / 4 : (1 + 1 + 72 + 1 + 33) / 4;
|
|
388
|
+
baseTxVSize += 8 + 1; //Output base
|
|
389
|
+
baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? 34 : 22;
|
|
390
|
+
//opreturn output
|
|
391
|
+
baseTxVSize += 8 + 1; //Output base
|
|
392
|
+
const opReturnDataSize = spvVaultContract.toOpReturnData(parsedBody.address, parsedBody.gasAmount > 0 ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]).length;
|
|
393
|
+
baseTxVSize += (opReturnDataSize <= 0x4b ? 2 : 3 /*Needs an OP_PUSHDATA1 opcode*/) + opReturnDataSize;
|
|
394
|
+
//LP output
|
|
395
|
+
baseTxVSize += 8 + 1; //Output base
|
|
396
|
+
baseTxVSize += this.bitcoin.getAddressType() === "p2tr" ? 34 : this.bitcoin.getAddressType() === "p2wpkh" ? 22 : 23;
|
|
397
|
+
const baseTxFee = Math.ceil(baseTxVSize) * btcFeeRate;
|
|
398
|
+
feeAccumulator += baseTxFee;
|
|
399
|
+
const amount = Math.floor(valueAccumulator - Math.ceil(feeAccumulator));
|
|
400
|
+
requestedAmount.amount = BigInt(amount);
|
|
401
|
+
}
|
|
336
402
|
const gasTokenAmount = {
|
|
337
403
|
input: false,
|
|
338
404
|
amount: parsedBody.gasAmount * (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100000n,
|
|
@@ -420,7 +486,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
420
486
|
gasSwapFee: gasSwapFeeInToken.toString(10),
|
|
421
487
|
callerFeeShare: callerFeeShare.toString(10),
|
|
422
488
|
frontingFeeShare: frontingFeeShare.toString(10),
|
|
423
|
-
executionFeeShare: executionFeeShare.toString(10)
|
|
489
|
+
executionFeeShare: executionFeeShare.toString(10),
|
|
490
|
+
usedUtxoInputCalculation: clientInputUtxos != null
|
|
424
491
|
}
|
|
425
492
|
});
|
|
426
493
|
}));
|
package/package.json
CHANGED
|
@@ -64,6 +64,26 @@ export type SpvVaultPostQuote = {
|
|
|
64
64
|
|
|
65
65
|
const TX_MAX_VSIZE = 16*1024;
|
|
66
66
|
|
|
67
|
+
type AmountAdjustUtxo = {
|
|
68
|
+
value: number,
|
|
69
|
+
vSize: number,
|
|
70
|
+
cpfp?: {
|
|
71
|
+
effectiveVSize: number,
|
|
72
|
+
effectiveFeeRate: number
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseAmountAdjustUtxos(amountAdjustUtxos: any): AmountAdjustUtxo[] {
|
|
77
|
+
if(!Array.isArray(amountAdjustUtxos)) return null;
|
|
78
|
+
if(amountAdjustUtxos.length > 250) return null;
|
|
79
|
+
const validArray = amountAdjustUtxos.every(value =>
|
|
80
|
+
value!=null && typeof(value)==="object" && typeof(value.value)==="number" && typeof(value.vSize)==="number" &&
|
|
81
|
+
(value.cpfp==null || (typeof(value.cpfp)==="object" && typeof(value.cpfp.effectiveVSize)==="number" && typeof(value.cpfp.effectiveFeeRate)==="number"))
|
|
82
|
+
);
|
|
83
|
+
if(!validArray) return null;
|
|
84
|
+
return amountAdjustUtxos;
|
|
85
|
+
}
|
|
86
|
+
|
|
67
87
|
export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapState> {
|
|
68
88
|
readonly type = SwapHandlerType.FROM_BTC_SPV;
|
|
69
89
|
readonly inflightSwapStates = new Set([SpvVaultSwapState.SIGNED, SpvVaultSwapState.SENT, SpvVaultSwapState.BTC_CONFIRMED]);
|
|
@@ -354,7 +374,7 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
354
374
|
gasTokenPricePrefetchPromise
|
|
355
375
|
} = this.getPricePrefetches(chainIdentifier, preFetchParsedBody.token, preFetchParsedBody.gasToken, abortController);
|
|
356
376
|
const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
|
|
357
|
-
const btcFeeRatePrefetch = this.bitcoin.getFeeRate().catch(e => {
|
|
377
|
+
const btcFeeRatePrefetch: Promise<number> = this.bitcoin.getFeeRate().catch(e => {
|
|
358
378
|
abortController.abort(e);
|
|
359
379
|
return null;
|
|
360
380
|
});
|
|
@@ -405,6 +425,23 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
405
425
|
msg: "Invalid request body"
|
|
406
426
|
};
|
|
407
427
|
|
|
428
|
+
const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
|
|
429
|
+
amountUtxos: FieldTypeEnum.AnyOptional,
|
|
430
|
+
amountFeeRate: FieldTypeEnum.NumberOptional
|
|
431
|
+
});
|
|
432
|
+
if(inputAmountAdjustments==null) throw {
|
|
433
|
+
code: 20100,
|
|
434
|
+
msg: "Invalid request body"
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const clientInputUtxos: AmountAdjustUtxo[] | null = inputAmountAdjustments?.amountUtxos!=null
|
|
438
|
+
? parseAmountAdjustUtxos(inputAmountAdjustments.amountUtxos)
|
|
439
|
+
: null;
|
|
440
|
+
if(inputAmountAdjustments?.amountUtxos!=null && clientInputUtxos==null) throw {
|
|
441
|
+
code: 20100,
|
|
442
|
+
msg: "Invalid request body (amountUtxos)"
|
|
443
|
+
};
|
|
444
|
+
|
|
408
445
|
const parsedBody: SpvVaultSwapRequestType = {...preFetchParsedBody, ...actualParsedBody};
|
|
409
446
|
metadata.request = parsedBody;
|
|
410
447
|
|
|
@@ -429,6 +466,48 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
429
466
|
parsedBody.amount,
|
|
430
467
|
token: parsedBody.token
|
|
431
468
|
};
|
|
469
|
+
if(clientInputUtxos!=null) {
|
|
470
|
+
if(parsedBody.exactOut) throw {
|
|
471
|
+
code: 20193,
|
|
472
|
+
msg: "amountAdjustUtxos cannot be specified for exactOut swaps!"
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
let btcFeeRate = await btcFeeRatePrefetch;
|
|
476
|
+
if(inputAmountAdjustments.amountFeeRate!=null && inputAmountAdjustments.amountFeeRate>btcFeeRate)
|
|
477
|
+
btcFeeRate = inputAmountAdjustments.amountFeeRate;
|
|
478
|
+
|
|
479
|
+
let feeAccumulator: number = 0;
|
|
480
|
+
let valueAccumulator: number = 0;
|
|
481
|
+
for(let utxo of clientInputUtxos) {
|
|
482
|
+
const cpfpAdditionalFee: number = utxo.cpfp==null ? 0 : Math.ceil(utxo.cpfp.effectiveVSize * Math.max(0, btcFeeRate - utxo.cpfp.effectiveFeeRate));
|
|
483
|
+
const spendFee: number = utxo.vSize * btcFeeRate;
|
|
484
|
+
const totalFee: number = cpfpAdditionalFee + spendFee;
|
|
485
|
+
if(totalFee > utxo.value) continue; //Skip detrimental UTXO
|
|
486
|
+
feeAccumulator += totalFee;
|
|
487
|
+
valueAccumulator += utxo.value;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let baseTxVSize: number = 10.5; // 4b version, 1b inputs, 1b outputs, 4b locktime, 0.5vB witness flag + witness elements count
|
|
491
|
+
//vault input and output
|
|
492
|
+
baseTxVSize += 32 + 4 + 1 + 4; //Input base
|
|
493
|
+
baseTxVSize += this.vaultSigner.getAddressType()==="p2tr" ? (1+1+65)/4 : (1+1+72+1+33)/4;
|
|
494
|
+
baseTxVSize += 8 + 1; //Output base
|
|
495
|
+
baseTxVSize += this.vaultSigner.getAddressType()==="p2tr" ? 34 : 22;
|
|
496
|
+
//opreturn output
|
|
497
|
+
baseTxVSize += 8 + 1; //Output base
|
|
498
|
+
const opReturnDataSize = spvVaultContract.toOpReturnData(parsedBody.address, parsedBody.gasAmount > 0 ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]).length;
|
|
499
|
+
baseTxVSize += (opReturnDataSize <= 0x4b ? 2 : 3 /*Needs an OP_PUSHDATA1 opcode*/) + opReturnDataSize;
|
|
500
|
+
//LP output
|
|
501
|
+
baseTxVSize += 8 + 1; //Output base
|
|
502
|
+
baseTxVSize += this.bitcoin.getAddressType()==="p2tr" ? 34 : this.bitcoin.getAddressType()==="p2wpkh" ? 22 : 23;
|
|
503
|
+
|
|
504
|
+
const baseTxFee = Math.ceil(baseTxVSize) * btcFeeRate;
|
|
505
|
+
feeAccumulator += baseTxFee;
|
|
506
|
+
|
|
507
|
+
const amount = Math.floor(valueAccumulator - Math.ceil(feeAccumulator));
|
|
508
|
+
requestedAmount.amount = BigInt(amount);
|
|
509
|
+
}
|
|
510
|
+
|
|
432
511
|
const gasTokenAmount = {
|
|
433
512
|
input: false,
|
|
434
513
|
amount: parsedBody.gasAmount * (100_000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100_000n,
|
|
@@ -562,7 +641,9 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
562
641
|
|
|
563
642
|
callerFeeShare: callerFeeShare.toString(10),
|
|
564
643
|
frontingFeeShare: frontingFeeShare.toString(10),
|
|
565
|
-
executionFeeShare: executionFeeShare.toString(10)
|
|
644
|
+
executionFeeShare: executionFeeShare.toString(10),
|
|
645
|
+
|
|
646
|
+
usedUtxoInputCalculation: clientInputUtxos!=null
|
|
566
647
|
}
|
|
567
648
|
});
|
|
568
649
|
}));
|