@atomiqlabs/lp-lib 12.1.0 → 13.0.0-beta.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 (119) hide show
  1. package/dist/index.d.ts +18 -13
  2. package/dist/index.js +18 -13
  3. package/dist/plugins/IPlugin.d.ts +35 -12
  4. package/dist/plugins/PluginManager.d.ts +38 -15
  5. package/dist/plugins/PluginManager.js +33 -9
  6. package/dist/prices/BinanceSwapPrice.d.ts +1 -1
  7. package/dist/prices/BinanceSwapPrice.js +1 -1
  8. package/dist/prices/CoinGeckoSwapPrice.d.ts +1 -1
  9. package/dist/prices/CoinGeckoSwapPrice.js +1 -1
  10. package/dist/{swaps → prices}/ISwapPrice.js +4 -0
  11. package/dist/prices/OKXSwapPrice.d.ts +1 -1
  12. package/dist/prices/OKXSwapPrice.js +1 -1
  13. package/dist/swaps/SwapHandler.d.ts +20 -58
  14. package/dist/swaps/SwapHandler.js +17 -186
  15. package/dist/swaps/SwapHandlerSwap.d.ts +8 -23
  16. package/dist/swaps/SwapHandlerSwap.js +7 -39
  17. package/dist/swaps/assertions/AmountAssertions.d.ts +28 -0
  18. package/dist/swaps/assertions/AmountAssertions.js +72 -0
  19. package/dist/swaps/assertions/FromBtcAmountAssertions.d.ts +76 -0
  20. package/dist/swaps/assertions/FromBtcAmountAssertions.js +162 -0
  21. package/dist/swaps/assertions/LightningAssertions.d.ts +44 -0
  22. package/dist/swaps/assertions/LightningAssertions.js +86 -0
  23. package/dist/swaps/assertions/ToBtcAmountAssertions.d.ts +53 -0
  24. package/dist/swaps/{ToBtcBaseSwapHandler.js → assertions/ToBtcAmountAssertions.js} +20 -94
  25. package/dist/swaps/escrow/EscrowHandler.d.ts +51 -0
  26. package/dist/swaps/escrow/EscrowHandler.js +158 -0
  27. package/dist/swaps/escrow/EscrowHandlerSwap.d.ts +35 -0
  28. package/dist/swaps/escrow/EscrowHandlerSwap.js +69 -0
  29. package/dist/swaps/{FromBtcBaseSwap.d.ts → escrow/FromBtcBaseSwap.d.ts} +2 -3
  30. package/dist/swaps/{FromBtcBaseSwap.js → escrow/FromBtcBaseSwap.js} +4 -7
  31. package/dist/swaps/{FromBtcBaseSwapHandler.d.ts → escrow/FromBtcBaseSwapHandler.d.ts} +10 -49
  32. package/dist/swaps/{FromBtcBaseSwapHandler.js → escrow/FromBtcBaseSwapHandler.js} +16 -137
  33. package/dist/swaps/{ToBtcBaseSwap.d.ts → escrow/ToBtcBaseSwap.d.ts} +2 -2
  34. package/dist/swaps/{ToBtcBaseSwap.js → escrow/ToBtcBaseSwap.js} +4 -4
  35. package/dist/swaps/escrow/ToBtcBaseSwapHandler.d.ts +53 -0
  36. package/dist/swaps/escrow/ToBtcBaseSwapHandler.js +81 -0
  37. package/dist/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcAbs.d.ts +4 -4
  38. package/dist/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcAbs.js +15 -15
  39. package/dist/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcSwapAbs.js +1 -1
  40. package/dist/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnAbs.d.ts +9 -7
  41. package/dist/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnAbs.js +22 -19
  42. package/dist/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnSwapAbs.js +3 -3
  43. package/dist/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcAbs.d.ts +4 -4
  44. package/dist/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcAbs.js +14 -13
  45. package/dist/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcSwapAbs.js +3 -3
  46. package/dist/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnAbs.d.ts +6 -26
  47. package/dist/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnAbs.js +20 -57
  48. package/dist/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnSwapAbs.js +3 -3
  49. package/dist/swaps/spv_vault_swap/SpvVault.d.ts +41 -0
  50. package/dist/swaps/spv_vault_swap/SpvVault.js +111 -0
  51. package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +63 -0
  52. package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +145 -0
  53. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +68 -0
  54. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +469 -0
  55. package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +57 -0
  56. package/dist/swaps/spv_vault_swap/SpvVaults.js +369 -0
  57. package/dist/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrusted.d.ts +10 -13
  58. package/dist/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrusted.js +25 -30
  59. package/dist/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrustedSwap.d.ts +9 -4
  60. package/dist/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrustedSwap.js +15 -7
  61. package/dist/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrusted.d.ts +12 -14
  62. package/dist/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrusted.js +33 -35
  63. package/dist/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrustedSwap.d.ts +9 -4
  64. package/dist/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrustedSwap.js +17 -7
  65. package/dist/utils/Utils.d.ts +13 -5
  66. package/dist/utils/Utils.js +23 -1
  67. package/dist/wallets/IBitcoinWallet.d.ts +6 -0
  68. package/dist/wallets/ISpvVaultSigner.d.ts +7 -0
  69. package/dist/wallets/ISpvVaultSigner.js +2 -0
  70. package/dist/wallets/ISpvVaultWallet.d.ts +42 -0
  71. package/dist/wallets/ISpvVaultWallet.js +2 -0
  72. package/package.json +2 -2
  73. package/src/index.ts +21 -15
  74. package/src/plugins/IPlugin.ts +27 -19
  75. package/src/plugins/PluginManager.ts +51 -26
  76. package/src/prices/BinanceSwapPrice.ts +1 -1
  77. package/src/prices/CoinGeckoSwapPrice.ts +1 -1
  78. package/src/{swaps → prices}/ISwapPrice.ts +4 -0
  79. package/src/prices/OKXSwapPrice.ts +1 -1
  80. package/src/swaps/SwapHandler.ts +22 -205
  81. package/src/swaps/SwapHandlerSwap.ts +10 -46
  82. package/src/swaps/assertions/AmountAssertions.ts +77 -0
  83. package/src/swaps/assertions/FromBtcAmountAssertions.ts +228 -0
  84. package/src/swaps/assertions/LightningAssertions.ts +103 -0
  85. package/src/swaps/{ToBtcBaseSwapHandler.ts → assertions/ToBtcAmountAssertions.ts} +27 -142
  86. package/src/swaps/escrow/EscrowHandler.ts +179 -0
  87. package/src/swaps/escrow/EscrowHandlerSwap.ts +87 -0
  88. package/src/swaps/{FromBtcBaseSwap.ts → escrow/FromBtcBaseSwap.ts} +4 -8
  89. package/src/swaps/{FromBtcBaseSwapHandler.ts → escrow/FromBtcBaseSwapHandler.ts} +30 -190
  90. package/src/swaps/{ToBtcBaseSwap.ts → escrow/ToBtcBaseSwap.ts} +4 -5
  91. package/src/swaps/escrow/ToBtcBaseSwapHandler.ts +130 -0
  92. package/src/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcAbs.ts +20 -20
  93. package/src/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcSwapAbs.ts +1 -1
  94. package/src/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnAbs.ts +29 -25
  95. package/src/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnSwapAbs.ts +2 -2
  96. package/src/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcAbs.ts +19 -18
  97. package/src/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcSwapAbs.ts +2 -2
  98. package/src/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnAbs.ts +26 -66
  99. package/src/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnSwapAbs.ts +2 -2
  100. package/src/swaps/spv_vault_swap/SpvVault.ts +143 -0
  101. package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +207 -0
  102. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +606 -0
  103. package/src/swaps/spv_vault_swap/SpvVaults.ts +441 -0
  104. package/src/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrusted.ts +36 -51
  105. package/src/swaps/{frombtc_trusted → trusted/frombtc_trusted}/FromBtcTrustedSwap.ts +18 -8
  106. package/src/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrusted.ts +43 -52
  107. package/src/swaps/{frombtcln_trusted → trusted/frombtcln_trusted}/FromBtcLnTrustedSwap.ts +20 -8
  108. package/src/utils/Utils.ts +27 -1
  109. package/src/wallets/IBitcoinWallet.ts +4 -0
  110. package/src/wallets/ISpvVaultSigner.ts +11 -0
  111. package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +0 -26
  112. package/dist/swaps/FromBtcLnBaseSwapHandler.js +0 -46
  113. package/dist/swaps/ToBtcBaseSwapHandler.d.ts +0 -95
  114. package/src/swaps/FromBtcLnBaseSwapHandler.ts +0 -63
  115. /package/dist/{swaps → prices}/ISwapPrice.d.ts +0 -0
  116. /package/dist/swaps/{frombtc_abstract → escrow/frombtc_abstract}/FromBtcSwapAbs.d.ts +0 -0
  117. /package/dist/swaps/{frombtcln_abstract → escrow/frombtcln_abstract}/FromBtcLnSwapAbs.d.ts +0 -0
  118. /package/dist/swaps/{tobtc_abstract → escrow/tobtc_abstract}/ToBtcSwapAbs.d.ts +0 -0
  119. /package/dist/swaps/{tobtcln_abstract → escrow/tobtcln_abstract}/ToBtcLnSwapAbs.d.ts +0 -0
