@atomiqlabs/lp-lib 10.3.11 → 11.0.1

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.
Files changed (97) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.js +3 -4
  3. package/dist/plugins/IPlugin.d.ts +3 -2
  4. package/dist/plugins/PluginManager.d.ts +3 -2
  5. package/dist/plugins/PluginManager.js +2 -2
  6. package/dist/prices/OKXSwapPrice.d.ts +27 -0
  7. package/dist/prices/OKXSwapPrice.js +106 -0
  8. package/dist/swaps/FromBtcBaseSwap.d.ts +5 -1
  9. package/dist/swaps/FromBtcBaseSwap.js +20 -0
  10. package/dist/swaps/FromBtcBaseSwapHandler.d.ts +1 -0
  11. package/dist/swaps/FromBtcBaseSwapHandler.js +1 -1
  12. package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +8 -6
  13. package/dist/swaps/FromBtcLnBaseSwapHandler.js +7 -5
  14. package/dist/swaps/SwapHandler.d.ts +1 -4
  15. package/dist/swaps/SwapHandler.js +1 -2
  16. package/dist/swaps/SwapHandlerSwap.d.ts +4 -0
  17. package/dist/swaps/SwapHandlerSwap.js +9 -1
  18. package/dist/swaps/ToBtcBaseSwap.d.ts +3 -1
  19. package/dist/swaps/ToBtcBaseSwap.js +8 -2
  20. package/dist/swaps/ToBtcBaseSwapHandler.d.ts +1 -0
  21. package/dist/swaps/ToBtcBaseSwapHandler.js +1 -1
  22. package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +3 -5
  23. package/dist/swaps/frombtc_abstract/FromBtcAbs.js +18 -25
  24. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +1 -4
  25. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +3 -16
  26. package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +6 -9
  27. package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +238 -137
  28. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +9 -6
  29. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +15 -10
  30. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
  31. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +42 -62
  32. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +1 -6
  33. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +2 -14
  34. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +3 -5
  35. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +90 -87
  36. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +1 -2
  37. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +5 -8
  38. package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +5 -125
  39. package/dist/swaps/tobtc_abstract/ToBtcAbs.js +41 -334
  40. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +1 -4
  41. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +2 -11
  42. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +5 -55
  43. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +152 -398
  44. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +1 -6
  45. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +2 -15
  46. package/dist/utils/Utils.d.ts +0 -10
  47. package/dist/utils/Utils.js +1 -34
  48. package/dist/wallets/IBitcoinWallet.d.ts +62 -0
  49. package/dist/wallets/IBitcoinWallet.js +2 -0
  50. package/dist/wallets/ILightningWallet.d.ts +118 -0
  51. package/dist/wallets/ILightningWallet.js +37 -0
  52. package/package.json +4 -9
  53. package/src/index.ts +4 -5
  54. package/src/plugins/IPlugin.ts +4 -2
  55. package/src/plugins/PluginManager.ts +6 -3
  56. package/src/prices/OKXSwapPrice.ts +114 -0
  57. package/src/swaps/FromBtcBaseSwap.ts +24 -1
  58. package/src/swaps/FromBtcBaseSwapHandler.ts +6 -2
  59. package/src/swaps/FromBtcLnBaseSwapHandler.ts +22 -6
  60. package/src/swaps/SwapHandler.ts +1 -8
  61. package/src/swaps/SwapHandlerSwap.ts +14 -1
  62. package/src/swaps/ToBtcBaseSwap.ts +12 -3
  63. package/src/swaps/ToBtcBaseSwapHandler.ts +6 -2
  64. package/src/swaps/frombtc_abstract/FromBtcAbs.ts +24 -28
  65. package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +3 -18
  66. package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +260 -159
  67. package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +22 -15
  68. package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +69 -79
  69. package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +3 -20
  70. package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +108 -103
  71. package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +6 -9
  72. package/src/swaps/tobtc_abstract/ToBtcAbs.ts +52 -410
  73. package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +3 -18
  74. package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +157 -434
  75. package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +3 -20
  76. package/src/utils/Utils.ts +0 -31
  77. package/src/wallets/IBitcoinWallet.ts +66 -0
  78. package/src/wallets/ILightningWallet.ts +179 -0
  79. package/dist/fees/OneDollarFeeEstimator.d.ts +0 -16
  80. package/dist/fees/OneDollarFeeEstimator.js +0 -71
  81. package/dist/utils/coinselect2/accumulative.d.ts +0 -6
  82. package/dist/utils/coinselect2/accumulative.js +0 -44
  83. package/dist/utils/coinselect2/blackjack.d.ts +0 -6
  84. package/dist/utils/coinselect2/blackjack.js +0 -41
  85. package/dist/utils/coinselect2/index.d.ts +0 -16
  86. package/dist/utils/coinselect2/index.js +0 -40
  87. package/dist/utils/coinselect2/utils.d.ts +0 -64
  88. package/dist/utils/coinselect2/utils.js +0 -121
  89. package/src/fees/OneDollarFeeEstimator.ts +0 -95
  90. package/src/utils/coinselect2/accumulative.js +0 -32
  91. package/src/utils/coinselect2/accumulative.ts +0 -58
  92. package/src/utils/coinselect2/blackjack.js +0 -29
  93. package/src/utils/coinselect2/blackjack.ts +0 -54
  94. package/src/utils/coinselect2/index.js +0 -16
  95. package/src/utils/coinselect2/index.ts +0 -50
  96. package/src/utils/coinselect2/utils.js +0 -110
  97. package/src/utils/coinselect2/utils.ts +0 -183
