@atomiqlabs/lp-lib 14.0.0-dev.12 → 14.0.0-dev.14

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.
@@ -55,30 +55,57 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
55
55
  }
56
56
  return null;
57
57
  }
58
- if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED) {
59
- const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
60
- if (isAuthorizationExpired) {
61
- const isCommited = await swapContract.isCommited(swap.data);
62
- if (!isCommited) {
63
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: " + swap.pr);
64
- await swap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.CANCELED);
65
- return "CANCEL";
58
+ if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED || swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED) {
59
+ const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
60
+ const state = swap.state;
61
+ if (onchainStatus.type === base_1.SwapCommitStateType.PAID) {
62
+ //Extract the swap secret
63
+ if (state !== FromBtcLnSwapAbs_1.FromBtcLnSwapState.CLAIMED && state !== FromBtcLnSwapAbs_1.FromBtcLnSwapState.SETTLED) {
64
+ const secretHex = await onchainStatus.getClaimResult();
65
+ const secret = Buffer.from(secretHex, "hex");
66
+ const paymentHash = (0, crypto_1.createHash)("sha256").update(secret).digest();
67
+ const paymentHashHex = paymentHash.toString("hex");
68
+ if (swap.lnPaymentHash !== paymentHashHex) {
69
+ //TODO: Possibly fatal failure
70
+ this.swapLogger.error(swap, "processPastSwap(state=RECEIVED|COMMITED): onchainStatus=PAID, Invalid swap secret specified: " + secretHex + " for paymentHash: " + paymentHashHex);
71
+ return null;
72
+ }
73
+ swap.secret = secretHex;
74
+ await swap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.CLAIMED);
75
+ await this.saveSwapData(swap);
76
+ this.swapLogger.warn(swap, "processPastSwap(state=RECEIVED|COMMITED): swap settled (detected from processPastSwap), invoice: " + swap.pr);
77
+ return "SETTLE";
66
78
  }
67
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap committed (detected from processPastSwap), invoice: " + swap.pr);
68
- await swap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED);
69
- await this.saveSwapData(swap);
79
+ return null;
70
80
  }
71
- }
72
- if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED || swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED) {
73
- if (!await swapContract.isExpired(signer.getAddress(), swap.data))
81
+ if (onchainStatus.type === base_1.SwapCommitStateType.COMMITED) {
82
+ if (state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED) {
83
+ await swap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED);
84
+ await this.saveSwapData(swap);
85
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap committed (detected from processPastSwap), invoice: " + swap.pr);
86
+ }
74
87
  return null;
75
- const isCommited = await swapContract.isCommited(swap.data);
76
- if (isCommited) {
77
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
88
+ }
89
+ if (onchainStatus.type === base_1.SwapCommitStateType.NOT_COMMITED || onchainStatus.type === base_1.SwapCommitStateType.EXPIRED) {
90
+ if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED) {
91
+ const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
92
+ if (isAuthorizationExpired) {
93
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: " + swap.pr);
94
+ await swap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.CANCELED);
95
+ return "CANCEL";
96
+ }
97
+ }
98
+ else {
99
+ if (await swapContract.isExpired(signer.getAddress(), swap.data)) {
100
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
101
+ return "REFUND";
102
+ }
103
+ }
104
+ }
105
+ if (onchainStatus.type === base_1.SwapCommitStateType.REFUNDABLE) {
106
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
78
107
  return "REFUND";
79
108
  }
80
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap timed out, cancelling the LN invoice, invoice: " + swap.pr);
81
- return "CANCEL";
82
109
  }
83
110
  if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CLAIMED)
84
111
  return "SETTLE";
@@ -65,31 +65,57 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
65
65
  }
66
66
  return null;
67
67
  }
68
- if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT) {
69
- const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
70
- if (isAuthorizationExpired) {
71
- const isCommited = await swapContract.isCommited(swap.data);
72
- if (!isCommited) {
73
- this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT): swap not committed before authorization expiry, cancelling the LN invoice, invoice: " + swap.pr);
74
- await this.cancelSwapAndInvoice(swap);
75
- return null;
68
+ if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT || swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED) {
69
+ const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
70
+ const state = swap.state;
71
+ if (onchainStatus.type === base_1.SwapCommitStateType.PAID) {
72
+ //Extract the swap secret
73
+ if (state !== FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.CLAIMED && state !== FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.SETTLED) {
74
+ const secretHex = await onchainStatus.getClaimResult();
75
+ const secret = Buffer.from(secretHex, "hex");
76
+ const paymentHash = (0, crypto_1.createHash)("sha256").update(secret).digest();
77
+ const paymentHashHex = paymentHash.toString("hex");
78
+ if (swap.lnPaymentHash !== paymentHashHex) {
79
+ //TODO: Possibly fatal failure
80
+ this.swapLogger.error(swap, "processPastSwap(state=TXS_SENT|COMMITED): onchainStatus=PAID, Invalid swap secret specified: " + secretHex + " for paymentHash: " + paymentHashHex);
81
+ return null;
82
+ }
83
+ swap.secret = secretHex;
84
+ await swap.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.CLAIMED);
85
+ await this.saveSwapData(swap);
86
+ this.swapLogger.warn(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap settled (detected from processPastSwap), invoice: " + swap.pr);
87
+ return "SETTLE";
76
88
  }
77
- this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT): swap committed (detected from processPastSwap), invoice: " + swap.pr);
78
- await swap.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED);
79
- await this.saveSwapData(swap);
89
+ return null;
80
90
  }