@@ -0,0 +1,441 @@
1
+ import {SpvVault, SpvVaultState} from "./SpvVault";
2
+ import {
3
+ BitcoinRpc,
4
+ IStorageManager,
5
+ SpvVaultClaimEvent,
6
+ SpvVaultCloseEvent,
7
+ SpvVaultDepositEvent,
8
+ SpvVaultOpenEvent, SpvWithdrawalTransactionData
9
+ } from "@atomiqlabs/base";
10
+ import {SpvVaultSwap} from "./SpvVaultSwap";
11
+ import {bigIntSorter} from "../../utils/Utils";
12
+ import {PluginManager} from "../../plugins/PluginManager";
13
+ import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
14
+ import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
15
+ import {AmountAssertions} from "../assertions/AmountAssertions";
16
+ import {ChainData} from "../SwapHandler";
17
+ import {Transaction} from "@scure/btc-signer";
18
+
19
+ export const VAULT_DUST_AMOUNT = 600;
20
+ const VAULT_INIT_CONFIRMATIONS = 2;
21
+ const BTC_FINALIZATION_CONFIRMATIONS = 6;
22
+
23
+ export class SpvVaults {
24
+
25
+ readonly vaultStorage: IStorageManager<SpvVault>;
26
+
27
+ readonly bitcoin: IBitcoinWallet;
28
+ readonly vaultSigner: ISpvVaultSigner;
29
+ readonly bitcoinRpc: BitcoinRpc<any>;
30
+ readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
31
+ readonly getChain: (chainId: string) => ChainData
32
+
33
+ readonly logger = {
34
+ debug: (msg: string, ...args: any) => console.debug("SpvVaults: "+msg, ...args),
35
+ info: (msg: string, ...args: any) => console.info("SpvVaults: "+msg, ...args),
36
+ warn: (msg: string, ...args: any) => console.warn("SpvVaults: "+msg, ...args),
37
+ error: (msg: string, ...args: any) => console.error("SpvVaults: "+msg, ...args)
38
+ };
39
+
40
+ constructor(
41
+ vaultStorage: IStorageManager<SpvVault>,
42
+ bitcoin: IBitcoinWallet,
43
+ vaultSigner: ISpvVaultSigner,
44
+ bitcoinRpc: BitcoinRpc<any>,
45
+ getChain: (chainId: string) => ChainData,
46
+ config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
47
+ ) {
48
+ this.vaultStorage = vaultStorage;
49
+ this.bitcoin = bitcoin;
50
+ this.vaultSigner = vaultSigner;
51
+ this.bitcoinRpc = bitcoinRpc;
52
+ this.getChain = getChain;
53
+ this.config = config;
54
+ }
55
+
56
+ async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
57
+ vault.update(event);
58
+ await this.saveVault(vault);
59
+ }
60
+
61
+ async processOpenEvent(vault: SpvVault, event: SpvVaultOpenEvent): Promise<void> {
62
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
63
+ vault.state = SpvVaultState.OPENED;
64
+ }
65
+ vault.update(event);
66
+ await this.saveVault(vault);
67
+ }
68
+
69
+ async processCloseEvent(vault: SpvVault, event: SpvVaultCloseEvent): Promise<void> {
70
+ if(vault.state===SpvVaultState.OPENED) {
71
+ vault.state = SpvVaultState.CLOSED;
72
+ }
73
+ vault.update(event);
74
+ await this.saveVault(vault);
75
+ }
76
+
77
+ async processClaimEvent(vault: SpvVault, swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
78
+ //Update vault
79
+ const foundPendingWithdrawal = vault.pendingWithdrawals.findIndex(val => val.btcTx.txid===event.btcTxId);
80
+ if(foundPendingWithdrawal!==-1) vault.pendingWithdrawals.splice(foundPendingWithdrawal, 1);
81
+ vault.update(event);
82
+ await this.saveVault(vault);
83
+ }
84
+
85
+ async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
86
+ const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.getChain(chainId);
87
+
88
+ const signerAddress = signer.getAddress();
89
+
90
+ //Check vaultId of the latest saved vault
91
+ let latestVaultId: bigint = -1n;
92
+ for(let key in this.vaultStorage.data) {
93
+ const vault = this.vaultStorage.data[key];
94
+ if(vault.chainId!==chainId) continue;
95
+ if(vault.data.getOwner()!==signerAddress) continue;
96
+ if(vault.data.getVaultId() > latestVaultId) latestVaultId = vault.data.getVaultId();
97
+ }
98
+
99
+ latestVaultId++;
100
+
101
+ const vaultAddreses: {vaultId: bigint, address: string}[] = [];
102
+ for(let i=0;i<count;i++) {
103
+ const vaultId = latestVaultId + BigInt(i);
104
+ const address = await this.vaultSigner.getAddress(chainId, vaultId);
105
+ vaultAddreses.push({vaultId, address});
106
+ }
107
+
108
+ //Construct transaction
109
+ const txResult = await this.bitcoin.getSignedMultiTransaction(vaultAddreses.map(val => {
110
+ return {address: val.address, amount: VAULT_DUST_AMOUNT}
111
+ }), feeRate);
112
+
113
+ const nativeToken = chainInterface.getNativeCurrencyAddress();
114
+
115
+ const vaults = await Promise.all(vaultAddreses.map(async (val, index) => {
116
+ const vaultData = await spvVaultContract.createVaultData(signerAddress, val.vaultId, txResult.txId+":"+index, confirmations, [
117
+ {token, multiplier: tokenMultipliers?.[token] ?? 1n},
118
+ {token: nativeToken, multiplier: tokenMultipliers?.[nativeToken] ?? 1n}
119
+ ]);
120
+ return new SpvVault(chainId, vaultData, val.address);
121
+ }));
122
+
123
+ //Save vaults
124
+ if(this.vaultStorage.saveDataArr!=null) {
125
+ await this.vaultStorage.saveDataArr(vaults.map(val => {
126
+ return {id: val.getIdentifier(), object: val}
127
+ }));
128
+ } else {
129
+ for(let vault of vaults) {
130
+ await this.vaultStorage.saveData(vault.getIdentifier(), vault);
131
+ }
132
+ }
133
+
134
+ //Send bitcoin tx
135
+ await this.bitcoin.sendRawTransaction(txResult.raw);
136
+
137
+ this.logger.info("createVaults(): Funding "+count+" vaults, bitcoin txId: "+txResult.txId);
138
+
139
+ return {
140
+ vaultsCreated: vaults.map(val => val.data.getVaultId()),
141
+ btcTxId: txResult.txId
142
+ };
143
+ }
144
+
145
+ async listVaults(chainId?: string, token?: string) {
146
+ return Object.keys(this.vaultStorage.data)
147
+ .map(key => this.vaultStorage.data[key])
148
+ .filter(val => chainId==null ? true : val.chainId===chainId)
149
+ .filter(val => val.data.getOwner()===this.getChain(val.chainId)?.signer?.getAddress())
150
+ .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
151
+ }
152
+
153
+ async fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string> {
154
+ if(vault.state!==SpvVaultState.OPENED) throw new Error("Vault not opened!");
155
+
156
+ this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
157
+
158
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
159
+
160
+ const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
161
+
162
+ this.logger.info("fundVault(): Tokens deposited to vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", ")+", txId: "+txId);
163
+
164
+ return txId;
165
+ }
166
+
167
+ async withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string> {
168
+ tokenAmounts.forEach((rawAmount, index) => {
169
+ if(vault.balances[index]==null) throw new Error("Token not found in the vault");
170
+ if(vault.balances[index].rawAmount<rawAmount) throw new Error("Not enough balance in the vault");
171
+ });
172
+
173
+ if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
174
+
175
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
176
+
177
+ const latestUtxo = vault.getLatestUtxo();
178
+ const [txId, voutStr] = latestUtxo.split(":");
179
+
180
+ const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
181
+ let opReturnScript: Buffer;
182
+ if(opReturnData.length<76) {
183
+ opReturnScript = Buffer.concat([
184
+ Buffer.from([0x6a, opReturnData.length]),
185
+ opReturnData
186
+ ]);
187
+ } else {
188
+ opReturnScript = Buffer.concat([
189
+ Buffer.from([0x6a, 0x4c, opReturnData.length]),
190
+ opReturnData
191
+ ]);
192
+ }
193
+
194
+ let psbt = new Transaction({
195
+ allowUnknownOutputs: true
196
+ });
197
+ psbt.addInput({
198
+ txid: txId,
199
+ index: parseInt(voutStr),
200
+ witnessUtxo: {
201
+ amount: BigInt(VAULT_DUST_AMOUNT),
202
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
203
+ }
204
+ });
205
+ psbt.addOutput({
206
+ amount: BigInt(VAULT_DUST_AMOUNT),
207
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
208
+ });
209
+ psbt.addOutput({
210
+ amount: 0n,
211
+ script: opReturnScript
212
+ });
213
+
214
+ psbt = await this.bitcoin.fundPsbt(psbt, feeRate);
215
+ if(psbt.inputsLength<2) throw new Error("PSBT needs at least 2 inputs!");
216
+ psbt.updateInput(0, {sequence: 0x80000000});
217
+ psbt.updateInput(1, {sequence: 0x80000000});
218
+ psbt = await this.vaultSigner.signPsbt(vault.chainId, vault.data.getVaultId(), psbt, [0]);
219
+ const res = await this.bitcoin.signPsbt(psbt);
220
+
221
+ const parsedTransaction = await this.bitcoinRpc.parseTransaction(res.raw);
222
+ const withdrawalData = await spvVaultContract.getWithdrawalData(parsedTransaction);
223
+
224
+ if(withdrawalData.getSpentVaultUtxo()!==vault.getLatestUtxo()) {
225
+ throw new Error("Latest vault UTXO already spent! Please try again later.");
226
+ }
227
+ vault.addWithdrawal(withdrawalData);
228
+ await this.saveVault(vault);
229
+
230
+ try {
231
+ await this.bitcoin.sendRawTransaction(res.raw);
232
+ } catch (e) {
233
+ vault.removeWithdrawal(withdrawalData);
234
+ await this.saveVault(vault);
235
+ throw e;
236
+ }
237
+
238
+ return res.txId;
239
+ }
240
+
241
+ async checkVaults() {
242
+ const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
243
+
244
+ const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
245
+
246
+ for(let vault of vaults) {
247
+ const {signer, spvVaultContract, chainInterface} = this.getChain(vault.chainId);
248
+ if(vault.data.getOwner()!==signer.getAddress()) continue;
249
+
250
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
251
+ //Check if btc tx confirmed
252
+ const txId = vault.initialUtxo.split(":")[0];
253
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
254
+ if(btcTx.confirmations >= VAULT_INIT_CONFIRMATIONS) {
255
+ //Double-check the state here to prevent race condition
256
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
257
+ vault.state = SpvVaultState.BTC_CONFIRMED;
258
+ await this.saveVault(vault);
259
+ }
260
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" confirmed on bitcoin, opening vault on "+vault.chainId);
261
+ }
262
+ }
263
+
264
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
265
+ //Check if open txs were sent already
266
+ if(vault.scOpenTx!=null) {
267
+ //Check if confirmed
268
+ const status = await chainInterface.getTxStatus(vault.scOpenTx.rawTx);
269
+ if(status==="pending") return;
270
+ if(status==="success") {
271
+ vault.state = SpvVaultState.OPENED;
272
+ await this.saveVault(vault);
273
+ return;
274
+ }
275
+ }
276
+
277
+ const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
278
+ let numTx = 0;
279
+ const txIds = await chainInterface.sendAndConfirm(
280
+ signer, txs, true, undefined, false,
281
+ async (txId: string, rawTx: string) => {
282
+ numTx++;
283
+ if(numTx===txs.length) {
284
+ //Final tx
285
+ vault.scOpenTx = {txId, rawTx};
286
+ await this.saveVault(vault);
287
+ }
288
+ }
289
+ );
290
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" opened on "+vault.chainId+" txId: "+txIds.join(", "));
291
+
292
+ vault.state = SpvVaultState.OPENED;
293
+ await this.saveVault(vault);
294
+ }
295
+
296
+ if(vault.state===SpvVaultState.OPENED) {
297
+ let changed = false;
298
+ //Check if some of the pendingWithdrawals got confirmed
299
+ let latestOwnWithdrawalIndex = -1;
300
+ let latestConfirmedWithdrawalIndex = -1;
301
+ for(let i=0; i<vault.pendingWithdrawals.length; i++) {
302
+ const pendingWithdrawal = vault.pendingWithdrawals[i];
303
+ //Check all the pending withdrawals that were not finalized yet
304
+ if(pendingWithdrawal.btcTx.confirmations==null || pendingWithdrawal.btcTx.confirmations < BTC_FINALIZATION_CONFIRMATIONS) {
305
+ const btcTx = await this.bitcoinRpc.getTransaction(pendingWithdrawal.btcTx.txid);
306
+ if(btcTx==null) {
307
+ //Probable double-spend, remove from pending withdrawals
308
+ const index = vault.pendingWithdrawals.indexOf(pendingWithdrawal);
309
+ if(index===-1) {
310
+ this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!")
311
+ } else {
312
+ vault.pendingWithdrawals.splice(index, 1);
313
+ }
314
+ changed = true;
315
+ } else {
316
+ //Update confirmations count
317
+ if(
318
+ pendingWithdrawal.btcTx.confirmations !== btcTx.confirmations ||
319
+ pendingWithdrawal.btcTx.blockhash !== btcTx.blockhash
320
+ ) {
321
+ pendingWithdrawal.btcTx.confirmations = btcTx.confirmations;
322
+ pendingWithdrawal.btcTx.blockhash = btcTx.blockhash;
323
+ changed = true;
324
+ }
325
+ }
326
+ }
327
+ //Check it has enough confirmations
328
+ if(pendingWithdrawal.btcTx.confirmations >= vault.data.getConfirmations()) {
329
+ latestConfirmedWithdrawalIndex = i;
330
+ //Check if the pending withdrawals contain a withdrawal to our own address
331
+ if (pendingWithdrawal.isRecipient(signer.getAddress())) {
332
+ latestOwnWithdrawalIndex = i;
333
+ }
334
+ }
335
+ }
336
+ if(changed) {
337
+ await this.saveVault(vault);
338
+ }
339
+ if(this.config.maxUnclaimedWithdrawals!=null && latestConfirmedWithdrawalIndex+1 >= this.config.maxUnclaimedWithdrawals) {
340
+ this.logger.info("checkVaults(): Processing withdrawals by self, because a lot of them are unclaimed!");
341
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestConfirmedWithdrawalIndex+1)});
342
+ } else if(latestOwnWithdrawalIndex!==-1) {
343
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestOwnWithdrawalIndex+1)});
344
+ }
345
+ }
346
+ }
347
+
348
+ for(let {vault, withdrawals} of claimWithdrawals) {
349
+ if(!await this.claimWithdrawals(vault, withdrawals)) {
350
+ this.logger.error("checkVaults(): Cannot process withdrawals "+withdrawals.map(val => val.btcTx.txid).join(", ")+" for vault: "+vault.data.getVaultId());
351
+ break;
352
+ }
353
+ }
354
+ }
355
+
356
+ async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
357
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
358
+
359
+ try {
360
+ const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
361
+ return {tx};
362
+ }), undefined, true, {waitForConfirmation: true});
363
+ this.logger.info("claimWithdrawal(): Successfully claimed withdrawals, btcTxIds: "+withdrawal.map(val => val.btcTx.txid).join(", ")+" smartChainTxId: "+txId);
364
+ return true;
365
+ } catch (e) {
366
+ this.logger.error("claimWithdrawal(): Tried to claim but got error: ", e);
367
+ return false;
368
+ }
369
+ }
370
+
371
+ async getVault(chainId: string, owner: string, vaultId: bigint) {
372
+ return this.vaultStorage.data[chainId+"_"+owner+"_"+vaultId.toString(10)];
373
+ }
374
+
375
+ /**
376
+ * Returns a ready-to-use vault for a specific request
377
+ *
378
+ * @param chainIdentifier
379
+ * @param totalSats
380
+ * @param token
381
+ * @param amount
382
+ * @param gasToken
383
+ * @param gasTokenAmount
384
+ * @protected
385
+ */
386
+ async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
387
+ const {signer} = this.getChain(chainIdentifier);
388
+
389
+ const pluginResponse = await PluginManager.onVaultSelection(
390
+ chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}
391
+ );
392
+ if(pluginResponse!=null) {
393
+ AmountAssertions.handlePluginErrorResponses(pluginResponse);
394
+ return pluginResponse as SpvVault;
395
+ }
396
+
397
+ const candidates = Object.keys(this.vaultStorage.data)
398
+ .map(key => this.vaultStorage.data[key])
399
+ .filter(vault =>
400
+ vault.chainId===chainIdentifier && vault.data.getOwner()===signer.getAddress() && vault.isReady()
401
+ )
402
+ .filter(vault => {
403
+ const token0 = vault.balances[0];
404
+ if(token0.token!==token || token0.scaledAmount < amount) return false;
405
+ if(gasToken!=null && gasTokenAmount!==0n) {
406
+ const token1 = vault.balances[1];
407
+ if(token1.token!==gasToken || token1.scaledAmount < gasTokenAmount) return false;
408
+ }
409
+ return true;
410
+ });
411
+
412
+ candidates.sort((a, b) => bigIntSorter(a.balances[0].scaledAmount, b.balances[0].scaledAmount));
413
+
414
+ const result = candidates[0];
415
+
416
+ if(result==null) throw {
417
+ code: 20301,
418
+ msg: "No suitable swap vault found, try again later!"
419
+ };
420
+
421
+ return result;
422
+ }
423
+
424
+ saveVault(vault: SpvVault) {
425
+ return this.vaultStorage.saveData(vault.getIdentifier(), vault);
426
+ }
427
+
428
+ async startVaultsWatchdog() {
429
+ let rerun: () => Promise<void>;
430
+ rerun = async () => {
431
+ await this.checkVaults().catch( e => console.error(e));
432
+ setTimeout(rerun, this.config.vaultsCheckInterval);
433
+ };
434
+ await rerun();
435
+ }
436
+
437
+ async init() {
438
+ const vaults = await this.vaultStorage.loadData(SpvVault);
439
+ }
440
+
441
+ }
@@ -1,27 +1,18 @@
1
- import {FromBtcBaseConfig, FromBtcBaseSwapHandler} from "../FromBtcBaseSwapHandler";
2
1
  import {FromBtcTrustedSwap, FromBtcTrustedSwapState} from "./FromBtcTrustedSwap";