@@ -1,41 +1,36 @@
1
1
  import {FromBtcBaseConfig, FromBtcBaseSwapHandler} from "../FromBtcBaseSwapHandler";
2
2
  import {FromBtcTrustedSwap, FromBtcTrustedSwapState} from "./FromBtcTrustedSwap";
3
- import {BitcoinRpc, BtcBlock, BtcTx, ClaimEvent, InitializeEvent, RefundEvent, SwapData} from "@atomiqlabs/base";
3
+ import {
4
+ BitcoinRpc,
5
+ BtcBlock,
6
+ BtcTx,
7
+ BtcVout,
8
+ ClaimEvent,
9
+ InitializeEvent,
10
+ RefundEvent,
11
+ SwapData
12
+ } from "@atomiqlabs/base";
4
13
  import {Express, Request, Response} from "express";
5
14
  import {MultichainData, SwapHandlerType} from "../SwapHandler";
6
15
  import * as BN from "bn.js";
7
16
  import {IIntermediaryStorage} from "../../storage/IIntermediaryStorage";
8
- import {
9
- AuthenticatedLnd,
10
- broadcastChainTransaction,
11
- ChainTransaction, createChainAddress, createHodlInvoice,
12
- getChainTransactions, getHeight,
13
- signPsbt,
14
- subscribeToTransactions, SubscribeToTransactionsChainTransactionEvent
15
- } from "lightning";
16
17
  import {ISwapPrice} from "../ISwapPrice";
17
18
  import {PluginManager} from "../../plugins/PluginManager";
18
- import {address, networks, Psbt, Transaction, TxOutput} from "bitcoinjs-lib";
19
- import {IBtcFeeEstimator} from "../../fees/IBtcFeeEstimator";
20
- import {utils} from "../../utils/coinselect2/utils";
21
19
  import {expressHandlerWrapper, HEX_REGEX} from "../../utils/Utils";
22
20
  import {IParamReader} from "../../utils/paramcoders/IParamReader";
23
21
  import {ServerParamEncoder} from "../../utils/paramcoders/server/ServerParamEncoder";
24
22
  import {FieldTypeEnum, verifySchema} from "../../utils/paramcoders/SchemaVerifier";
25
- import * as bitcoin from "bitcoinjs-lib";
26
23
  import {serverParamDecoder} from "../../utils/paramcoders/server/ServerParamDecoder";
24
+ import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
27
25
 
28
26
  export type FromBtcTrustedConfig = FromBtcBaseConfig & {
29
- bitcoinNetwork: networks.Network,
30
- feeEstimator: IBtcFeeEstimator,
31
27
  doubleSpendCheckInterval: number,
32
28
  swapAddressExpiry: number,
33
- recommendFeeMultiplier?: number,
29
+ recommendFeeMultiplier?: number
34
30
  }
35
31
 
36
32
  export type FromBtcTrustedRequestType = {
37
33
  address: string,
38
- refundAddress: string,
39
34
  amount: BN,
40
35
  exactOut?: boolean
41
36
  };
@@ -44,6 +39,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
44
39
  readonly type: SwapHandlerType = SwapHandlerType.FROM_BTC_TRUSTED;
45
40
 
46
41
  readonly config: FromBtcTrustedConfig;
42
+ readonly bitcoin: IBitcoinWallet;
47
43
  readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
48
44
 
49
45
  readonly subscriptions: Map<string, FromBtcTrustedSwap> = new Map<string, FromBtcTrustedSwap>();
@@ -51,81 +47,72 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
51
47
 
52
48
  readonly refundedSwaps: Map<string, string> = new Map();
53
49
  readonly doubleSpentSwaps: Map<string, string> = new Map();
54
- readonly processedTxIds: Map<string, { txId: string, adjustedAmount: BN, adjustedTotal: BN }> = new Map();
50
+ readonly processedTxIds: Map<string, { scTxId: string, txId: string, adjustedAmount: BN, adjustedTotal: BN }> = new Map();
55
51
 
56
52
  constructor(
57
53
  storageDirectory: IIntermediaryStorage<FromBtcTrustedSwap>,
58
54
  path: string,
59
55
  chains: MultichainData,
60
- lnd: AuthenticatedLnd,
56
+ bitcoin: IBitcoinWallet,
61
57
  swapPricing: ISwapPrice,
62
58
  bitcoinRpc: BitcoinRpc<BtcBlock>,
63
59
  config: FromBtcTrustedConfig
64
60
  ) {
65
- super(storageDirectory, path, chains, lnd, swapPricing);
61
+ super(storageDirectory, path, chains, swapPricing);
66
62
  this.config = config;
67
63
  this.config.recommendFeeMultiplier ??= 1.25;
64
+ this.bitcoin = bitcoin;
68
65
  this.bitcoinRpc = bitcoinRpc;
69
66
  for(let chainId in chains.chains) {
70
67
  this.allowedTokens[chainId] = new Set<string>([chains.chains[chainId].swapContract.getNativeCurrencyAddress()]);
71
68
  }
72
69
  }
73
70
 