81
- }
82
- if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT || swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED) {
83
- if (!await swapContract.isExpired(signer.getAddress(), swap.data))
91
+ if (onchainStatus.type === base_1.SwapCommitStateType.COMMITED) {
92
+ if (state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT) {
93
+ await swap.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED);
94
+ await this.saveSwapData(swap);
95
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap committed (detected from processPastSwap), invoice: " + swap.pr);
96
+ }
84
97
  return null;
85
- const isCommited = await swapContract.isCommited(swap.data);
86
- if (isCommited) {
87
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
98
+ }
99
+ if (onchainStatus.type === base_1.SwapCommitStateType.NOT_COMMITED || onchainStatus.type === base_1.SwapCommitStateType.EXPIRED) {
100
+ if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT) {
101
+ const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
102
+ if (isAuthorizationExpired) {
103
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: " + swap.pr);
104
+ await this.cancelSwapAndInvoice(swap);
105
+ return null;
106
+ }
107
+ }
108
+ else {
109
+ if (await swapContract.isExpired(signer.getAddress(), swap.data)) {
110
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
111
+ return "REFUND";
112
+ }
113
+ }
114
+ }
115
+ if (onchainStatus.type === base_1.SwapCommitStateType.REFUNDABLE) {
116
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap timed out, refunding to self, invoice: " + swap.pr);
88
117
  return "REFUND";
89
118
  }
90
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, cancelling the LN invoice, invoice: " + swap.pr);
91
- await this.cancelSwapAndInvoice(swap);
92
- return null;
93
119
  }
94
120
  if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.CLAIMED)
95
121
  return "SETTLE";
@@ -13,9 +13,8 @@ export declare class SpvVault<D extends SpvWithdrawalTransactionData = SpvWithdr
13
13
  data: T;
14
14
  state: SpvVaultState;
15
15
  balances: SpvVaultTokenBalance[];
16
- scOpenTx: {
17
- txId: string;
18
- rawTx: string;
16
+ scOpenTxs: {
17
+ [txId: string]: string;
19
18
  };
20
19
  constructor(chainId: string, vault: T, btcAddress: string);
21
20
  constructor(obj: any);
@@ -27,7 +27,7 @@ class SpvVault extends base_1.Lockable {
27
27
  this.initialUtxo = chainIdOrObj.initialUtxo;
28
28
  this.btcAddress = chainIdOrObj.btcAddress;
29
29
  this.pendingWithdrawals = chainIdOrObj.pendingWithdrawals.map((base_1.SpvWithdrawalTransactionData.deserialize));
30
- this.scOpenTx = chainIdOrObj.scOpenTx;
30
+ this.scOpenTxs = chainIdOrObj.scOpenTxs;
31
31
  }
32
32
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
33
33
  }
@@ -83,7 +83,7 @@ class SpvVault extends base_1.Lockable {
83
83
  initialUtxo: this.initialUtxo,
84
84
  btcAddress: this.btcAddress,
85
85
  pendingWithdrawals: this.pendingWithdrawals.map(val => val.serialize()),
86
- scOpenTx: this.scOpenTx
86
+ scOpenTxs: this.scOpenTxs
87
87
  };
88
88
  }
89
89
  getIdentifier() {
@@ -23,7 +23,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
23
23
  this.vaultSigner = spvVaultSigner;
24
24
  this.config = config;
25
25
  this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
26
- this.Vaults = new SpvVaults_1.SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.getChain.bind(this), config);
26
+ this.Vaults = new SpvVaults_1.SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
27
27
  }
28
28
  async processClaimEvent(swap, event) {
29
29
  if (swap == null)
@@ -372,6 +372,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
372
372
  data.executionFeeRate !== swap.executionFeeShare ||
373
373
  data.rawAmounts[0] !== swap.rawAmountToken ||
374
374
  data.rawAmounts[1] !== swap.rawAmountGasToken ||
375
+ data.getExecutionData() != null ||
375
376
  data.getSpentVaultUtxo() !== swap.vaultUtxo ||
376
377
  data.btcTx.outs[0].value !== SpvVaults_1.VAULT_DUST_AMOUNT ||
377
378
  !Buffer.from(data.btcTx.outs[0].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.vaultAddress)) ||
