@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "17.4.1",
3
+ "version": "17.5.0",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -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
  }));