74
- private getAllAncestors(tx: ChainTransaction): Promise<{tx: BtcTx, vout: number}[]> {
75
- return Promise.all(tx.inputs.map(input => this.bitcoinRpc.getTransaction(input.transaction_id).then(tx => {
76
- return {tx, vout: input.transaction_vout}
71
+ private getAllAncestors(tx: BtcTx): Promise<{tx: BtcTx, vout: number}[]> {
72
+ return Promise.all(tx.ins.map(input => this.bitcoinRpc.getTransaction(input.txid).then(tx => {
73
+ return {tx, vout: input.vout}
77
74
  })));
78
75
  }
79
76
 
80
77
  private async refundSwap(swap: FromBtcTrustedSwap) {
78
+ if(swap.refundAddress==null) {
79
+ if(swap.state!==FromBtcTrustedSwapState.REFUNDABLE) {
80
+ await swap.setState(FromBtcTrustedSwapState.REFUNDABLE);
81
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
82
+ }
83
+ return;
84
+ }
85
+
81
86
  let unlock = swap.lock(30*1000);
82
87
  if(unlock==null) return;
83
88
 
84
- const feeRate = await this.config.feeEstimator.estimateFee();
89
+ const feeRate = await this.bitcoin.getFeeRate();
85
90
 
86
- const initialTx = Transaction.fromHex(swap.rawTx);
87
- const ourOutput = initialTx.outs[swap.vout];
91
+ const ourOutput = swap.btcTx.outs[swap.vout];
88
92
 
89
- //Construct PSBT
90
- const refundOutputScript = address.toOutputScript(swap.refundAddress, this.config.bitcoinNetwork);
91
- const txBytes = utils.transactionBytes([{type: "p2wpkh"}], [{script: refundOutputScript}], "p2wpkh");
92
- const txFee = txBytes*feeRate;
93
- const adjustedOutput = ourOutput.value-txFee;
94
- if(adjustedOutput<546) {
93
+ const resp = await this.bitcoin.drainAll(swap.refundAddress, [{
94
+ type: this.bitcoin.getAddressType(),
95
+ confirmations: swap.btcTx.confirmations,
96
+ outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
97
+ value: ourOutput.value,
98
+ txId: swap.btcTx.txid,
99
+ vout: swap.vout
100
+ }], feeRate);
101
+
102
+ if(resp==null) {
95
103
  this.swapLogger.error(swap, "refundSwap(): cannot refund swap because of dust limit, txId: "+swap.txId);
96
104
  unlock();
97
105
  return;
98
106
  }
99
107
 
100
- //Construct PSBT
101
- const _psbt = new Psbt({network: this.config.bitcoinNetwork});
102
- _psbt.addInput({
103
- hash: initialTx.getHash(),
104
- index: swap.vout,
105
- witnessUtxo: ourOutput,
106
- sighashType: 0x01,
107
- sequence: 0xfffffffd
108
- });
109
- _psbt.addOutput({
110
- script: refundOutputScript,
111
- value: adjustedOutput
112
- });
113
-
114
- //Sign
115
- const {psbt, transaction} = await signPsbt({
116
- lnd: this.LND,
117
- psbt: _psbt.toHex()
118
- });
119
108
  if(swap.metadata!=null) swap.metadata.times.refundSignPSBT = Date.now();
120
- this.swapLogger.debug(swap, "refundSwap(): signed raw transaction: "+transaction);
109
+ this.swapLogger.debug(swap, "refundSwap(): signed raw transaction: "+resp.raw);
121
110
 
122
- const signedTx = Transaction.fromHex(transaction);
123
- const refundTxId = signedTx.getId();
111
+ const refundTxId = resp.txId;
124
112
  swap.refundTxId = refundTxId;
125
113
 
126
114
  //Send the refund TX
127
- await broadcastChainTransaction({transaction, lnd: this.LND});
128
-
115
+ await this.bitcoin.sendRawTransaction(resp.raw);
129
116
  this.swapLogger.debug(swap, "refundSwap(): sent refund transaction: "+refundTxId);
130
117
 
131
118
  this.refundedSwaps.set(swap.getHash(), refundTxId);
@@ -134,128 +121,143 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
134
121
  }
135
122
 
136
123
  private async burn(swap: FromBtcTrustedSwap) {
137
- const initialTx = Transaction.fromHex(swap.rawTx);
138
- const ourOutput = initialTx.outs[swap.vout];
124
+ const ourOutput = swap.btcTx.outs[swap.vout];
125
+
126
+ //Check if we can even increase the feeRate by burning
127
+ const txSize = 110;
128
+ const burnTxFeeRate = Math.floor(ourOutput.value/txSize);
129
+ const initialTxFeeRate = Math.ceil(swap.txFee/swap.txSize);
130
+
131
+ if(burnTxFeeRate<initialTxFeeRate) {
132
+ this.swapLogger.warn(swap, "burn(): cannot send burn transaction, pays too little fee, " +
133
+ "initialTxId: "+swap.txId+" initialTxFeeRate: "+initialTxFeeRate+" burnTxFeeRate: "+burnTxFeeRate);
134
+ this.doubleSpentSwaps.set(swap.getHash(), null);
135
+ await this.removeSwapData(swap, FromBtcTrustedSwapState.DOUBLE_SPENT);
136
+ return;
137
+ }
139
138
 
140
139
  //Construct PSBT
141
- const _psbt = new Psbt({network: this.config.bitcoinNetwork});
142
- _psbt.addInput({
143
- hash: initialTx.getHash(),
144
- index: swap.vout,
145
- witnessUtxo: ourOutput,
146
- sighashType: 0x01,
147
- sequence: 0xfffffffd
148
- });
149
- _psbt.addOutput({
150
- script: Buffer.concat([Buffer.from([0x6a, 20]), Buffer.from("BURN, BABY, BURN! AQ", "ascii")]),
151
- value: 0
152
- });
153
-
154
- //Sign
155
- const {psbt, transaction} = await signPsbt({
156
- lnd: this.LND,
157
- psbt: _psbt.toHex()
158
- });
140
+ const resp = await this.bitcoin.burnAll([{
141
+ type: this.bitcoin.getAddressType(),
142
+ confirmations: swap.btcTx.confirmations,
143
+ outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
144
+ value: ourOutput.value,
145
+ txId: swap.btcTx.txid,
146
+ vout: swap.vout
147
+ }]);
159
148
  if(swap.metadata!=null) swap.metadata.times.burnSignPSBT = Date.now();
160
- this.swapLogger.debug(swap, "burn(): signed raw transaction: "+transaction);
149
+ this.swapLogger.debug(swap, "burn(): signed raw transaction: "+resp.raw);
161
150
 
162
- const signedTx = Transaction.fromHex(transaction);
163
- const burnTxId = signedTx.getId();
151
+ const burnTxId = resp.txId;
164
152
  swap.burnTxId = burnTxId;
165
153
 
166
154
  //Send the original TX + our burn TX as a package
167
- const sendTxns = [swap.rawTx, transaction];
168
- await this.bitcoinRpc.sendRawPackage(sendTxns);
155
+ const sendTxns = [swap.btcTx.raw, resp.raw];
156
+ //TODO: We should handle this in a better way
157
+ try {
158
+ await this.bitcoinRpc.sendRawPackage(sendTxns);
159
+ this.swapLogger.debug(swap, "burn(): sent burn transaction: "+burnTxId);
160
+ } catch (e) {
161
+ this.swapLogger.error(swap, "burn(): error sending burn package: ", e);
162
+ }
169
163
 
170
- this.swapLogger.debug(swap, "burn(): sent burn transaction: "+burnTxId);
171
164
  this.doubleSpentSwaps.set(swap.getHash(), burnTxId);
172
165
  await this.removeSwapData(swap, FromBtcTrustedSwapState.DOUBLE_SPENT);
173
166
  }
174
167
 
175
- protected async processPastSwap(swap: FromBtcTrustedSwap, tx: ChainTransaction | null): Promise<void> {
176
- let parsedTx: Transaction = null;
177
- let foundVout: TxOutput = null;
178
- let vout: number = -1;
179
- if(tx!=null) {
180
- parsedTx = Transaction.fromHex(tx.transaction);
181
- const requiredOutputScript = address.toOutputScript(swap.btcAddress, this.config.bitcoinNetwork);
182
- vout = parsedTx.outs.findIndex(vout => vout.script.equals(requiredOutputScript));
183
- if(vout!==-1) foundVout = parsedTx.outs[vout];
184
- }
168
+ protected async processPastSwap(swap: FromBtcTrustedSwap, tx: BtcTx | null, vout: number | null): Promise<void> {
169
+ const foundVout: BtcVout = tx.outs[vout];
185
170
 
186
171
  const {swapContract, signer} = this.getChain(swap.chainIdentifier);
187
172
 
173
+ const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
174
+
188
175
  if(swap.state===FromBtcTrustedSwapState.CREATED) {
189
- this.subscriptions.set(swap.btcAddress, swap);
176
+ this.subscriptions.set(outputScript, swap);
190
177
  if(foundVout==null) {
191
178
  //Check expiry
192
179
  if(swap.expiresAt<Date.now()) {
193
- this.subscriptions.delete(swap.btcAddress);
180
+ this.subscriptions.delete(outputScript);
181
+ await this.bitcoin.addUnusedAddress(swap.btcAddress);
194
182
  await this.removeSwapData(swap, FromBtcTrustedSwapState.EXPIRED);
195
183
  return;
196
184
  }
197
185
  return;
198
186
  }
199
187
  const sentSats = new BN(foundVout.value);
200
- if(sentSats.eq(swap.inputSats)) {
201
- swap.adjustedInput = swap.inputSats;
188
+ if(sentSats.eq(swap.amount)) {
189
+ swap.adjustedInput = swap.amount;
202
190
  swap.adjustedOutput = swap.outputTokens;
203
191
  } else {
204
192
  //If lower than minimum then ignore
205
193
  if(sentSats.lt(this.config.min)) return;
206
194
  if(sentSats.gt(this.config.max)) {
207
- swap.rawTx = tx.transaction;
208
- swap.txId = tx.id;
195
+ swap.adjustedInput = sentSats;
196
+ swap.btcTx = tx;
197
+ swap.txId = tx.txid;
209
198
  swap.vout = vout;
210
- this.subscriptions.delete(swap.btcAddress);
199
+ this.subscriptions.delete(outputScript);
211
200
  await this.refundSwap(swap);
212
201
  return;
213
202
  }
214
203
  //Adjust the amount
215
204
  swap.adjustedInput = sentSats;
216
- swap.adjustedOutput = swap.outputTokens.mul(sentSats).div(swap.inputSats);
205
+ swap.adjustedOutput = swap.outputTokens.mul(sentSats).div(swap.amount);
217
206
  }
218
- swap.rawTx = tx.transaction;
219
- swap.txId = tx.id;
207
+ swap.btcTx = tx;
208
+ swap.txId = tx.txid;
220
209
  swap.vout = vout;
221
- this.subscriptions.delete(swap.btcAddress);
210
+ this.subscriptions.delete(outputScript);
222
211
  await swap.setState(FromBtcTrustedSwapState.RECEIVED);
223
- await this.storageManager.saveData(swap.getHash(), null, swap);
212
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
224
213
  }
225
214
 
226
215
  if(swap.state===FromBtcTrustedSwapState.RECEIVED) {
227
216
  //Check if transaction still exists
228
- if(tx==null || foundVout==null || tx.id!==swap.txId) {
217
+ if(tx==null || foundVout==null || tx.txid!==swap.txId) {
229
218
  await swap.setState(FromBtcTrustedSwapState.CREATED);
230
- await this.storageManager.saveData(swap.getHash(), null, swap);
219
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
231
220
  return;
232
221
  }
233
222
  //Check if it is confirmed
234
- if(tx.confirmation_count>0) {
223
+ if(tx.confirmations>0) {
235
224
  await swap.setState(FromBtcTrustedSwapState.BTC_CONFIRMED);
236
- await this.storageManager.saveData(swap.getHash(), null, swap);
225
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
237
226
  } else {
238
227
  //Check if it pays high enough fee AND has confirmed ancestors
239
228
  const ancestors = await this.getAllAncestors(tx);
240
229
  const allAncestorsConfirmed = ancestors.reduce((prev, curr) => prev && curr.tx.confirmations>0, true);
241
230
  const totalInput = ancestors.reduce((prev, curr) => prev + curr.tx.outs[curr.vout].value, 0);
242
- const totalOutput = parsedTx.outs.reduce((prev, curr) => prev + curr.value, 0);
231
+ const totalOutput = tx.outs.reduce((prev, curr) => prev + curr.value, 0);
243
232
  const fee = totalInput-totalOutput;
244
- const feePerVbyte = Math.ceil(fee/parsedTx.virtualSize());
233
+ const feePerVbyte = Math.ceil(fee/tx.vsize);
245
234
  if(
246
235
  allAncestorsConfirmed &&
247
- (feePerVbyte>=swap.recommendedFee || feePerVbyte>=await this.config.feeEstimator.estimateFee())
236
+ (feePerVbyte>=swap.recommendedFee || feePerVbyte>=await this.bitcoin.getFeeRate())
248
237
  ) {
249
238
  if(swap.state!==FromBtcTrustedSwapState.RECEIVED) return;
239
+ swap.txSize = tx.vsize;
240
+ swap.txFee = fee;
250
241
  await swap.setState(FromBtcTrustedSwapState.BTC_CONFIRMED);
251
- await this.storageManager.saveData(swap.getHash(), null, swap);
242
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
252
243
  } else {
253
244
  return;
254
245
  }
255
246
  }
256
247
  }
257
248
 
258
- if(swap.doubleSpent || tx==null || foundVout==null || tx.id!==swap.txId) {
249
+ if(swap.state===FromBtcTrustedSwapState.REFUNDABLE) {
250
+ if(swap.refundAddress!=null) {
251
+ await this.refundSwap(swap);
252
+ return;
253
+ }
254
+ }
255
+
256
+ if(swap.doubleSpent || tx==null || foundVout==null || tx.txid!==swap.txId) {
257
+ if(swap.state===FromBtcTrustedSwapState.REFUNDABLE) {
258
+ await swap.setState(FromBtcTrustedSwapState.CREATED);
259
+ return;
260
+ }
259
261
  if(!swap.doubleSpent) {
260
262
  swap.doubleSpent = true;
261
263
  try {
@@ -268,14 +270,13 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
268
270
  }
269
271
  return;
270
272
  } else {
271
- if(!this.doubleSpendWatchdogSwaps.has(swap)) {
273
+ if(tx.confirmations<=0 && !this.doubleSpendWatchdogSwaps.has(swap)) {
272
274
  this.swapLogger.debug(swap, "processPastSwap(): Adding swap transaction to double spend watchdog list: ", swap.txId);
273
275
  this.doubleSpendWatchdogSwaps.add(swap);
274
276
  }
275
277
  }
276
- if(tx.confirmation_count > 0) {
278
+ if(tx.confirmations>0 && this.doubleSpendWatchdogSwaps.delete(swap)) {
277
279
  this.swapLogger.debug(swap, "processPastSwap(): Removing confirmed swap transaction from double spend watchdog list: ", swap.txId);
278
- this.doubleSpendWatchdogSwaps.delete(swap);
279
280
  }
280
281
 
281
282
  if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
@@ -301,7 +302,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
301
302
  swap.scRawTx = rawTx;
302
303
  if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
303
304
  await swap.setState(FromBtcTrustedSwapState.SENT);
304
- await this.storageManager.saveData(swap.getHash(), null, swap);
305
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
305
306
  }
306
307
  if(unlock!=null) unlock();
307
308
  unlock = null;
@@ -316,7 +317,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
316
317
  swap.txIds = {init: null};
317
318
  swap.scRawTx = null;
318
319
  await swap.setState(FromBtcTrustedSwapState.RECEIVED);
319
- await this.storageManager.saveData(swap.getHash(), null, swap);
320
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
320
321
  break;
321
322
  case "reverted":
322
323
  //Cancel invoice
@@ -325,18 +326,19 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
325
326
  break;
326
327
  case "success":
327
328
  await swap.setState(FromBtcTrustedSwapState.CONFIRMED);
328
- await this.storageManager.saveData(swap.getHash(), null, swap);
329
+ await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
329
330
  break;
330
331
  }
331
332
  }
332
333
 
333
334
  if(swap.state===FromBtcTrustedSwapState.CONFIRMED) {
334
335
  this.processedTxIds.set(swap.getHash(), {
335
- txId: swap.txIds.init,
336
+ txId: swap.txId,
337
+ scTxId: swap.txIds.init,
336
338
  adjustedAmount: swap.adjustedInput,
337
339
  adjustedTotal: swap.adjustedOutput
338
340
  });
339
- if(tx.confirmation_count>0) await this.removeSwapData(swap, FromBtcTrustedSwapState.FINISHED);
341
+ if(tx.confirmations>0) await this.removeSwapData(swap, FromBtcTrustedSwapState.FINISHED);
340
342
  }
341
343
  }
342
344
 
@@ -345,6 +347,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
345
347
  {
346
348
  key: "state",
347
349
  value: [
350
+ FromBtcTrustedSwapState.REFUNDABLE,
348
351
  FromBtcTrustedSwapState.CREATED,
349
352
  FromBtcTrustedSwapState.RECEIVED,
350
353
  FromBtcTrustedSwapState.BTC_CONFIRMED,
@@ -356,17 +359,34 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
356
359
 
357
360
  const startingBlockheight = queriedData.reduce((prev, swap) => Math.min(prev, swap.createdHeight), Infinity);
358
361
  if(startingBlockheight===Infinity) return;
359
- const {transactions} = await getChainTransactions({lnd: this.LND, after: startingBlockheight});
362
+ const transactions = await this.bitcoin.getWalletTransactions(startingBlockheight);
363
+
364
+ const map = new Map<string, {tx: BtcTx, vout: number}[]>();
365
+ transactions.forEach(tx => {
366
+ tx.outs.forEach((out, vout) => {
367
+ const existing = map.get(out.scriptPubKey.hex);
368
+ if(existing==null) {
369
+ map.set(out.scriptPubKey.hex, [{tx, vout}]);
370
+ } else {
371
+ existing.push({tx, vout});
372
+ }
373
+ })
374
+ });
360
375
 
361
376
  for(let swap of queriedData) {
362
- const tx = transactions.find(tx => tx.output_addresses.includes(swap.btcAddress));
363
- await this.processPastSwap(swap, tx);
377
+ const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
378
+ const txs = map.get(outputScript) ?? [];
379
+ try {
380
+ await this.processPastSwap(swap, txs[0]?.tx, txs[0]?.vout);
381
+ } catch (e) {
382
+ this.swapLogger.error(swap, "processPastSwaps(): Error ocurred while processing swap: ", e);
383
+ }
364
384
  }
365
385
  }
366
386
 
367
387
  private isValidBitcoinAddress(address: string) {
368
388
  try {
369
- bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork);
389
+ this.bitcoin.toOutputScript(address);
370
390
  return true;
371
391
  } catch (e) {}
372
392
  return false;
@@ -388,18 +408,14 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
388
408
  metadata.times.requestReceived = Date.now();
389
409
  /**
390
410
  * address: string solana address of the recipient
391
- * refundAddress: string bitcoin address to use in case of refund
411
+ * refundAddress?: string bitcoin address to use in case of refund
392
412
  * amount: string amount (in lamports/smart chain base units) of the invoice
393
413
  * exactOut: boolean whether to create and exact output swap
394
414
  */
395
-
396
415
  const parsedBody: FromBtcTrustedRequestType = await req.paramReader.getParams({
397
416
  address: (val: string) => val!=null &&
398
417
  typeof(val)==="string" &&
399
418
  swapContract.isValidAddress(val) ? val : null,
400
- refundAddress: (val: string) => val!=null &&
401
- typeof(val)==="string" &&
402
- this.isValidBitcoinAddress(val) ? val : null,
403
419
  amount: FieldTypeEnum.BN,
404
420
  exactOut: FieldTypeEnum.BooleanOptional
405
421
  });
@@ -409,6 +425,13 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
409
425
  };
410
426
  metadata.request = parsedBody;
411
427
 
428
+ const refundAddressData = req.paramReader.getExistingParamsOrNull({
429
+ refundAddress: (val: string) => val!=null &&
430
+ typeof(val)==="string" &&
431
+ this.isValidBitcoinAddress(val) ? val : null
432
+ });
433
+ const refundAddress = refundAddressData?.refundAddress;
434
+
412
435
  const requestedAmount = {input: !parsedBody.exactOut, amount: parsedBody.amount};
413
436
  const request = {
414
437
  chainIdentifier,
@@ -443,21 +466,25 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
443
466
  } = await this.checkFromBtcAmount(request, requestedAmount, fees, useToken, abortController.signal, pricePrefetchPromise);
444
467
  metadata.times.priceCalculated = Date.now();
445
468
 
446
- //Check if we have enough funds to honor the request
447
- await this.checkBalance(totalInToken, balancePrefetch, abortController.signal)
469
+ //Make sure we have MORE THAN ENOUGH to honor the swap request
470
+ await this.checkBalance(totalInToken.mul(new BN(4)), balancePrefetch, abortController.signal)
448
471
  metadata.times.balanceChecked = Date.now();
449
472
 
450
- const {address: receiveAddress} = await createChainAddress({
451
- lnd: this.LND,
452
- format: "p2wpkh"
453
- });
473
+ const blockHeight = await this.bitcoin.getBlockheight();
474
+ const feeRate = await this.bitcoin.getFeeRate();
475
+ const recommendedFee = Math.ceil(feeRate*this.config.recommendFeeMultiplier);
476
+ if(recommendedFee===0) throw {
477
+ _httpStatus: 500,
478
+ code: 21100,
479
+ msg: "Cannot estimate bitcoin fee!"
480
+ };
481
+ metadata.times.feeEstimated = Date.now();
482
+
483
+ const receiveAddress = await this.bitcoin.getAddress();
484
+ const outputScript = this.bitcoin.toOutputScript(receiveAddress).toString("hex");
454
485
  abortController.signal.throwIfAborted();
455
486
  metadata.times.addressCreated = Date.now();
456
487
 
457
- const {current_block_height} = await getHeight({lnd: this.LND});
458
- const feeRate = await this.config.feeEstimator.estimateFee();
459
- const recommendedFee = Math.ceil(feeRate*this.config.recommendFeeMultiplier);
460
-
461
488
  const createdSwap = new FromBtcTrustedSwap(
462
489
  chainIdentifier,
463
490
  swapFee,
@@ -466,17 +493,17 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
466
493
  amountBD,
467
494
  parsedBody.address,
468
495
  totalInToken,
469
- current_block_height,
496
+ blockHeight,
470
497
  Date.now()+(this.config.swapAddressExpiry*1000),
471
498
  recommendedFee,
472
- parsedBody.refundAddress
499
+ refundAddress
473
500
  );
474
501
  metadata.times.swapCreated = Date.now();
475
502
  createdSwap.metadata = metadata;
476
503
 
477
504
  await PluginManager.swapCreate(createdSwap);
478
- await this.storageManager.saveData(createdSwap.getHash(), null, createdSwap);
479
- this.subscriptions.set(createdSwap.btcAddress, createdSwap);
505
+ await this.storageManager.saveData(createdSwap.getHash(), createdSwap.getSequence(), createdSwap);
506
+ this.subscriptions.set(outputScript, createdSwap);
480
507
 
481
508
  this.swapLogger.info(createdSwap, "REST: /getAddress: Created swap address: "+createdSwap.btcAddress+" amount: "+amountBD.toString(10));
482
509
 
@@ -485,6 +512,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
485
512
  code: 10000,
486
513
  data: {
487
514
  paymentHash: createdSwap.getHash(),
515
+ sequence: createdSwap.getSequence().toString(10),
488
516
  btcAddress: receiveAddress,
489
517
  amountSats: amountBD.toString(10),
490
518
  swapFeeSats: swapFee.toString(10),
@@ -496,27 +524,37 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
496
524
  }
497
525
  });
498
526
  });
499
-
500
527
  restServer.use(this.path+"/getAddress", serverParamDecoder(10*1000));
501
528
  restServer.post(this.path+"/getAddress", getAddress);
502
529
 
503
530
  const getInvoiceStatus = expressHandlerWrapper(async (req, res) => {
504
531
  /**
505
532
  * paymentHash: string payment hash of the invoice
533
+ * sequence: BN secret sequence for the swap,
506
534
  */
507
535
  const parsedBody = verifySchema(req.query, {
508
536
  paymentHash: (val: string) => val!=null &&
509
537
  typeof(val)==="string" &&
510
538
  val.length===64 &&
511
539
  HEX_REGEX.test(val) ? val: null,
540
+ sequence: FieldTypeEnum.BN,
512
541
  });
542
+ if(parsedBody==null) throw {
543
+ code: 20100,
544
+ msg: "Invalid request"
545
+ };
513
546
 
514
547
  const processedTxData = this.processedTxIds.get(parsedBody.paymentHash);
515
548
  if(processedTxData!=null) throw {
516
549
  _httpStatus: 200,
517
550
  code: 10000,
518
551
  msg: "Success, tx confirmed",
519
- data: processedTxData
552
+ data: {
553
+ adjustedAmount: processedTxData.adjustedAmount.toString(10),
554
+ adjustedTotal: processedTxData.adjustedTotal.toString(10),
555
+ txId: processedTxData.txId,
556
+ scTxId: processedTxData.scTxId
557
+ }
520
558
  };
521
559
 
522
560
  const refundTxId = this.refundedSwaps.get(parsedBody.paymentHash);
@@ -539,7 +577,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
539
577
  }
540
578
  };
541
579
 
542
- const invoiceData: FromBtcTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, null);
580
+ const invoiceData: FromBtcTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
543
581
  if (invoiceData==null) throw {
544
582
  _httpStatus: 200,
545
583
  code: 10001,
@@ -558,7 +596,8 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
558
596
  msg: "Bitcoin received, payment processing",
559
597
  data: {
560
598
  adjustedAmount: invoiceData.adjustedInput.toString(10),
561
- adjustedTotal: invoiceData.adjustedOutput.toString(10)
599
+ adjustedTotal: invoiceData.adjustedOutput.toString(10),
600
+ txId: invoiceData.txId
562
601
  }
563
602
  };
564
603
 
@@ -568,7 +607,8 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
568
607
  msg: "Bitcoin accepted, payment processing",
569
608
  data: {
570
609
  adjustedAmount: invoiceData.adjustedInput.toString(10),
571
- adjustedTotal: invoiceData.adjustedOutput.toString(10)
610
+ adjustedTotal: invoiceData.adjustedOutput.toString(10),
611
+ txId: invoiceData.txId
572
612
  }
573
613
  };
574
614
 
@@ -579,7 +619,8 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
579
619
  data: {
580
620
  adjustedAmount: invoiceData.adjustedInput.toString(10),
581
621
  adjustedTotal: invoiceData.adjustedOutput.toString(10),
582
- txId: invoiceData.txIds.init
622
+ txId: invoiceData.txId,
623
+ scTxId: invoiceData.txIds.init
583
624
  }
584
625
  };
585
626
 
@@ -590,22 +631,83 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
590
631
  data: {
591
632
  adjustedAmount: invoiceData.adjustedInput.toString(10),
592
633
  adjustedTotal: invoiceData.adjustedOutput.toString(10),
593
- txId: invoiceData.txIds.init
634
+ txId: invoiceData.txId,
635
+ scTxId: invoiceData.txIds.init
594
636
  }
595
637
  };
596
- });
597
638
 
639
+ if (invoiceData.state === FromBtcTrustedSwapState.REFUNDABLE) throw {
640
+ _httpStatus: 200,
641
+ code: 10016,
642
+ msg: "Refundable",
643
+ data: {
644
+ adjustedAmount: invoiceData.adjustedInput.toString(10)
645
+ }
646
+ };
647
+ });
598
648
  restServer.get(this.path+"/getAddressStatus", getInvoiceStatus);