3
- import {
4
- BitcoinRpc,
5
- BtcBlock,
6
- BtcTx,
7
- BtcVout,
8
- ClaimEvent,
9
- InitializeEvent,
10
- RefundEvent,
11
- SwapData
12
- } from "@atomiqlabs/base";
2
+ import {BitcoinRpc, BtcBlock, BtcTx, BtcVout} from "@atomiqlabs/base";
13
3
  import {Express, Request, Response} from "express";
14
- import {MultichainData, SwapHandlerType} from "../SwapHandler";
15
- import {IIntermediaryStorage} from "../../storage/IIntermediaryStorage";
16
- import {ISwapPrice} from "../ISwapPrice";
17
- import {PluginManager} from "../../plugins/PluginManager";
18
- import {expressHandlerWrapper, HEX_REGEX} from "../../utils/Utils";
19
- import {IParamReader} from "../../utils/paramcoders/IParamReader";
20
- import {ServerParamEncoder} from "../../utils/paramcoders/server/ServerParamEncoder";
21
- import {FieldTypeEnum, verifySchema} from "../../utils/paramcoders/SchemaVerifier";
22
- import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
23
-
24
- export type FromBtcTrustedConfig = FromBtcBaseConfig & {
4
+ import {MultichainData, SwapBaseConfig, SwapHandler, SwapHandlerType} from "../../SwapHandler";
5
+ import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
6
+ import {ISwapPrice} from "../../../prices/ISwapPrice";
7
+ import {PluginManager} from "../../../plugins/PluginManager";
8
+ import {expressHandlerWrapper, getAbortController, HEX_REGEX} from "../../../utils/Utils";
9
+ import {IParamReader} from "../../../utils/paramcoders/IParamReader";
10
+ import {ServerParamEncoder} from "../../../utils/paramcoders/server/ServerParamEncoder";
11
+ import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
12
+ import {IBitcoinWallet} from "../../../wallets/IBitcoinWallet";
13
+ import {FromBtcAmountAssertions} from "../../assertions/FromBtcAmountAssertions";
14
+
15
+ export type FromBtcTrustedConfig = SwapBaseConfig & {
25
16
  doubleSpendCheckInterval: number,
26
17
  swapAddressExpiry: number,
27
18
  recommendFeeMultiplier?: number
@@ -35,9 +26,8 @@ export type FromBtcTrustedRequestType = {
35
26
  token?: string
36
27
  };
37
28
 
38
- export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, FromBtcTrustedSwapState> {
39
- readonly type: SwapHandlerType = SwapHandlerType.FROM_BTC_TRUSTED;
40
- readonly swapType = null;
29
+ export class FromBtcTrusted extends SwapHandler<FromBtcTrustedSwap, FromBtcTrustedSwapState> {
30
+ readonly type = SwapHandlerType.FROM_BTC_TRUSTED;
41
31
 
42
32
  readonly config: FromBtcTrustedConfig;
43
33
  readonly bitcoin: IBitcoinWallet;
@@ -50,6 +40,8 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
50
40
  readonly doubleSpentSwaps: Map<string, string> = new Map();
51
41
  readonly processedTxIds: Map<string, { scTxId: string, txId: string, adjustedAmount: bigint, adjustedTotal: bigint }> = new Map();
52
42
 
43
+ readonly AmountAssertions: FromBtcAmountAssertions;
44
+
53
45
  constructor(
54
46
  storageDirectory: IIntermediaryStorage<FromBtcTrustedSwap>,
55
47
  path: string,
@@ -60,6 +52,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
60
52
  config: FromBtcTrustedConfig
61
53
  ) {
62
54
  super(storageDirectory, path, chains, swapPricing);
55
+ this.AmountAssertions = new FromBtcAmountAssertions(config, swapPricing);
63
56
  this.config = config;
64
57
  this.config.recommendFeeMultiplier ??= 1.25;
65
58
  this.bitcoin = bitcoin;
@@ -166,7 +159,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
166
159
  protected async processPastSwap(swap: FromBtcTrustedSwap, tx: BtcTx | null, vout: number | null): Promise<void> {
167
160
  const foundVout: BtcVout = tx.outs[vout];
168
161
 
169
- const {swapContract, signer} = this.getChain(swap.chainIdentifier);
162
+ const {chainInterface, signer} = this.getChain(swap.chainIdentifier);
170
163
 
171
164
  const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
172
165
 
@@ -279,7 +272,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
279
272
 
280
273
  if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
281
274
  //Send gas token
282
- const balance: Promise<bigint> = swapContract.getBalance(signer.getAddress(), swap.token, false);
275
+ const balance: Promise<bigint> = chainInterface.getBalance(signer.getAddress(), swap.token);
283
276
  try {
284
277
  await this.checkBalance(swap.adjustedOutput, balance, null);
285
278
  if(swap.metadata!=null) swap.metadata.times.receivedBalanceChecked = Date.now();
@@ -294,8 +287,8 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
294
287
  let unlock = swap.lock(30*1000);
295
288
  if(unlock==null) return;
296
289
 
297
- const txns = await swapContract.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
298
- await swapContract.sendAndConfirm(signer, txns, true, null, false, async (txId: string, rawTx: string) => {
290
+ const txns = await chainInterface.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
291
+ await chainInterface.sendAndConfirm(signer, txns, true, null, false, async (txId: string, rawTx: string) => {
299
292
  swap.txIds = {init: txId};
300
293
  swap.scRawTx = rawTx;
301
294
  if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
@@ -308,7 +301,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
308
301
  }
309
302
 
310
303
  if(swap.state===FromBtcTrustedSwapState.SENT) {
311
- const txStatus = await swapContract.getTxStatus(swap.scRawTx);
304
+ const txStatus = await chainInterface.getTxStatus(swap.scRawTx);
312
305
  switch(txStatus) {
313
306
  case "not_found":
314
307
  //Retry
@@ -401,7 +394,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
401
394
  } = {request: {}, times: {}};
402
395
 
403
396
  const chainIdentifier = req.query.chain as string ?? this.chains.default;
404
- const {swapContract, signer} = this.getChain(chainIdentifier);
397
+ const {chainInterface, signer} = this.getChain(chainIdentifier);
405
398
 
406
399
  metadata.times.requestReceived = Date.now();
407
400
  /**
@@ -410,11 +403,11 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
410
403
  * amount: string amount (in lamports/smart chain base units) of the invoice
411
404
  * exactOut: boolean whether to create and exact output swap
412
405
  */
413
- req.query.token ??= swapContract.getNativeCurrencyAddress();
406
+ req.query.token ??= chainInterface.getNativeCurrencyAddress();
414
407
  const parsedBody: FromBtcTrustedRequestType = verifySchema(req.query,{
415
408
  address: (val: string) => val!=null &&
416
409
  typeof(val)==="string" &&
417
- swapContract.isValidAddress(val) ? val : null,
410
+ chainInterface.isValidAddress(val) ? val : null,
418
411
  refundAddress: (val: string) => val==null ? "" :
419
412
  typeof(val)==="string" &&
420
413
  this.isValidBitcoinAddress(val) ? val : null,
@@ -433,7 +426,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
433
426
 
434
427
  const refundAddress = parsedBody.refundAddress==="" ? null : parsedBody.refundAddress;
435
428
 
436
- const requestedAmount = {input: parsedBody.exactIn, amount: parsedBody.amount};
429
+ const requestedAmount = {input: parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token};
437
430
  const request = {
438
431
  chainIdentifier,
439
432
  raw: req,
@@ -443,16 +436,20 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
443
436
  const useToken = parsedBody.token;
444
437
 
445
438
  //Check request params
446
- const fees = await this.preCheckAmounts(request, requestedAmount, useToken);
439
+ const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
447
440
  metadata.times.requestChecked = Date.now();
448
441
 
449
442
  //Create abortController for parallel prefetches
450
443
  const responseStream = res.responseStream;
451
- const abortController = this.getAbortController(responseStream);
444
+ const abortController = getAbortController(responseStream);
452
445
 
453
446
  //Pre-fetch data
454
- const {pricePrefetchPromise} = this.getFromBtcPricePrefetches(chainIdentifier, useToken, useToken, abortController);
455
- const balancePrefetch = swapContract.getBalance(signer.getAddress(), useToken, false).catch(e => {
447
+ const pricePrefetchPromise = this.swapPricing.preFetchPrice(useToken, chainIdentifier).catch(e => {
448
+ this.logger.error("pricePrefetchPromise(): pricePrefetch error: ", e);
449
+ abortController.abort(e);
450
+ return null;
451
+ });
452
+ const balancePrefetch = chainInterface.getBalance(signer.getAddress(), useToken).catch(e => {
456
453
  this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
457
454
  abortController.abort(e);
458
455
  return null;
@@ -464,7 +461,7 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
464
461
  swapFee,
465
462
  swapFeeInToken,
466
463
  totalInToken
467
- } = await this.checkFromBtcAmount(request, requestedAmount, fees, useToken, abortController.signal, pricePrefetchPromise);
464
+ } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, {...requestedAmount, pricePrefetch: pricePrefetchPromise}, fees, abortController.signal);
468
465
  metadata.times.priceCalculated = Date.now();
469
466
 
470
467
  //Make sure we have MORE THAN ENOUGH to honor the swap request
@@ -748,16 +745,4 @@ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, F
748
745
  return {};
749
746
  }
750
747
 
751
- protected processClaimEvent(chainIdentifier: string, swap: FromBtcTrustedSwap, event: ClaimEvent<SwapData>): Promise<void> {
752
- return Promise.resolve(undefined);
753
- }
754
-
755
- protected processInitializeEvent(chainIdentifier: string, swap: FromBtcTrustedSwap, event: InitializeEvent<SwapData>): Promise<void> {
756
- return Promise.resolve(undefined);
757
- }
758
-
759
- protected processRefundEvent(chainIdentifier: string, swap: FromBtcTrustedSwap, event: RefundEvent<SwapData>): Promise<void> {
760
- return Promise.resolve(undefined);
761
- }
762
-
763
748
  }