@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.
- package/dist/swaps/spv_vault_swap/SpvVault.d.ts +1 -0
- package/dist/swaps/spv_vault_swap/SpvVault.js +4 -1
- package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +9 -2
- package/dist/swaps/spv_vault_swap/SpvVaults.js +63 -0
- package/dist/utils/Utils.js +2 -1
- package/dist/wallets/IBitcoinWallet.d.ts +1 -0
- package/dist/wallets/IBitcoinWallet.js +3 -0
- package/package.json +1 -1
- package/src/swaps/spv_vault_swap/SpvVault.ts +5 -1
- package/src/swaps/spv_vault_swap/SpvVaults.ts +74 -3
- package/src/utils/Utils.ts +3 -1
- package/src/wallets/IBitcoinWallet.ts +4 -0
|
@@ -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
|
|
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<
|
|
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;
|
package/dist/utils/Utils.js
CHANGED
|
@@ -31,7 +31,8 @@ function expressHandlerWrapper(func) {
|
|
|
31
31
|
await func(req, res);
|
|
32
32
|
}
|
|
33
33
|
catch (e) {
|
|
34
|
-
|
|
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
|
@@ -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
|
|
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,
|
|
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<
|
|
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
|
}
|
package/src/utils/Utils.ts
CHANGED
|
@@ -53,7 +53,9 @@ export function expressHandlerWrapper(func: (
|
|
|
53
53
|
try {
|
|
54
54
|
await func(req, res);
|
|
55
55
|
} catch (e) {
|
|
56
|
-
|
|
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
|
}
|