599
649
 
650
+ const setRefundAddress = expressHandlerWrapper(async (req, res) => {
651
+ /**
652
+ * paymentHash: string payment hash of the invoice
653
+ * sequence: BN secret sequence for the swap,
654
+ * refundAddress: string valid bitcoin address to be used for refunds
655
+ */
656
+ const parsedBody = verifySchema({...req.body, ...req.query}, {
657
+ paymentHash: (val: string) => val!=null &&
658
+ typeof(val)==="string" &&
659
+ val.length===64 &&
660
+ HEX_REGEX.test(val) ? val: null,
661
+ sequence: FieldTypeEnum.BN,
662
+ refundAddress: (val: string) => val!=null &&
663
+ typeof(val)==="string" &&
664
+ this.isValidBitcoinAddress(val) ? val : null
665
+ });
666
+ if(parsedBody==null) throw {
667
+ code: 20100,
668
+ msg: "Invalid request"
669
+ };
670
+
671
+ const invoiceData: FromBtcTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, null);
672
+ if (invoiceData==null || !invoiceData.getSequence().eq(parsedBody.sequence)) throw {
673
+ code: 10001,
674
+ msg: "Swap not found"
675
+ };
676
+
677
+ if(invoiceData.refundAddress!=null) throw {
678
+ code: 10080,
679
+ msg: "Refund address already set!",
680
+ data: {
681
+ refundAddress: invoiceData.refundAddress
682
+ }
683
+ };
684
+
685
+ invoiceData.refundAddress = parsedBody.refundAddress;
686
+
687
+ if (invoiceData.state === FromBtcTrustedSwapState.REFUNDABLE) {
688
+ this.refundSwap(invoiceData).catch(e => {
689
+ this.swapLogger.error(invoiceData, "/setRefundAddress: Failed to refund!");
690
+ });
691
+ }
692
+
693
+ throw {
694
+ _httpStatus: 200,
695
+ code: 10000,
696
+ msg: "Refund address set"
697
+ };
698
+ });
699
+ restServer.get(this.path+"/setRefundAddress", setRefundAddress);
700
+ restServer.post(this.path+"/setRefundAddress", setRefundAddress);
701
+
600
702
  this.logger.info("started at path: ", this.path);