@@ -3,7 +3,7 @@ import { BitcoinRpc, IStorageManager, SpvVaultClaimEvent, SpvVaultCloseEvent, Sp
3
3
  import { SpvVaultSwap } from "./SpvVaultSwap";
4
4
  import { IBitcoinWallet } from "../../wallets/IBitcoinWallet";
5
5
  import { ISpvVaultSigner } from "../../wallets/ISpvVaultSigner";
6
- import { ChainData } from "../SwapHandler";
6
+ import { MultichainData } from "../SwapHandler";
7
7
  export declare const VAULT_DUST_AMOUNT = 600;
8
8
  export declare class SpvVaults {
9
9
  readonly vaultStorage: IStorageManager<SpvVault>;
@@ -14,9 +14,9 @@ export declare class SpvVaults {
14
14
  vaultsCheckInterval: number;
15
15
  maxUnclaimedWithdrawals?: number;
16
16
  };
17
- readonly getChain: (chainId: string) => ChainData;
17
+ readonly chains: MultichainData;
18
18
  readonly logger: import("../../utils/Utils").LoggerType;
19
- constructor(vaultStorage: IStorageManager<SpvVault>, bitcoin: IBitcoinWallet, vaultSigner: ISpvVaultSigner, bitcoinRpc: BitcoinRpc<any>, getChain: (chainId: string) => ChainData, config: {
19
+ constructor(vaultStorage: IStorageManager<SpvVault>, bitcoin: IBitcoinWallet, vaultSigner: ISpvVaultSigner, bitcoinRpc: BitcoinRpc<any>, chains: MultichainData, config: {
20
20
  vaultsCheckInterval: number;
21
21
  maxUnclaimedWithdrawals?: number;
22
22
  });
@@ -10,14 +10,27 @@ exports.VAULT_DUST_AMOUNT = 600;
10
10
  const VAULT_INIT_CONFIRMATIONS = 2;
11
11
  const BTC_FINALIZATION_CONFIRMATIONS = 6;
12
12
  class SpvVaults {
13
- constructor(vaultStorage, bitcoin, vaultSigner, bitcoinRpc, getChain, config) {
13
+ constructor(vaultStorage, bitcoin, vaultSigner, bitcoinRpc, chains, config) {
14
14
  this.logger = (0, Utils_1.getLogger)("SpvVaults: ");
15
15
  this.vaultStorage = vaultStorage;
16
16
  this.bitcoin = bitcoin;
17
17
  this.vaultSigner = vaultSigner;
18
18
  this.bitcoinRpc = bitcoinRpc;
19
- this.getChain = getChain;
19
+ this.chains = chains;
20
20
  this.config = config;
21
+ for (let chainId in chains.chains) {
22
+ const { chainInterface } = chains.chains[chainId];
23
+ chainInterface.onBeforeTxReplace(async (oldTx, oldTxId, newTx, newTxId) => {
24
+ for (let key in this.vaultStorage.data) {
25
+ const vaultData = this.vaultStorage.data[key];
26
+ if (vaultData.scOpenTxs != null && vaultData.scOpenTxs[oldTxId] != null) {
27
+ vaultData.scOpenTxs[newTxId] = newTx;
28
+ await this.saveVault(vaultData);
29
+ break;
30
+ }
31
+ }
32
+ });
33
+ }
21
34
  }
22
35
  async processDepositEvent(vault, event) {
23
36
  vault.update(event);
@@ -46,7 +59,7 @@ class SpvVaults {
46
59
  await this.saveVault(vault);
47
60
  }
48
61
  async createVaults(chainId, count, token, confirmations = 2, feeRate) {
49
- const { signer, chainInterface, tokenMultipliers, spvVaultContract } = this.getChain(chainId);
62
+ const { signer, chainInterface, tokenMultipliers, spvVaultContract } = this.chains.chains[chainId];
50
63
  const signerAddress = signer.getAddress();
51
64
  //Check vaultId of the latest saved vault
52
65
  let latestVaultId = -1n;
@@ -101,14 +114,14 @@ class SpvVaults {
101
114
  return Object.keys(this.vaultStorage.data)
102
115
  .map(key => this.vaultStorage.data[key])
103
116
  .filter(val => chainId == null ? true : val.chainId === chainId)
104
- .filter(val => val.data.getOwner() === this.getChain(val.chainId)?.signer?.getAddress())
117
+ .filter(val => val.data.getOwner() === this.chains.chains[val.chainId]?.signer?.getAddress())
105
118
  .filter(val => token == null ? true : val.data.getTokenData()[0].token === token);
106
119
  }
107
120
  async fundVault(vault, tokenAmounts) {
108
121
  if (vault.state !== SpvVault_1.SpvVaultState.OPENED)
109
122
  throw new Error("Vault not opened!");
110
123
  this.logger.info("fundVault(): Depositing tokens to the vault " + vault.data.getVaultId().toString(10) + ", amounts: " + tokenAmounts.map(val => val.toString(10)).join(", "));
111
- const { signer, spvVaultContract } = this.getChain(vault.chainId);
124
+ const { signer, spvVaultContract } = this.chains.chains[vault.chainId];
112
125
  const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, { waitForConfirmation: true });
113
126
  this.logger.info("fundVault(): Tokens deposited to vault " + vault.data.getVaultId().toString(10) + ", amounts: " + tokenAmounts.map(val => val.toString(10)).join(", ") + ", txId: " + txId);
114
127
  return txId;
@@ -122,7 +135,7 @@ class SpvVaults {
122
135
  });
123
136
  if (!vault.isReady())
124
137
  throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
125
- const { signer, spvVaultContract } = this.getChain(vault.chainId);
138
+ const { signer, spvVaultContract } = this.chains.chains[vault.chainId];
126
139
  const latestUtxo = vault.getLatestUtxo();
127
140
  const [txId, voutStr] = latestUtxo.split(":");
128
141
  const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
@@ -186,7 +199,7 @@ class SpvVaults {
186
199
  const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
187
200
  const claimWithdrawals = [];
188
201
  for (let vault of vaults) {
189
- const { signer, spvVaultContract, chainInterface } = this.getChain(vault.chainId);
202
+ const { signer, spvVaultContract, chainInterface } = this.chains.chains[vault.chainId];
190
203
  if (vault.data.getOwner() !== signer.getAddress())
191
204
  continue;
192
205
  if (vault.state === SpvVault_1.SpvVaultState.BTC_INITIATED) {
@@ -204,16 +217,25 @@ class SpvVaults {
204
217
  }
205
218
  if (vault.state === SpvVault_1.SpvVaultState.BTC_CONFIRMED) {
206
219
  //Check if open txs were sent already
207
- if (vault.scOpenTx != null) {
220
+ if (vault.scOpenTxs != null) {
208
221
  //Check if confirmed
209
- const status = await chainInterface.getTxStatus(vault.scOpenTx.rawTx);
210
- if (status === "pending")
211
- return;
212
- if (status === "success") {
213
- vault.state = SpvVault_1.SpvVaultState.OPENED;
214
- await this.saveVault(vault);
215
- return;
222
+ let _continue = false;
223
+ for (let txId in vault.scOpenTxs) {
224
+ const tx = vault.scOpenTxs[txId];
225
+ const status = await chainInterface.getTxStatus(tx);
226
+ if (status === "pending") {
227
+ _continue = true;
228
+ break;
229
+ }
230
+ if (status === "success") {
231
+ vault.state = SpvVault_1.SpvVaultState.OPENED;
232
+ await this.saveVault(vault);
233
+ _continue = true;
234
+ break;
235
+ }
216
236
  }
237
+ if (_continue)
238
+ continue;
217
239
  }
218
240
  const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
219
241
  let numTx = 0;
@@ -221,7 +243,7 @@ class SpvVaults {
221
243
  numTx++;
222
244
  if (numTx === txs.length) {
223
245
  //Final tx
224
- vault.scOpenTx = { txId, rawTx };
246
+ vault.scOpenTxs = { [txId]: rawTx };
225
247
  await this.saveVault(vault);
226
248
  }
227
249
  });
@@ -289,7 +311,7 @@ class SpvVaults {
289
311
  }
290
312
  }
291
313
  async claimWithdrawals(vault, withdrawal) {
292
- const { signer, spvVaultContract } = this.getChain(vault.chainId);
314
+ const { signer, spvVaultContract } = this.chains.chains[vault.chainId];
293
315
  try {
294
316
  const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
295
317
  return { tx };
@@ -317,7 +339,7 @@ class SpvVaults {
317
339
  * @protected
318
340
  */
319
341
  async findVaultForSwap(chainIdentifier, totalSats, token, amount, gasToken, gasTokenAmount) {
320
- const { signer } = this.getChain(chainIdentifier);
342
+ const { signer } = this.chains.chains[chainIdentifier];
321
343
  const pluginResponse = await PluginManager_1.PluginManager.onVaultSelection(chainIdentifier, totalSats, { token, amount }, { token: gasToken, amount: gasTokenAmount });
322
344
  if (pluginResponse != null) {
323
345
  AmountAssertions_1.AmountAssertions.handlePluginErrorResponses(pluginResponse);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "14.0.0-dev.12",
3
+ "version": "14.0.0-dev.14",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "author": "adambor",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "@atomiqlabs/base": "^10.0.0-dev.7",
25
+ "@atomiqlabs/base": "^10.0.0-dev.9",
26
26
  "@atomiqlabs/server-base": "2.0.0",
27
27
  "@scure/btc-signer": "1.6.0",
28
28
  "express": "4.21.1",
@@ -9,6 +9,7 @@ import {
9
9
  ClaimEvent,
10
10
  InitializeEvent,
11
11
  RefundEvent,
12
+ SwapCommitStateType,
12
13
  SwapData
13
14
  } from "@atomiqlabs/base";
14
15
  import {expressHandlerWrapper, getAbortController, HEX_REGEX, isDefinedRuntimeError} from "../../../utils/Utils";
@@ -25,7 +26,7 @@ import {
25
26
  LightningNetworkChannel,
26
27
  LightningNetworkInvoice
27
28
  } from "../../../wallets/ILightningWallet";
28
- import { LightningAssertions } from "../../assertions/LightningAssertions";
29
+ import {LightningAssertions} from "../../assertions/LightningAssertions";
29
30
 
30
31
  export type FromBtcLnConfig = FromBtcBaseConfig & {
31
32
  invoiceTimeoutSeconds?: number,
@@ -103,34 +104,61 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
103
104
  return null;
104
105
  }
105
106
 
106
- if(swap.state===FromBtcLnSwapState.RECEIVED) {
107
- const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
108
- if(isAuthorizationExpired) {
109
- const isCommited = await swapContract.isCommited(swap.data);
110
-
111
- if(!isCommited) {
112
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: "+swap.pr);
113
- await swap.setState(FromBtcLnSwapState.CANCELED);
114
- return "CANCEL";
107
+ if(swap.state===FromBtcLnSwapState.RECEIVED || swap.state===FromBtcLnSwapState.COMMITED) {
108
+ const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
109
+ const state: FromBtcLnSwapState = swap.state as FromBtcLnSwapState;
110
+ if(onchainStatus.type===SwapCommitStateType.PAID) {
111
+ //Extract the swap secret
112
+ if(state!==FromBtcLnSwapState.CLAIMED && state!==FromBtcLnSwapState.SETTLED) {
113
+ const secretHex = await onchainStatus.getClaimResult();
114
+ const secret: Buffer = Buffer.from(secretHex, "hex");
115
+ const paymentHash: Buffer = createHash("sha256").update(secret).digest();
116
+ const paymentHashHex = paymentHash.toString("hex");
117
+
118
+ if (swap.lnPaymentHash!==paymentHashHex) {
119
+ //TODO: Possibly fatal failure
120
+ this.swapLogger.error(swap, "processPastSwap(state=RECEIVED|COMMITED): onchainStatus=PAID, Invalid swap secret specified: "+secretHex+" for paymentHash: "+paymentHashHex);
121
+ return null;
122
+ }
123
+
124
+ swap.secret = secretHex;
125
+ await swap.setState(FromBtcLnSwapState.CLAIMED);
126
+ await this.saveSwapData(swap);
127
+
128
+ this.swapLogger.warn(swap, "processPastSwap(state=RECEIVED|COMMITED): swap settled (detected from processPastSwap), invoice: "+swap.pr);
129
+
130
+ return "SETTLE";
115
131
  }
116
-
117
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap committed (detected from processPastSwap), invoice: "+swap.pr);
118
- await swap.setState(FromBtcLnSwapState.COMMITED);
119
- await this.saveSwapData(swap);
132
+ return null;
120
133
  }
121
- }
122
-
123
- if(swap.state===FromBtcLnSwapState.RECEIVED || swap.state===FromBtcLnSwapState.COMMITED) {
124
- if(!await swapContract.isExpired(signer.getAddress(), swap.data)) return null;
134
+ if(onchainStatus.type===SwapCommitStateType.COMMITED) {
135
+ if(state===FromBtcLnSwapState.RECEIVED) {
136
+ await swap.setState(FromBtcLnSwapState.COMMITED);
137
+ await this.saveSwapData(swap);
125
138
 
126
- const isCommited = await swapContract.isCommited(swap.data);
127
- if(isCommited) {
128
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
139
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap committed (detected from processPastSwap), invoice: "+swap.pr);
140
+ }
141
+ return null;
142
+ }
143
+ if(onchainStatus.type===SwapCommitStateType.NOT_COMMITED || onchainStatus.type===SwapCommitStateType.EXPIRED) {
144
+ if(swap.state===FromBtcLnSwapState.RECEIVED) {
145
+ const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
146
+ if(isAuthorizationExpired) {
147
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: "+swap.pr);
148
+ await swap.setState(FromBtcLnSwapState.CANCELED);
149
+ return "CANCEL";
150
+ }
151
+ } else {
152
+ if(await swapContract.isExpired(signer.getAddress(), swap.data)) {
153
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
154
+ return "REFUND";
155
+ }
156
+ }
157
+ }
158
+ if(onchainStatus.type===SwapCommitStateType.REFUNDABLE) {
159
+ this.swapLogger.info(swap, "processPastSwap(state=RECEIVED|COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
129
160
  return "REFUND";
130
161
  }
131
-
132
- this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): swap timed out, cancelling the LN invoice, invoice: "+swap.pr);
133
- return "CANCEL";
134
162
  }
135
163
 
136
164
  if(swap.state===FromBtcLnSwapState.CLAIMED) return "SETTLE";
@@ -3,7 +3,7 @@ import {createHash} from "crypto";
3
3
  import {FromBtcLnAutoSwap, FromBtcLnAutoSwapState} from "./FromBtcLnAutoSwap";
4
4
  import {MultichainData, SwapHandlerType} from "../../SwapHandler";
5
5
  import {ISwapPrice} from "../../../prices/ISwapPrice";
6
- import {ChainSwapType, ClaimEvent, InitializeEvent, RefundEvent, SwapData} from "@atomiqlabs/base";
6
+ import {ChainSwapType, ClaimEvent, InitializeEvent, RefundEvent, SwapCommitStateType, SwapData} from "@atomiqlabs/base";
7
7
  import {expressHandlerWrapper, getAbortController, HEX_REGEX} from "../../../utils/Utils";
8
8
  import {PluginManager} from "../../../plugins/PluginManager";
9
9
  import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
@@ -19,6 +19,7 @@ import {
19
19
  LightningNetworkInvoice
20
20
  } from "../../../wallets/ILightningWallet";
21
21
  import {LightningAssertions} from "../../assertions/LightningAssertions";
22
+ import {FromBtcLnSwapState} from "../frombtcln_abstract/FromBtcLnSwapAbs";
22
23
 
23
24
  export type FromBtcLnAutoConfig = FromBtcBaseConfig & {
24
25
  invoiceTimeoutSeconds?: number,
@@ -110,35 +111,61 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
110
111
  return null;
111
112
  }
112
113
 
113
- if(swap.state===FromBtcLnAutoSwapState.TXS_SENT) {
114
- const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
115
- if(isAuthorizationExpired) {
116
- const isCommited = await swapContract.isCommited(swap.data);
114
+ if(swap.state===FromBtcLnAutoSwapState.TXS_SENT || swap.state===FromBtcLnAutoSwapState.COMMITED) {
115
+ const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
116
+ const state: FromBtcLnAutoSwapState = swap.state as FromBtcLnAutoSwapState;
117
+ if(onchainStatus.type===SwapCommitStateType.PAID) {
118
+ //Extract the swap secret
119
+ if(state!==FromBtcLnAutoSwapState.CLAIMED && state!==FromBtcLnAutoSwapState.SETTLED) {
120
+ const secretHex = await onchainStatus.getClaimResult();
121
+ const secret: Buffer = Buffer.from(secretHex, "hex");
122
+ const paymentHash: Buffer = createHash("sha256").update(secret).digest();
123
+ const paymentHashHex = paymentHash.toString("hex");
124
+
125
+ if (swap.lnPaymentHash!==paymentHashHex) {
126
+ //TODO: Possibly fatal failure
127
+ this.swapLogger.error(swap, "processPastSwap(state=TXS_SENT|COMMITED): onchainStatus=PAID, Invalid swap secret specified: "+secretHex+" for paymentHash: "+paymentHashHex);
128
+ return null;
129
+ }
117
130
 
118
- if(!isCommited) {
119
- this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT): swap not committed before authorization expiry, cancelling the LN invoice, invoice: "+swap.pr);
120
- await this.cancelSwapAndInvoice(swap);
121
- return null;
122
- }
131
+ swap.secret = secretHex;
132
+ await swap.setState(FromBtcLnAutoSwapState.CLAIMED);
133
+ await this.saveSwapData(swap);
123
134
 
124
- this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT): swap committed (detected from processPastSwap), invoice: "+swap.pr);
125
- await swap.setState(FromBtcLnAutoSwapState.COMMITED);
126
- await this.saveSwapData(swap);
127
- }
128
- }
135
+ this.swapLogger.warn(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap settled (detected from processPastSwap), invoice: "+swap.pr);
129
136
 
130
- if(swap.state===FromBtcLnAutoSwapState.TXS_SENT || swap.state===FromBtcLnAutoSwapState.COMMITED) {
131
- if(!await swapContract.isExpired(signer.getAddress(), swap.data)) return null;
137
+ return "SETTLE";
138
+ }
139
+ return null;
140
+ }
141
+ if(onchainStatus.type===SwapCommitStateType.COMMITED) {
142
+ if(state===FromBtcLnAutoSwapState.TXS_SENT) {
143
+ await swap.setState(FromBtcLnAutoSwapState.COMMITED);
144
+ await this.saveSwapData(swap);
132
145
 
133
- const isCommited = await swapContract.isCommited(swap.data);
134
- if(isCommited) {
135
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
146
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap committed (detected from processPastSwap), invoice: "+swap.pr);
147
+ }
148
+ return null;
149
+ }
150
+ if(onchainStatus.type===SwapCommitStateType.NOT_COMMITED || onchainStatus.type===SwapCommitStateType.EXPIRED) {
151
+ if(swap.state===FromBtcLnAutoSwapState.TXS_SENT) {
152
+ const isAuthorizationExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
153
+ if(isAuthorizationExpired) {
154
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap not committed before authorization expiry, cancelling the LN invoice, invoice: "+swap.pr);
155
+ await this.cancelSwapAndInvoice(swap);
156
+ return null;
157
+ }
158
+ } else {
159
+ if(await swapContract.isExpired(signer.getAddress(), swap.data)) {
160
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
161
+ return "REFUND";
162
+ }
163
+ }
164
+ }
165
+ if(onchainStatus.type===SwapCommitStateType.REFUNDABLE) {
166
+ this.swapLogger.info(swap, "processPastSwap(state=TXS_SENT|COMMITED): swap timed out, refunding to self, invoice: "+swap.pr);
136
167
  return "REFUND";
137
168
  }
138
-
139
- this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap timed out, cancelling the LN invoice, invoice: "+swap.pr);
140
- await this.cancelSwapAndInvoice(swap);
141
- return null;
142
169
  }
143
170
 
144
171
  if(swap.state===FromBtcLnAutoSwapState.CLAIMED) return "SETTLE";
@@ -34,7 +34,7 @@ export class SpvVault<
34
34
 
35
35
  balances: SpvVaultTokenBalance[];
36
36
 
37
- scOpenTx: {txId: string, rawTx: string};
37
+ scOpenTxs: {[txId: string]: string};
38
38
 
39
39
  constructor(chainId: string, vault: T, btcAddress: string);
40
40
  constructor(obj: any);
@@ -54,7 +54,7 @@ export class SpvVault<
54
54
  this.initialUtxo = chainIdOrObj.initialUtxo;
55
55
  this.btcAddress = chainIdOrObj.btcAddress;
56
56
  this.pendingWithdrawals = chainIdOrObj.pendingWithdrawals.map(SpvWithdrawalTransactionData.deserialize<D>);
57
- this.scOpenTx = chainIdOrObj.scOpenTx;
57
+ this.scOpenTxs = chainIdOrObj.scOpenTxs;
58
58
  }
59
59
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
60
60
  }
@@ -113,7 +113,7 @@ export class SpvVault<
113
113
  initialUtxo: this.initialUtxo,
114
114
  btcAddress: this.btcAddress,
115
115
  pendingWithdrawals: this.pendingWithdrawals.map(val => val.serialize()),
116
- scOpenTx: this.scOpenTx
116
+ scOpenTxs: this.scOpenTxs
117
117
  }
118
118
  }
119
119
 
@@ -86,7 +86,7 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
86
86
  this.vaultSigner = spvVaultSigner;
87
87
  this.config = config;
88
88
  this.AmountAssertions = new FromBtcAmountAssertions(config, swapPricing);
89
- this.Vaults = new SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.getChain.bind(this), config);
89
+ this.Vaults = new SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
90
90
  }
91
91
 
92
92
  protected async processClaimEvent(swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
@@ -502,6 +502,7 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
502
502
  data.executionFeeRate!==swap.executionFeeShare ||
503
503
  data.rawAmounts[0]!==swap.rawAmountToken ||
504
504
  data.rawAmounts[1]!==swap.rawAmountGasToken ||
505
+ data.getExecutionData()!=null ||
505
506
  data.getSpentVaultUtxo()!==swap.vaultUtxo ||
506
507
  data.btcTx.outs[0].value!==VAULT_DUST_AMOUNT ||
507
508
  !Buffer.from(data.btcTx.outs[0].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.vaultAddress)) ||
@@ -13,7 +13,7 @@ import {PluginManager} from "../../plugins/PluginManager";
13
13
  import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
14
14
  import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
15
15
  import {AmountAssertions} from "../assertions/AmountAssertions";
16
- import {ChainData} from "../SwapHandler";
16
+ import {MultichainData} from "../SwapHandler";
17
17
  import {Transaction} from "@scure/btc-signer";
18
18
 
19
19
  export const VAULT_DUST_AMOUNT = 600;
@@ -28,7 +28,7 @@ export class SpvVaults {
28
28
  readonly vaultSigner: ISpvVaultSigner;
29
29
  readonly bitcoinRpc: BitcoinRpc<any>;
30
30
  readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
31
- readonly getChain: (chainId: string) => ChainData
31
+ readonly chains: MultichainData;
32
32
 
33
33
  readonly logger = getLogger("SpvVaults: ");
34
34
 
@@ -37,15 +37,29 @@ export class SpvVaults {
37
37
  bitcoin: IBitcoinWallet,
38
38
  vaultSigner: ISpvVaultSigner,
39
39
  bitcoinRpc: BitcoinRpc<any>,
40
- getChain: (chainId: string) => ChainData,
40
+ chains: MultichainData,
41
41
  config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
42
42
  ) {
43
43
  this.vaultStorage = vaultStorage;
44
44
  this.bitcoin = bitcoin;
45
45
  this.vaultSigner = vaultSigner;
46
46
  this.bitcoinRpc = bitcoinRpc;
47
- this.getChain = getChain;
47
+ this.chains = chains;
48
48
  this.config = config;
49
+
50
+ for(let chainId in chains.chains) {
51
+ const {chainInterface} = chains.chains[chainId];
52
+ chainInterface.onBeforeTxReplace(async (oldTx: string, oldTxId: string, newTx: string, newTxId: string) => {
53
+ for(let key in this.vaultStorage.data) {
54
+ const vaultData = this.vaultStorage.data[key];
55
+ if(vaultData.scOpenTxs!=null && vaultData.scOpenTxs[oldTxId]!=null){
56
+ vaultData.scOpenTxs[newTxId] = newTx;
57
+ await this.saveVault(vaultData);
58
+ break;
59
+ }
60
+ }
61
+ });
62
+ }
49
63
  }
50
64
 
51
65
  async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
@@ -78,7 +92,7 @@ export class SpvVaults {
78
92
  }
79
93
 
80
94
  async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
81
- const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.getChain(chainId);
95
+ const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.chains.chains[chainId];
82
96
 
83
97
  const signerAddress = signer.getAddress();
84
98
 
@@ -141,7 +155,7 @@ export class SpvVaults {
141
155
  return Object.keys(this.vaultStorage.data)
142
156
  .map(key => this.vaultStorage.data[key])
143
157
  .filter(val => chainId==null ? true : val.chainId===chainId)
144
- .filter(val => val.data.getOwner()===this.getChain(val.chainId)?.signer?.getAddress())
158
+ .filter(val => val.data.getOwner()===this.chains.chains[val.chainId]?.signer?.getAddress())
145
159
  .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
146
160
  }
147
161
 
@@ -150,7 +164,7 @@ export class SpvVaults {
150
164
 
151
165
  this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
152
166
 
153
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
167
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
154
168
 
155
169
  const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
156
170
 
@@ -167,7 +181,7 @@ export class SpvVaults {
167
181
 
168
182
  if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
169
183
 
170
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
184
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
171
185
 
172
186
  const latestUtxo = vault.getLatestUtxo();
173
187
  const [txId, voutStr] = latestUtxo.split(":");
@@ -239,7 +253,7 @@ export class SpvVaults {
239
253
  const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
240
254
 
241
255
  for(let vault of vaults) {
242
- const {signer, spvVaultContract, chainInterface} = this.getChain(vault.chainId);
256
+ const {signer, spvVaultContract, chainInterface} = this.chains.chains[vault.chainId];
243
257
  if(vault.data.getOwner()!==signer.getAddress()) continue;
244
258
 
245
259
  if(vault.state===SpvVaultState.BTC_INITIATED) {
@@ -258,15 +272,24 @@ export class SpvVaults {
258
272
 
259
273
  if(vault.state===SpvVaultState.BTC_CONFIRMED) {
260
274
  //Check if open txs were sent already
261
- if(vault.scOpenTx!=null) {
275
+ if(vault.scOpenTxs!=null) {
262
276
  //Check if confirmed
263
- const status = await chainInterface.getTxStatus(vault.scOpenTx.rawTx);
264
- if(status==="pending") return;
265
- if(status==="success") {
266
- vault.state = SpvVaultState.OPENED;
267
- await this.saveVault(vault);
268
- return;
277
+ let _continue = false;
278
+ for(let txId in vault.scOpenTxs) {
279
+ const tx = vault.scOpenTxs[txId];
280
+ const status = await chainInterface.getTxStatus(tx);
281
+ if(status==="pending") {
282
+ _continue = true;
283
+ break;
284
+ }
285
+ if(status==="success") {
286
+ vault.state = SpvVaultState.OPENED;
287
+ await this.saveVault(vault);
288
+ _continue = true;
289
+ break;
290
+ }
269
291
  }
292
+ if(_continue) continue;
270
293
  }
271
294
 
272
295
  const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
@@ -277,7 +300,7 @@ export class SpvVaults {
277
300
  numTx++;
278
301
  if(numTx===txs.length) {
279
302
  //Final tx
280
- vault.scOpenTx = {txId, rawTx};
303
+ vault.scOpenTxs = {[txId]: rawTx};
281
304
  await this.saveVault(vault);
282
305
  }
283
306
  }
@@ -349,7 +372,7 @@ export class SpvVaults {
349
372
  }
350
373
 
351
374
  async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
352
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
375
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
353
376
 
354
377
  try {
355
378
  const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
@@ -379,7 +402,7 @@ export class SpvVaults {
379
402
  * @protected
380
403
  */
381
404
  async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
382
- const {signer} = this.getChain(chainIdentifier);
405
+ const {signer} = this.chains.chains[chainIdentifier];
383
406
 
384
407
  const pluginResponse = await PluginManager.onVaultSelection(
385
408
  chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}