@atomiqlabs/lp-lib 17.0.1 → 17.0.3

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.
@@ -32,6 +32,7 @@ export declare class SpvVault<D extends SpvWithdrawalTransactionData = SpvWithdr
32
32
  */
33
33
  getConfirmedBalance(): SpvVaultTokenBalance[];
34
34
  serialize(): any;
35
+ static _getIdentifier(chainId: string, data: SpvVaultData): string;
35
36
  getIdentifier(): string;
36
37
  /**
37
38
  * Returns the latest vault utxo
@@ -120,8 +120,11 @@ class SpvVault extends base_1.Lockable {
120
120
  scOpenTxs: this.scOpenTxs
121
121
  };
122
122
  }
123
+ static _getIdentifier(chainId, data) {
124
+ return chainId + "_" + data.getOwner() + "_" + data.getVaultId().toString(10);
125
+ }
123
126
  getIdentifier() {
124
- return this.chainId + "_" + this.data.getOwner() + "_" + this.data.getVaultId().toString(10);
127
+ return SpvVault._getIdentifier(this.chainId, this.data);
125
128
  }
126
129
  /**
127
130
  * Returns the latest vault utxo
@@ -1,5 +1,5 @@
1
1
  import { SpvVault } from "./SpvVault";
2
- import { BitcoinRpc, IStorageManager, SpvVaultClaimEvent, SpvVaultCloseEvent, SpvVaultDepositEvent, SpvVaultOpenEvent, SpvWithdrawalTransactionData } from "@atomiqlabs/base";
2
+ import { BitcoinRpc, BtcBlock, IStorageManager, SpvVaultClaimEvent, SpvVaultCloseEvent, SpvVaultDepositEvent, SpvVaultOpenEvent, SpvWithdrawalTransactionData } from "@atomiqlabs/base";
3
3
  import { SpvVaultSwap } from "./SpvVaultSwap";
4
4
  import { IBitcoinWallet } from "../../wallets/IBitcoinWallet";
5
5
  import { ISpvVaultSigner } from "../../wallets/ISpvVaultSigner";
@@ -9,7 +9,7 @@ export declare class SpvVaults {
9
9
  readonly vaultStorage: IStorageManager<SpvVault>;
10
10
  readonly bitcoin: IBitcoinWallet;
11
11
  readonly vaultSigner: ISpvVaultSigner;
12
- readonly bitcoinRpc: BitcoinRpc<any>;
12
+ readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
13
13
  readonly config: {
14
14
  vaultsCheckInterval: number;
15
15
  maxUnclaimedWithdrawals?: number;
@@ -60,4 +60,11 @@ export declare class SpvVaults {
60
60
  saveVault(vault: SpvVault): Promise<void>;
61
61
  startVaultsWatchdog(): Promise<void>;
62
62
  init(): Promise<void>;
63
+ /**
64
+ * Recovers already created vaults for a given chain from on-chain data. Requires initialized BTC wallet to
65
+ * fetch wallet transactions
66
+ *
67
+ * @param chainId
68
+ */
69
+ recoverVaults(chainId: string): Promise<SpvVault[]>;
63
70
  }
@@ -487,5 +487,68 @@ class SpvVaults {
487
487
  async init() {
488
488
  const vaults = await this.vaultStorage.loadData(SpvVault_1.SpvVault);
489
489
  }
490
+ /**
491
+ * Recovers already created vaults for a given chain from on-chain data. Requires initialized BTC wallet to
492
+ * fetch wallet transactions
493
+ *
494
+ * @param chainId
495
+ */
496
+ async recoverVaults(chainId) {
497
+ const chain = this.chains.chains[chainId];
498
+ if (chainId == null)
499
+ throw new Error(`Chain ${chainId} not found in known chains!`);
500
+ const vaults = await chain.spvVaultContract.getAllVaults(chain.signer.getAddress());
501
+ const recoveredVaults = [];
502
+ let minimumBlockheight = null;
503
+ for (let vaultData of vaults) {
504
+ const vaultIdentifier = SpvVault_1.SpvVault._getIdentifier(chainId, vaultData);
505
+ if (this.vaultStorage.data[vaultIdentifier] != null) {
506
+ this.logger.info(`recoverVaults(${chainId}): Skipping vault ${vaultIdentifier}, because it is already known!`);
507
+ continue;
508
+ }
509
+ const [txId, voutStr] = vaultData.getUtxo().split(":");
510
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
511
+ const btcTxOutput = btcTx.outs[parseInt(voutStr)];
512
+ const vaultAddress = this.bitcoin.fromOutputScript(Buffer.from(btcTxOutput.scriptPubKey.hex, "hex"));
513
+ const vault = new SpvVault_1.SpvVault(chainId, vaultData, vaultAddress);
514
+ vault.state = SpvVault_1.SpvVaultState.OPENED;
515
+ recoveredVaults.push(vault);
516
+ if (await this.bitcoinRpc.isSpent(vaultData.getUtxo())) {
517
+ if (!this.bitcoin.isReady())
518
+ throw new Error("Bitcoin wallet is not ready, but is required to check wallet transactions!");
519
+ //The latest smart chain UTXO is spent, we need to check if we have some further transactions
520
+ // spending the vault UTXO in our wallet history
521
+ const btcTxBlock = await this.bitcoinRpc.getBlockHeader(btcTx.blockhash);
522
+ minimumBlockheight = minimumBlockheight == null
523
+ ? btcTxBlock.getHeight()
524
+ : Math.min(minimumBlockheight, btcTxBlock.getHeight());
525
+ }
526
+ }
527
+ if (minimumBlockheight != null) {
528
+ const txinMap = new Map();
529
+ const txs = await this.bitcoin.getWalletTransactions(minimumBlockheight);
530
+ txs.forEach(tx => {
531
+ tx.ins.forEach(txin => {
532
+ txinMap.set(txin.txid + ":" + txin.vout, tx);
533
+ });
534
+ });
535
+ for (let vault of recoveredVaults) {
536
+ let utxo = vault.data.getUtxo();
537
+ let btcTx;
538
+ do {
539
+ btcTx = txinMap.get(utxo);
540
+ if (btcTx != null) {
541
+ const withdrawalData = await chain.spvVaultContract.getWithdrawalData(btcTx);
542
+ vault.addWithdrawal(withdrawalData);
543
+ utxo = withdrawalData.getCreatedVaultUtxo();
544
+ }
545
+ } while (btcTx != null);
546
+ }
547
+ }
548
+ for (let vault of recoveredVaults) {
549
+ await this.saveVault(vault);
550
+ }
551
+ return recoveredVaults;
552
+ }
490
553
  }