601
703
  }
602
704
 
603
705
  private async checkDoubleSpends(): Promise<void> {
604
706
  for(let swap of this.doubleSpendWatchdogSwaps.keys()) {
605
- const tx = await this.bitcoinRpc.getTransaction(swap.txId);
707
+ const tx = await this.bitcoin.getWalletTransaction(swap.txId);
606
708
  if(tx==null) {
607
709
  this.swapLogger.debug(swap, "checkDoubleSpends(): Swap was double spent, burning... - original txId: "+swap.txId);
608
- this.processPastSwap(swap, null);
710
+ this.processPastSwap(swap, null, null);
609
711
  }
610
712
  }
611
713
  }
@@ -620,12 +722,11 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
620
722
  }
621
723
 
622
724
  private listenToTxns() {
623
- const res = subscribeToTransactions({lnd: this.LND});
624
- res.on("chain_transaction", (tx: SubscribeToTransactionsChainTransactionEvent) => {
625
- for(let address of tx.output_addresses) {
626
- const savedSwap = this.subscriptions.get(address);
725
+ this.bitcoin.subscribeToWalletTransactions((btcTx: BtcTx) => {
726
+ for(let out of btcTx.outs) {
727
+ const savedSwap = this.subscriptions.get(out.scriptPubKey.hex);
627
728
  if(savedSwap==null) continue;
628
- this.processPastSwap(savedSwap, tx);
729
+ this.processPastSwap(savedSwap, btcTx, out.n);
629
730
  return;
630
731
  }
631
732
  });