491
554
  exports.SpvVaults = SpvVaults;
@@ -31,7 +31,8 @@ function expressHandlerWrapper(func) {
31
31
  await func(req, res);
32
32
  }
33
33
  catch (e) {
34
- expressHandlerWrapperLogger.error("Error in called function " + req.path + ": ", e);
34
+ if (!isDefinedRuntimeError(e) || e._httpStatus !== 200)
35
+ expressHandlerWrapperLogger.error("Error in called function " + req.path + ": ", e);
35
36
  let statusCode = 500;
36
37
  const obj = {
37
38
  code: 0,
@@ -23,6 +23,7 @@ export declare abstract class IBitcoinWallet {
23
23
  readonly network: BTC_NETWORK;
24
24
  protected constructor(network: BTC_NETWORK);
25
25
  toOutputScript(address: string): Buffer;
26
+ fromOutputScript(outputScript: Buffer): string;
26
27
  getSignedTransaction(destination: string, amount: number, feeRate?: number, nonce?: bigint, maxAllowedFeeRate?: number): Promise<SignPsbtResponse>;
27
28
  getSignedMultiTransaction(destinations: {
28
29
  address: string;
@@ -25,6 +25,9 @@ class IBitcoinWallet {
25
25
  }
26
26
  throw new Error("Unrecognized address type");
27
27
  }
28
+ fromOutputScript(outputScript) {
29
+ return (0, btc_signer_1.Address)(this.network).encode(btc_signer_1.OutScript.decode(outputScript));
30
+ }
28
31
  getSignedTransaction(destination, amount, feeRate, nonce, maxAllowedFeeRate) {
29
32
  return this.getSignedMultiTransaction([{ address: destination, amount }], feeRate, nonce, maxAllowedFeeRate);
30
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "17.0.1",
3
+ "version": "17.0.3",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -152,8 +152,12 @@ export class SpvVault<
152
152
  }
153
153
  }
154
154
 
155
+ static _getIdentifier(chainId: string, data: SpvVaultData): string {
156
+ return chainId+"_"+data.getOwner()+"_"+data.getVaultId().toString(10);
157
+ }
158
+
155
159
  getIdentifier(): string {
156
- return this.chainId+"_"+this.data.getOwner()+"_"+this.data.getVaultId().toString(10);
160
+ return SpvVault._getIdentifier(this.chainId, this.data);
157
161
  }
158
162
 
159
163
  /**
@@ -1,11 +1,12 @@
1
1
  import {SpvVault, SpvVaultState} from "./SpvVault";
2
2
  import {
3
- BitcoinRpc,
3
+ BitcoinRpc, BtcBlock, BtcTx,
4
4
  IStorageManager,
5
5
  SpvVaultClaimEvent,
6
6
  SpvVaultCloseEvent,
7
7
  SpvVaultDepositEvent,
8
- SpvVaultOpenEvent, SpvWithdrawalTransactionData
8
+ SpvVaultOpenEvent,
9
+ SpvWithdrawalTransactionData
9
10
  } from "@atomiqlabs/base";
10
11
  import {SpvVaultSwap} from "./SpvVaultSwap";
11
12
  import {bigIntSorter, getLogger} from "../../utils/Utils";
@@ -27,7 +28,7 @@ export class SpvVaults {
27
28
 
28
29
  readonly bitcoin: IBitcoinWallet;
29
30
  readonly vaultSigner: ISpvVaultSigner;
30
- readonly bitcoinRpc: BitcoinRpc<any>;
31
+ readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
31
32
  readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
32
33
  readonly chains: MultichainData;
33
34
 
@@ -565,4 +566,74 @@ export class SpvVaults {
565
566
  const vaults = await this.vaultStorage.loadData(SpvVault);
566
567
  }
567
568
 
569
+ /**
570
+ * Recovers already created vaults for a given chain from on-chain data. Requires initialized BTC wallet to
571
+ * fetch wallet transactions
572
+ *
573
+ * @param chainId
574
+ */
575
+ async recoverVaults(chainId: string): Promise<SpvVault[]> {
576
+ const chain = this.chains.chains[chainId];
577
+ if(chainId==null) throw new Error(`Chain ${chainId} not found in known chains!`);
578
+ const vaults = await chain.spvVaultContract.getAllVaults(chain.signer.getAddress());
579
+
580
+ const recoveredVaults: SpvVault[] = [];
581
+ let minimumBlockheight = null;
582
+
583
+ for(let vaultData of vaults) {
584
+ const vaultIdentifier = SpvVault._getIdentifier(chainId, vaultData);
585
+ if(this.vaultStorage.data[vaultIdentifier]!=null) {
586
+ this.logger.info(`recoverVaults(${chainId}): Skipping vault ${vaultIdentifier}, because it is already known!`);
587
+ continue;
588
+ }
589
+ const [txId, voutStr] = vaultData.getUtxo().split(":");
590
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
591
+ const btcTxOutput = btcTx.outs[parseInt(voutStr)];
592
+ const vaultAddress = this.bitcoin.fromOutputScript(Buffer.from(btcTxOutput.scriptPubKey.hex, "hex"));
593
+ const vault = new SpvVault(chainId, vaultData, vaultAddress);
594
+ vault.state = SpvVaultState.OPENED;
595
+ recoveredVaults.push(vault);
596
+ if(await this.bitcoinRpc.isSpent(vaultData.getUtxo())) {
597
+ if(!this.bitcoin.isReady())
598
+ throw new Error("Bitcoin wallet is not ready, but is required to check wallet transactions!");
599
+
600
+ //The latest smart chain UTXO is spent, we need to check if we have some further transactions
601
+ // spending the vault UTXO in our wallet history
602
+ const btcTxBlock = await this.bitcoinRpc.getBlockHeader(btcTx.blockhash);
603
+ minimumBlockheight = minimumBlockheight==null
604
+ ? btcTxBlock.getHeight()
605
+ : Math.min(minimumBlockheight, btcTxBlock.getHeight());
606
+ }
607
+ }
608
+
609
+ if(minimumBlockheight!=null) {
610
+ const txinMap = new Map<string, BtcTx>();
611
+ const txs = await this.bitcoin.getWalletTransactions(minimumBlockheight);
612
+ txs.forEach(tx => {
613
+ tx.ins.forEach(txin => {
614
+ txinMap.set(txin.txid+":"+txin.vout, tx);
615
+ })
616
+ });
617
+
618
+ for(let vault of recoveredVaults) {
619
+ let utxo = vault.data.getUtxo();
620
+ let btcTx: BtcTx;
621
+ do {
622
+ btcTx = txinMap.get(utxo);
623
+ if(btcTx!=null) {
624
+ const withdrawalData = await chain.spvVaultContract.getWithdrawalData(btcTx);
625
+ vault.addWithdrawal(withdrawalData);
626
+ utxo = withdrawalData.getCreatedVaultUtxo();
627
+ }
628
+ } while(btcTx!=null);
629
+ }
630
+ }
631
+
632
+ for(let vault of recoveredVaults) {
633
+ await this.saveVault(vault);
634
+ }
635
+
636
+ return recoveredVaults;
637
+ }
638
+
568
639
  }
@@ -53,7 +53,9 @@ export function expressHandlerWrapper(func: (
53
53
  try {
54
54
  await func(req, res);
55
55
  } catch (e) {
56
- expressHandlerWrapperLogger.error("Error in called function "+req.path+": ", e);
56
+ if(!isDefinedRuntimeError(e) || e._httpStatus!==200)
57
+ expressHandlerWrapperLogger.error("Error in called function "+req.path+": ", e);
58
+
57
59
  let statusCode = 500;
58
60
  const obj: {code: number, msg: string, data?: any} = {
59
61
  code: 0,
@@ -49,6 +49,10 @@ export abstract class IBitcoinWallet {
49
49
  throw new Error("Unrecognized address type");
50
50
  }
51
51
 
52
+ fromOutputScript(outputScript: Buffer): string {
53
+ return Address(this.network).encode(OutScript.decode(outputScript));
54
+ }
55
+
52
56
  getSignedTransaction(destination: string, amount: number, feeRate?: number, nonce?: bigint, maxAllowedFeeRate?: number): Promise<SignPsbtResponse> {
53
57
  return this.getSignedMultiTransaction([{address: destination, amount}], feeRate, nonce, maxAllowedFeeRate);
54
58
  }