@atomiqlabs/chain-evm 1.0.0-dev.89 → 1.0.0-dev.93
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/LICENSE +201 -201
- package/dist/chains/botanix/BotanixChainType.d.ts +13 -13
- package/dist/chains/botanix/BotanixChainType.js +2 -2
- package/dist/chains/botanix/BotanixInitializer.d.ts +30 -30
- package/dist/chains/botanix/BotanixInitializer.js +125 -122
- package/dist/chains/citrea/CitreaBtcRelay.d.ts +21 -21
- package/dist/chains/citrea/CitreaBtcRelay.js +43 -43
- package/dist/chains/citrea/CitreaChainType.d.ts +13 -13
- package/dist/chains/citrea/CitreaChainType.js +2 -2
- package/dist/chains/citrea/CitreaFees.d.ts +29 -29
- package/dist/chains/citrea/CitreaFees.js +67 -67
- package/dist/chains/citrea/CitreaInitializer.d.ts +30 -30
- package/dist/chains/citrea/CitreaInitializer.js +132 -129
- package/dist/chains/citrea/CitreaSpvVaultContract.d.ts +15 -15
- package/dist/chains/citrea/CitreaSpvVaultContract.js +74 -74
- package/dist/chains/citrea/CitreaSwapContract.d.ts +22 -22
- package/dist/chains/citrea/CitreaSwapContract.js +96 -96
- package/dist/chains/citrea/CitreaTokens.d.ts +9 -9
- package/dist/chains/citrea/CitreaTokens.js +20 -20
- package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -198
- package/dist/evm/btcrelay/BtcRelayAbi.js +261 -261
- package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -172
- package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -2
- package/dist/evm/btcrelay/EVMBtcRelay.d.ts +197 -197
- package/dist/evm/btcrelay/EVMBtcRelay.js +435 -435
- package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -33
- package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -84
- package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -56
- package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -123
- package/dist/evm/chain/EVMChainInterface.d.ts +54 -51
- package/dist/evm/chain/EVMChainInterface.js +89 -89
- package/dist/evm/chain/EVMModule.d.ts +9 -9
- package/dist/evm/chain/EVMModule.js +13 -13
- package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -168
- package/dist/evm/chain/modules/ERC20Abi.js +225 -225
- package/dist/evm/chain/modules/EVMAddresses.d.ts +10 -10
- package/dist/evm/chain/modules/EVMAddresses.js +30 -30
- package/dist/evm/chain/modules/EVMBlocks.d.ts +20 -20
- package/dist/evm/chain/modules/EVMBlocks.js +64 -64
- package/dist/evm/chain/modules/EVMEvents.d.ts +46 -46
- package/dist/evm/chain/modules/EVMEvents.js +151 -137
- package/dist/evm/chain/modules/EVMFees.d.ts +36 -36
- package/dist/evm/chain/modules/EVMFees.js +74 -74
- package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -29
- package/dist/evm/chain/modules/EVMSignatures.js +68 -68
- package/dist/evm/chain/modules/EVMTokens.d.ts +70 -70
- package/dist/evm/chain/modules/EVMTokens.js +142 -142
- package/dist/evm/chain/modules/EVMTransactions.d.ts +94 -94
- package/dist/evm/chain/modules/EVMTransactions.js +288 -286
- package/dist/evm/contract/EVMContractBase.d.ts +22 -22
- package/dist/evm/contract/EVMContractBase.js +34 -34
- package/dist/evm/contract/EVMContractModule.d.ts +8 -8
- package/dist/evm/contract/EVMContractModule.js +11 -11
- package/dist/evm/contract/modules/EVMContractEvents.d.ts +42 -42
- package/dist/evm/contract/modules/EVMContractEvents.js +75 -75
- package/dist/evm/events/EVMChainEvents.d.ts +22 -22
- package/dist/evm/events/EVMChainEvents.js +69 -69
- package/dist/evm/events/EVMChainEventsBrowser.d.ts +102 -102
- package/dist/evm/events/EVMChainEventsBrowser.js +412 -412
- package/dist/evm/providers/JsonRpcProviderWithRetries.d.ts +16 -16
- package/dist/evm/providers/JsonRpcProviderWithRetries.js +27 -27
- package/dist/evm/providers/ReconnectingWebSocketProvider.d.ts +22 -22
- package/dist/evm/providers/ReconnectingWebSocketProvider.js +91 -91
- package/dist/evm/providers/SocketProvider.d.ts +111 -111
- package/dist/evm/providers/SocketProvider.js +336 -336
- package/dist/evm/providers/WebSocketProviderWithRetries.d.ts +17 -17
- package/dist/evm/providers/WebSocketProviderWithRetries.js +23 -23
- package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +107 -79
- package/dist/evm/spv_swap/EVMSpvVaultContract.js +578 -482
- package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +40 -39
- package/dist/evm/spv_swap/EVMSpvVaultData.js +184 -180
- package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -19
- package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -55
- package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -91
- package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -849
- package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -450
- package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -2
- package/dist/evm/swaps/EVMSwapContract.d.ts +199 -193
- package/dist/evm/swaps/EVMSwapContract.js +394 -378
- package/dist/evm/swaps/EVMSwapData.d.ts +66 -66
- package/dist/evm/swaps/EVMSwapData.js +260 -260
- package/dist/evm/swaps/EVMSwapModule.d.ts +9 -9
- package/dist/evm/swaps/EVMSwapModule.js +11 -11
- package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -120
- package/dist/evm/swaps/EscrowManagerAbi.js +985 -985
- package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -475
- package/dist/evm/swaps/EscrowManagerTypechain.js +2 -2
- package/dist/evm/swaps/handlers/IHandler.d.ts +13 -13
- package/dist/evm/swaps/handlers/IHandler.js +2 -2
- package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -10
- package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -13
- package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -20
- package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -39
- package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -24
- package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -59
- package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -25
- package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -51
- package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -21
- package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -28
- package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -48
- package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -63
- package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -17
- package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -28
- package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -69
- package/dist/evm/swaps/modules/EVMLpVault.js +134 -134
- package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +54 -54
- package/dist/evm/swaps/modules/EVMSwapClaim.js +137 -137
- package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -88
- package/dist/evm/swaps/modules/EVMSwapInit.js +274 -274
- package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -62
- package/dist/evm/swaps/modules/EVMSwapRefund.js +167 -167
- package/dist/evm/typechain/common.d.ts +50 -50
- package/dist/evm/typechain/common.js +2 -2
- package/dist/evm/wallet/EVMBrowserSigner.d.ts +5 -5
- package/dist/evm/wallet/EVMBrowserSigner.js +11 -11
- package/dist/evm/wallet/EVMPersistentSigner.d.ts +29 -29
- package/dist/evm/wallet/EVMPersistentSigner.js +230 -230
- package/dist/evm/wallet/EVMSigner.d.ts +11 -11
- package/dist/evm/wallet/EVMSigner.js +24 -24
- package/dist/index.d.ts +44 -44
- package/dist/index.js +60 -60
- package/dist/utils/Utils.d.ts +19 -19
- package/dist/utils/Utils.js +98 -98
- package/package.json +39 -39
- package/src/chains/botanix/BotanixChainType.ts +28 -28
- package/src/chains/botanix/BotanixInitializer.ts +175 -171
- package/src/chains/citrea/CitreaBtcRelay.ts +57 -57
- package/src/chains/citrea/CitreaChainType.ts +28 -28
- package/src/chains/citrea/CitreaFees.ts +77 -77
- package/src/chains/citrea/CitreaInitializer.ts +182 -178
- package/src/chains/citrea/CitreaSpvVaultContract.ts +75 -75
- package/src/chains/citrea/CitreaSwapContract.ts +102 -102
- package/src/chains/citrea/CitreaTokens.ts +21 -21
- package/src/evm/btcrelay/BtcRelayAbi.ts +258 -258
- package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -371
- package/src/evm/btcrelay/EVMBtcRelay.ts +537 -537
- package/src/evm/btcrelay/headers/EVMBtcHeader.ts +109 -109
- package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +152 -152
- package/src/evm/chain/EVMChainInterface.ts +158 -155
- package/src/evm/chain/EVMModule.ts +21 -21
- package/src/evm/chain/modules/ERC20Abi.ts +222 -222
- package/src/evm/chain/modules/EVMAddresses.ts +28 -28
- package/src/evm/chain/modules/EVMBlocks.ts +75 -75
- package/src/evm/chain/modules/EVMEvents.ts +182 -156
- package/src/evm/chain/modules/EVMFees.ts +104 -104
- package/src/evm/chain/modules/EVMSignatures.ts +76 -76
- package/src/evm/chain/modules/EVMTokens.ts +155 -155
- package/src/evm/chain/modules/EVMTransactions.ts +327 -325
- package/src/evm/contract/EVMContractBase.ts +63 -63
- package/src/evm/contract/EVMContractModule.ts +16 -16
- package/src/evm/contract/modules/EVMContractEvents.ts +102 -102
- package/src/evm/events/EVMChainEvents.ts +82 -82
- package/src/evm/events/EVMChainEventsBrowser.ts +533 -533
- package/src/evm/providers/JsonRpcProviderWithRetries.ts +33 -33
- package/src/evm/providers/ReconnectingWebSocketProvider.ts +106 -106
- package/src/evm/providers/SocketProvider.ts +371 -371
- package/src/evm/providers/WebSocketProviderWithRetries.ts +34 -34
- package/src/evm/spv_swap/EVMSpvVaultContract.ts +723 -615
- package/src/evm/spv_swap/EVMSpvVaultData.ts +228 -224
- package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -70
- package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -846
- package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -685
- package/src/evm/swaps/EVMSwapContract.ts +621 -600
- package/src/evm/swaps/EVMSwapData.ts +378 -378
- package/src/evm/swaps/EVMSwapModule.ts +16 -16
- package/src/evm/swaps/EscrowManagerAbi.ts +982 -982
- package/src/evm/swaps/EscrowManagerTypechain.ts +723 -723
- package/src/evm/swaps/handlers/IHandler.ts +17 -17
- package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -20
- package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +46 -46
- package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -82
- package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -76
- package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -46
- package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -115
- package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +37 -37
- package/src/evm/swaps/modules/EVMLpVault.ts +154 -154
- package/src/evm/swaps/modules/EVMSwapClaim.ts +172 -172
- package/src/evm/swaps/modules/EVMSwapInit.ts +328 -328
- package/src/evm/swaps/modules/EVMSwapRefund.ts +229 -229
- package/src/evm/typechain/common.ts +131 -131
- package/src/evm/wallet/EVMBrowserSigner.ts +11 -11
- package/src/evm/wallet/EVMPersistentSigner.ts +307 -307
- package/src/evm/wallet/EVMSigner.ts +31 -31
- package/src/index.ts +53 -53
- package/src/utils/Utils.ts +111 -111
|
@@ -1,615 +1,723 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BigIntBufferUtils,
|
|
3
|
-
BitcoinRpc,
|
|
4
|
-
BtcTx,
|
|
5
|
-
RelaySynchronizer,
|
|
6
|
-
SpvVaultContract,
|
|
7
|
-
SpvVaultTokenData,
|
|
8
|
-
SpvWithdrawalState,
|
|
9
|
-
SpvWithdrawalStateType,
|
|
10
|
-
SpvWithdrawalTransactionData,
|
|
11
|
-
TransactionConfirmationOptions
|
|
12
|
-
} from "@atomiqlabs/base";
|
|
13
|
-
import {Buffer} from "buffer";
|
|
14
|
-
import { EVMTx } from "../chain/modules/EVMTransactions";
|
|
15
|
-
import { EVMContractBase } from "../contract/EVMContractBase";
|
|
16
|
-
import { EVMSigner } from "../wallet/EVMSigner";
|
|
17
|
-
import {SpvVaultContractAbi} from "./SpvVaultContractAbi";
|
|
18
|
-
import {SpvVaultManager, SpvVaultParametersStructOutput} from "./SpvVaultContractTypechain";
|
|
19
|
-
import {EVMBtcRelay} from "../btcrelay/EVMBtcRelay";
|
|
20
|
-
import {getLogger} from "../../utils/Utils";
|
|
21
|
-
import {EVMChainInterface} from "../chain/EVMChainInterface";
|
|
22
|
-
import {AbiCoder, getAddress, hexlify, keccak256, TransactionRequest, ZeroAddress, ZeroHash} from "ethers";
|
|
23
|
-
import {EVMAddresses} from "../chain/modules/EVMAddresses";
|
|
24
|
-
import {EVMSpvVaultData, getVaultParamsCommitment} from "./EVMSpvVaultData";
|
|
25
|
-
import {EVMSpvWithdrawalData} from "./EVMSpvWithdrawalData";
|
|
26
|
-
import {EVMFees} from "../chain/modules/EVMFees";
|
|
27
|
-
import {EVMBtcStoredHeader} from "../btcrelay/headers/EVMBtcStoredHeader";
|
|
28
|
-
import {TypedEventLog} from "../typechain/common";
|
|
29
|
-
import {PromiseLruCache} from "promise-cache-ts";
|
|
30
|
-
|
|
31
|
-
function decodeUtxo(utxo: string): {txHash: string, vout: bigint} {
|
|
32
|
-
const [txId, vout] = utxo.split(":");
|
|
33
|
-
return {
|
|
34
|
-
txHash: "0x"+Buffer.from(txId, "hex").reverse().toString("hex"),
|
|
35
|
-
vout: BigInt(vout)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function packOwnerAndVaultId(owner: string, vaultId: bigint): string {
|
|
40
|
-
if(owner.length!==42) throw new Error("Invalid owner address");
|
|
41
|
-
return owner.toLowerCase() + BigIntBufferUtils.toBuffer(vaultId, "be", 12).toString("hex");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function unpackOwnerAndVaultId(data: string): [string, bigint] {
|
|
45
|
-
return [getAddress(data.substring(0, 42)), BigInt("0x"+data.substring(42, 66))];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class EVMSpvVaultContract<ChainId extends string>
|
|
49
|
-
extends EVMContractBase<SpvVaultManager>
|
|
50
|
-
implements SpvVaultContract<
|
|
51
|
-
EVMTx,
|
|
52
|
-
EVMSigner,
|
|
53
|
-
ChainId,
|
|
54
|
-
EVMSpvVaultData,
|
|
55
|
-
EVMSpvWithdrawalData
|
|
56
|
-
>
|
|
57
|
-
{
|
|
58
|
-
public static readonly GasCosts = {
|
|
59
|
-
DEPOSIT_BASE: 15_000 + 21_000,
|
|
60
|
-
DEPOSIT_ERC20: 40_000,
|
|
61
|
-
|
|
62
|
-
OPEN: 80_000 + 21_000,
|
|
63
|
-
|
|
64
|
-
CLAIM_BASE: 85_000 + 21_000,
|
|
65
|
-
CLAIM_NATIVE_TRANSFER: 35_000,
|
|
66
|
-
CLAIM_ERC20_TRANSFER: 40_000,
|
|
67
|
-
CLAIM_EXECUTION_SCHEDULE: 30_000,
|
|
68
|
-
|
|
69
|
-
FRONT_BASE: 75_000 + 21_000,
|
|
70
|
-
FRONT_NATIVE_TRANSFER: 35_000,
|
|
71
|
-
FRONT_ERC20_TRANSFER: 40_000,
|
|
72
|
-
FRONT_EXECUTION_SCHEDULE: 30_000
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
readonly chainId: ChainId;
|
|
76
|
-
|
|
77
|
-
readonly btcRelay: EVMBtcRelay<any>;
|
|
78
|
-
readonly bitcoinRpc: BitcoinRpc<any>;
|
|
79
|
-
readonly claimTimeout: number = 180;
|
|
80
|
-
|
|
81
|
-
readonly logger = getLogger("EVMSpvVaultContract: ");
|
|
82
|
-
|
|
83
|
-
constructor(
|
|
84
|
-
chainInterface: EVMChainInterface<ChainId>,
|
|
85
|
-
btcRelay: EVMBtcRelay<any>,
|
|
86
|
-
bitcoinRpc: BitcoinRpc<any>,
|
|
87
|
-
contractAddress: string,
|
|
88
|
-
contractDeploymentHeight?: number
|
|
89
|
-
) {
|
|
90
|
-
super(chainInterface, contractAddress, SpvVaultContractAbi, contractDeploymentHeight);
|
|
91
|
-
this.btcRelay = btcRelay;
|
|
92
|
-
this.bitcoinRpc = bitcoinRpc;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//Transactions
|
|
96
|
-
protected async Open(signer: string, vault: EVMSpvVaultData, feeRate: string): Promise<TransactionRequest> {
|
|
97
|
-
const {txHash, vout} = decodeUtxo(vault.getUtxo());
|
|
98
|
-
|
|
99
|
-
const tokens = vault.getTokenData();
|
|
100
|
-
if(tokens.length!==2) throw new Error("Must specify exactly 2 tokens for vault!");
|
|
101
|
-
|
|
102
|
-
const tx = await this.contract.open.populateTransaction(vault.vaultId, vault.getVaultParamsStruct(), txHash, vout);
|
|
103
|
-
tx.from = signer;
|
|
104
|
-
EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.OPEN, feeRate);
|
|
105
|
-
|
|
106
|
-
return tx;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
protected async Deposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate: string): Promise<TransactionRequest> {
|
|
110
|
-
let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
|
|
111
|
-
let value = 0n;
|
|
112
|
-
if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
113
|
-
value += rawAmounts[0] * vault.token0.multiplier;
|
|
114
|
-
} else {
|
|
115
|
-
if(rawAmounts[0] > 0n) totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
|
|
116
|
-
}
|
|
117
|
-
if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
118
|
-
value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
|
|
119
|
-
} else {
|
|
120
|
-
if(rawAmounts[1]!=null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase()!==vault.token1.token.toLowerCase())
|
|
121
|
-
totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const tx = await this.contract.deposit.populateTransaction(
|
|
125
|
-
vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
|
|
126
|
-
rawAmounts[0], rawAmounts[1] ?? 0n, { value }
|
|
127
|
-
);
|
|
128
|
-
tx.from = signer;
|
|
129
|
-
EVMFees.applyFeeRate(tx, totalGas, feeRate);
|
|
130
|
-
|
|
131
|
-
return tx;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
protected async Front(
|
|
135
|
-
signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData, withdrawalSequence: number, feeRate: string
|
|
136
|
-
): Promise<TransactionRequest> {
|
|
137
|
-
let value = 0n;
|
|
138
|
-
const frontingAmount = data.getFrontingAmount();
|
|
139
|
-
if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
|
|
140
|
-
value += frontingAmount[0] * vault.token0.multiplier;
|
|
141
|
-
if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
|
|
142
|
-
value += (frontingAmount[1] ?? 0n) * vault.token1.multiplier;
|
|
143
|
-
|
|
144
|
-
const tx = await this.contract.front.populateTransaction(
|
|
145
|
-
vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
|
|
146
|
-
withdrawalSequence, data.getTxHash(), data.serializeToStruct(),
|
|
147
|
-
{ value }
|
|
148
|
-
);
|
|
149
|
-
tx.from = signer;
|
|
150
|
-
EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
|
|
151
|
-
|
|
152
|
-
return tx;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
protected async Claim(
|
|
156
|
-
signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData,
|
|
157
|
-
blockheader: EVMBtcStoredHeader, merkle: Buffer[], position: number, feeRate: string
|
|
158
|
-
): Promise<TransactionRequest> {
|
|
159
|
-
const tx = await this.contract.claim.populateTransaction(
|
|
160
|
-
vault.owner, vault.vaultId, vault.getVaultParamsStruct(), "0x"+data.btcTx.hex,
|
|
161
|
-
blockheader.serializeToStruct(), merkle, position
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
tx.from = signer;
|
|
165
|
-
EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
|
|
166
|
-
|
|
167
|
-
return tx;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void> {
|
|
171
|
-
const result = await this.contract.parseBitcoinTx(Buffer.from(tx.btcTx.hex, "hex"));
|
|
172
|
-
if(result==null) throw new Error("Failed to parse transaction!");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
createVaultData(
|
|
176
|
-
owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]
|
|
177
|
-
): Promise<EVMSpvVaultData> {
|
|
178
|
-
if(tokenData.length!==2) throw new Error("Must specify 2 tokens in tokenData!");
|
|
179
|
-
|
|
180
|
-
const vaultParams = {
|
|
181
|
-
btcRelayContract: this.btcRelay.contractAddress,
|
|
182
|
-
token0: tokenData[0].token,
|
|
183
|
-
token1: tokenData[1].token,
|
|
184
|
-
token0Multiplier: tokenData[0].multiplier,
|
|
185
|
-
token1Multiplier: tokenData[1].multiplier,
|
|
186
|
-
confirmations: BigInt(confirmations)
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const spvVaultParametersCommitment = keccak256(AbiCoder.defaultAbiCoder().encode(
|
|
190
|
-
["address", "address", "address", "uint192", "uint192", "uint256"],
|
|
191
|
-
[vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]
|
|
192
|
-
));
|
|
193
|
-
|
|
194
|
-
return Promise.resolve(new EVMSpvVaultData(owner, vaultId, {
|
|
195
|
-
spvVaultParametersCommitment,
|
|
196
|
-
utxoTxHash: ZeroHash,
|
|
197
|
-
utxoVout: 0n,
|
|
198
|
-
openBlockheight: 0n,
|
|
199
|
-
withdrawCount: 0n,
|
|
200
|
-
depositCount: 0n,
|
|
201
|
-
token0Amount: 0n,
|
|
202
|
-
token1Amount: 0n
|
|
203
|
-
}, vaultParams, utxo));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
//Getters
|
|
207
|
-
async getFronterAddress(owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData): Promise<string> {
|
|
208
|
-
const frontingAddress = await this.contract.getFronterById(owner, vaultId, "0x"+withdrawal.getFrontingId());
|
|
209
|
-
if(frontingAddress===ZeroAddress) return null;
|
|
210
|
-
return frontingAddress;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
[
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
]
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async
|
|
394
|
-
const result
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if(rawAmounts
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
let
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
if
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1
|
+
import {
|
|
2
|
+
BigIntBufferUtils,
|
|
3
|
+
BitcoinRpc,
|
|
4
|
+
BtcTx,
|
|
5
|
+
RelaySynchronizer,
|
|
6
|
+
SpvVaultContract,
|
|
7
|
+
SpvVaultTokenData,
|
|
8
|
+
SpvWithdrawalState,
|
|
9
|
+
SpvWithdrawalStateType,
|
|
10
|
+
SpvWithdrawalTransactionData,
|
|
11
|
+
TransactionConfirmationOptions
|
|
12
|
+
} from "@atomiqlabs/base";
|
|
13
|
+
import {Buffer} from "buffer";
|
|
14
|
+
import { EVMTx } from "../chain/modules/EVMTransactions";
|
|
15
|
+
import { EVMContractBase } from "../contract/EVMContractBase";
|
|
16
|
+
import { EVMSigner } from "../wallet/EVMSigner";
|
|
17
|
+
import {SpvVaultContractAbi} from "./SpvVaultContractAbi";
|
|
18
|
+
import {SpvVaultManager, SpvVaultParametersStructOutput} from "./SpvVaultContractTypechain";
|
|
19
|
+
import {EVMBtcRelay} from "../btcrelay/EVMBtcRelay";
|
|
20
|
+
import {getLogger} from "../../utils/Utils";
|
|
21
|
+
import {EVMChainInterface} from "../chain/EVMChainInterface";
|
|
22
|
+
import {AbiCoder, getAddress, hexlify, keccak256, TransactionRequest, ZeroAddress, ZeroHash} from "ethers";
|
|
23
|
+
import {EVMAddresses} from "../chain/modules/EVMAddresses";
|
|
24
|
+
import {EVMSpvVaultData, getVaultParamsCommitment, getVaultUtxoFromState} from "./EVMSpvVaultData";
|
|
25
|
+
import {EVMSpvWithdrawalData} from "./EVMSpvWithdrawalData";
|
|
26
|
+
import {EVMFees} from "../chain/modules/EVMFees";
|
|
27
|
+
import {EVMBtcStoredHeader} from "../btcrelay/headers/EVMBtcStoredHeader";
|
|
28
|
+
import {TypedEventLog} from "../typechain/common";
|
|
29
|
+
import {PromiseLruCache} from "promise-cache-ts";
|
|
30
|
+
|
|
31
|
+
function decodeUtxo(utxo: string): {txHash: string, vout: bigint} {
|
|
32
|
+
const [txId, vout] = utxo.split(":");
|
|
33
|
+
return {
|
|
34
|
+
txHash: "0x"+Buffer.from(txId, "hex").reverse().toString("hex"),
|
|
35
|
+
vout: BigInt(vout)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function packOwnerAndVaultId(owner: string, vaultId: bigint): string {
|
|
40
|
+
if(owner.length!==42) throw new Error("Invalid owner address");
|
|
41
|
+
return owner.toLowerCase() + BigIntBufferUtils.toBuffer(vaultId, "be", 12).toString("hex");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function unpackOwnerAndVaultId(data: string): [string, bigint] {
|
|
45
|
+
return [getAddress(data.substring(0, 42)), BigInt("0x"+data.substring(42, 66))];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class EVMSpvVaultContract<ChainId extends string>
|
|
49
|
+
extends EVMContractBase<SpvVaultManager>
|
|
50
|
+
implements SpvVaultContract<
|
|
51
|
+
EVMTx,
|
|
52
|
+
EVMSigner,
|
|
53
|
+
ChainId,
|
|
54
|
+
EVMSpvVaultData,
|
|
55
|
+
EVMSpvWithdrawalData
|
|
56
|
+
>
|
|
57
|
+
{
|
|
58
|
+
public static readonly GasCosts = {
|
|
59
|
+
DEPOSIT_BASE: 15_000 + 21_000,
|
|
60
|
+
DEPOSIT_ERC20: 40_000,
|
|
61
|
+
|
|
62
|
+
OPEN: 80_000 + 21_000,
|
|
63
|
+
|
|
64
|
+
CLAIM_BASE: 85_000 + 21_000,
|
|
65
|
+
CLAIM_NATIVE_TRANSFER: 35_000,
|
|
66
|
+
CLAIM_ERC20_TRANSFER: 40_000,
|
|
67
|
+
CLAIM_EXECUTION_SCHEDULE: 30_000,
|
|
68
|
+
|
|
69
|
+
FRONT_BASE: 75_000 + 21_000,
|
|
70
|
+
FRONT_NATIVE_TRANSFER: 35_000,
|
|
71
|
+
FRONT_ERC20_TRANSFER: 40_000,
|
|
72
|
+
FRONT_EXECUTION_SCHEDULE: 30_000
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
readonly chainId: ChainId;
|
|
76
|
+
|
|
77
|
+
readonly btcRelay: EVMBtcRelay<any>;
|
|
78
|
+
readonly bitcoinRpc: BitcoinRpc<any>;
|
|
79
|
+
readonly claimTimeout: number = 180;
|
|
80
|
+
|
|
81
|
+
readonly logger = getLogger("EVMSpvVaultContract: ");
|
|
82
|
+
|
|
83
|
+
constructor(
|
|
84
|
+
chainInterface: EVMChainInterface<ChainId>,
|
|
85
|
+
btcRelay: EVMBtcRelay<any>,
|
|
86
|
+
bitcoinRpc: BitcoinRpc<any>,
|
|
87
|
+
contractAddress: string,
|
|
88
|
+
contractDeploymentHeight?: number
|
|
89
|
+
) {
|
|
90
|
+
super(chainInterface, contractAddress, SpvVaultContractAbi, contractDeploymentHeight);
|
|
91
|
+
this.btcRelay = btcRelay;
|
|
92
|
+
this.bitcoinRpc = bitcoinRpc;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//Transactions
|
|
96
|
+
protected async Open(signer: string, vault: EVMSpvVaultData, feeRate: string): Promise<TransactionRequest> {
|
|
97
|
+
const {txHash, vout} = decodeUtxo(vault.getUtxo());
|
|
98
|
+
|
|
99
|
+
const tokens = vault.getTokenData();
|
|
100
|
+
if(tokens.length!==2) throw new Error("Must specify exactly 2 tokens for vault!");
|
|
101
|
+
|
|
102
|
+
const tx = await this.contract.open.populateTransaction(vault.vaultId, vault.getVaultParamsStruct(), txHash, vout);
|
|
103
|
+
tx.from = signer;
|
|
104
|
+
EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.OPEN, feeRate);
|
|
105
|
+
|
|
106
|
+
return tx;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected async Deposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate: string): Promise<TransactionRequest> {
|
|
110
|
+
let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
|
|
111
|
+
let value = 0n;
|
|
112
|
+
if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
113
|
+
value += rawAmounts[0] * vault.token0.multiplier;
|
|
114
|
+
} else {
|
|
115
|
+
if(rawAmounts[0] > 0n) totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
|
|
116
|
+
}
|
|
117
|
+
if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
118
|
+
value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
|
|
119
|
+
} else {
|
|
120
|
+
if(rawAmounts[1]!=null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase()!==vault.token1.token.toLowerCase())
|
|
121
|
+
totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const tx = await this.contract.deposit.populateTransaction(
|
|
125
|
+
vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
|
|
126
|
+
rawAmounts[0], rawAmounts[1] ?? 0n, { value }
|
|
127
|
+
);
|
|
128
|
+
tx.from = signer;
|
|
129
|
+
EVMFees.applyFeeRate(tx, totalGas, feeRate);
|
|
130
|
+
|
|
131
|
+
return tx;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected async Front(
|
|
135
|
+
signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData, withdrawalSequence: number, feeRate: string
|
|
136
|
+
): Promise<TransactionRequest> {
|
|
137
|
+
let value = 0n;
|
|
138
|
+
const frontingAmount = data.getFrontingAmount();
|
|
139
|
+
if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
|
|
140
|
+
value += frontingAmount[0] * vault.token0.multiplier;
|
|
141
|
+
if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
|
|
142
|
+
value += (frontingAmount[1] ?? 0n) * vault.token1.multiplier;
|
|
143
|
+
|
|
144
|
+
const tx = await this.contract.front.populateTransaction(
|
|
145
|
+
vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
|
|
146
|
+
withdrawalSequence, data.getTxHash(), data.serializeToStruct(),
|
|
147
|
+
{ value }
|
|
148
|
+
);
|
|
149
|
+
tx.from = signer;
|
|
150
|
+
EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
|
|
151
|
+
|
|
152
|
+
return tx;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected async Claim(
|
|
156
|
+
signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData,
|
|
157
|
+
blockheader: EVMBtcStoredHeader, merkle: Buffer[], position: number, feeRate: string
|
|
158
|
+
): Promise<TransactionRequest> {
|
|
159
|
+
const tx = await this.contract.claim.populateTransaction(
|
|
160
|
+
vault.owner, vault.vaultId, vault.getVaultParamsStruct(), "0x"+data.btcTx.hex,
|
|
161
|
+
blockheader.serializeToStruct(), merkle, position
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
tx.from = signer;
|
|
165
|
+
EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
|
|
166
|
+
|
|
167
|
+
return tx;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void> {
|
|
171
|
+
const result = await this.contract.parseBitcoinTx(Buffer.from(tx.btcTx.hex, "hex"));
|
|
172
|
+
if(result==null) throw new Error("Failed to parse transaction!");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
createVaultData(
|
|
176
|
+
owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]
|
|
177
|
+
): Promise<EVMSpvVaultData> {
|
|
178
|
+
if(tokenData.length!==2) throw new Error("Must specify 2 tokens in tokenData!");
|
|
179
|
+
|
|
180
|
+
const vaultParams = {
|
|
181
|
+
btcRelayContract: this.btcRelay.contractAddress,
|
|
182
|
+
token0: tokenData[0].token,
|
|
183
|
+
token1: tokenData[1].token,
|
|
184
|
+
token0Multiplier: tokenData[0].multiplier,
|
|
185
|
+
token1Multiplier: tokenData[1].multiplier,
|
|
186
|
+
confirmations: BigInt(confirmations)
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const spvVaultParametersCommitment = keccak256(AbiCoder.defaultAbiCoder().encode(
|
|
190
|
+
["address", "address", "address", "uint192", "uint192", "uint256"],
|
|
191
|
+
[vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]
|
|
192
|
+
));
|
|
193
|
+
|
|
194
|
+
return Promise.resolve(new EVMSpvVaultData(owner, vaultId, {
|
|
195
|
+
spvVaultParametersCommitment,
|
|
196
|
+
utxoTxHash: ZeroHash,
|
|
197
|
+
utxoVout: 0n,
|
|
198
|
+
openBlockheight: 0n,
|
|
199
|
+
withdrawCount: 0n,
|
|
200
|
+
depositCount: 0n,
|
|
201
|
+
token0Amount: 0n,
|
|
202
|
+
token1Amount: 0n
|
|
203
|
+
}, vaultParams, utxo));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//Getters
|
|
207
|
+
async getFronterAddress(owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData): Promise<string> {
|
|
208
|
+
const frontingAddress = await this.contract.getFronterById(owner, vaultId, "0x"+withdrawal.getFrontingId());
|
|
209
|
+
if(frontingAddress===ZeroAddress) return null;
|
|
210
|
+
return frontingAddress;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async getFronterAddresses(withdrawals: {owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData}[]): Promise<{[btcTxId: string]: string | null}> {
|
|
214
|
+
const result: {
|
|
215
|
+
[btcTxId: string]: string | null
|
|
216
|
+
} = {};
|
|
217
|
+
let promises: Promise<void>[] = [];
|
|
218
|
+
//TODO: We can upgrade this to use multicall
|
|
219
|
+
for(let {owner, vaultId, withdrawal} of withdrawals) {
|
|
220
|
+
promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
|
|
221
|
+
result[withdrawal.getTxId()] = val;
|
|
222
|
+
}));
|
|
223
|
+
if(promises.length>=this.Chain.config.maxParallelCalls) {
|
|
224
|
+
await Promise.all(promises);
|
|
225
|
+
promises = [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
await Promise.all(promises);
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private vaultParamsCache: PromiseLruCache<string, SpvVaultParametersStructOutput> = new PromiseLruCache<string, SpvVaultParametersStructOutput>(5000);
|
|
233
|
+
|
|
234
|
+
async getVaultData(owner: string, vaultId: bigint): Promise<EVMSpvVaultData> {
|
|
235
|
+
const vaultState = await this.contract.getVault(owner, vaultId);
|
|
236
|
+
|
|
237
|
+
const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
|
|
238
|
+
const blockheight = Number(vaultState.openBlockheight);
|
|
239
|
+
const events = await this.Events.getContractBlockEvents(
|
|
240
|
+
["Opened"],
|
|
241
|
+
[
|
|
242
|
+
"0x"+owner.substring(2).padStart(64, "0"),
|
|
243
|
+
hexlify(BigIntBufferUtils.toBuffer(vaultId, "be", 32))
|
|
244
|
+
],
|
|
245
|
+
blockheight
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const foundEvent = events.find(
|
|
249
|
+
event => getVaultParamsCommitment(event.args.params)===vaultState.spvVaultParametersCommitment
|
|
250
|
+
);
|
|
251
|
+
if(foundEvent==null) throw new Error("Valid open event not found!");
|
|
252
|
+
|
|
253
|
+
return foundEvent.args.params;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if(vaultParams.btcRelayContract.toLowerCase()!==this.btcRelay.contractAddress.toLowerCase()) return null;
|
|
257
|
+
|
|
258
|
+
return new EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async getMultipleVaultData(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: EVMSpvVaultData}}> {
|
|
262
|
+
const result: {[owner: string]: {[vaultId: string]: EVMSpvVaultData}} = {};
|
|
263
|
+
let promises: Promise<void>[] = [];
|
|
264
|
+
//TODO: We can upgrade this to use multicall
|
|
265
|
+
for(let {owner, vaultId} of vaults) {
|
|
266
|
+
promises.push(this.getVaultData(owner, vaultId).then(val => {
|
|
267
|
+
result[owner] ??= {};
|
|
268
|
+
result[owner][vaultId.toString(10)] = val;
|
|
269
|
+
}));
|
|
270
|
+
if(promises.length>=this.Chain.config.maxParallelCalls) {
|
|
271
|
+
await Promise.all(promises);
|
|
272
|
+
promises = [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
await Promise.all(promises);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getVaultLatestUtxo(owner: string, vaultId: bigint): Promise<string | null> {
|
|
280
|
+
const vaultState = await this.contract.getVault(owner, vaultId);
|
|
281
|
+
const utxo = getVaultUtxoFromState(vaultState);
|
|
282
|
+
if(utxo==="0000000000000000000000000000000000000000000000000000000000000000:0") return null;
|
|
283
|
+
return utxo;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async getVaultLatestUtxos(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: string | null}}> {
|
|
287
|
+
const result: {[owner: string]: {[vaultId: string]: string | null}} = {};
|
|
288
|
+
let promises: Promise<void>[] = [];
|
|
289
|
+
//TODO: We can upgrade this to use multicall
|
|
290
|
+
for(let {owner, vaultId} of vaults) {
|
|
291
|
+
promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
|
|
292
|
+
result[owner] ??= {};
|
|
293
|
+
result[owner][vaultId.toString(10)] = val;
|
|
294
|
+
}));
|
|
295
|
+
if(promises.length>=this.Chain.config.maxParallelCalls) {
|
|
296
|
+
await Promise.all(promises);
|
|
297
|
+
promises = [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
await Promise.all(promises);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async getAllVaults(owner?: string): Promise<EVMSpvVaultData[]> {
|
|
305
|
+
const openedVaults = new Map<string, SpvVaultParametersStructOutput>();
|
|
306
|
+
await this.Events.findInContractEventsForward(
|
|
307
|
+
["Opened", "Closed"],
|
|
308
|
+
owner==null ? null : [
|
|
309
|
+
"0x"+owner.substring(2).padStart(64, "0")
|
|
310
|
+
],
|
|
311
|
+
(event) => {
|
|
312
|
+
const vaultIdentifier = event.args.owner+":"+event.args.vaultId.toString(10);
|
|
313
|
+
if(event.eventName==="Opened") {
|
|
314
|
+
const _event = event as TypedEventLog<SpvVaultManager["filters"]["Opened"]>;
|
|
315
|
+
openedVaults.set(vaultIdentifier, _event.args.params);
|
|
316
|
+
} else {
|
|
317
|
+
openedVaults.delete(vaultIdentifier);
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
const vaults: EVMSpvVaultData[] = [];
|
|
323
|
+
for(let [identifier, vaultParams] of openedVaults.entries()) {
|
|
324
|
+
const [owner, vaultIdStr] = identifier.split(":");
|
|
325
|
+
|
|
326
|
+
const vaultState = await this.contract.getVault(owner, BigInt(vaultIdStr));
|
|
327
|
+
if(vaultState.spvVaultParametersCommitment === getVaultParamsCommitment(vaultParams)) {
|
|
328
|
+
vaults.push(new EVMSpvVaultData(owner, BigInt(vaultIdStr), vaultState, vaultParams))
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return vaults;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private parseWithdrawalEvent(event: TypedEventLog<SpvVaultManager["filters"][keyof SpvVaultManager["filters"]]>): SpvWithdrawalState | null {
|
|
335
|
+
switch(event.eventName) {
|
|
336
|
+
case "Fronted":
|
|
337
|
+
const frontedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Fronted"]>;
|
|
338
|
+
const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
|
|
339
|
+
return {
|
|
340
|
+
type: SpvWithdrawalStateType.FRONTED,
|
|
341
|
+
txId: event.transactionHash,
|
|
342
|
+
owner: ownerFront,
|
|
343
|
+
vaultId: vaultIdFront,
|
|
344
|
+
recipient: frontedEvent.args.recipient,
|
|
345
|
+
fronter: frontedEvent.args.caller
|
|
346
|
+
};
|
|
347
|
+
case "Claimed":
|
|
348
|
+
const claimedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Claimed"]>;
|
|
349
|
+
const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
|
|
350
|
+
return {
|
|
351
|
+
type: SpvWithdrawalStateType.CLAIMED,
|
|
352
|
+
txId: event.transactionHash,
|
|
353
|
+
owner: ownerClaim,
|
|
354
|
+
vaultId: vaultIdClaim,
|
|
355
|
+
recipient: claimedEvent.args.recipient,
|
|
356
|
+
claimer: claimedEvent.args.caller,
|
|
357
|
+
fronter: claimedEvent.args.frontingAddress
|
|
358
|
+
};
|
|
359
|
+
case "Closed":
|
|
360
|
+
const closedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Closed"]>;
|
|
361
|
+
return {
|
|
362
|
+
type: SpvWithdrawalStateType.CLOSED,
|
|
363
|
+
txId: event.transactionHash,
|
|
364
|
+
owner: closedEvent.args.owner,
|
|
365
|
+
vaultId: closedEvent.args.vaultId,
|
|
366
|
+
error: closedEvent.args.error
|
|
367
|
+
};
|
|
368
|
+
default:
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState> {
|
|
374
|
+
const txHash = Buffer.from(btcTxId, "hex").reverse();
|
|
375
|
+
let result: SpvWithdrawalState = await this.Events.findInContractEvents(
|
|
376
|
+
["Fronted", "Claimed", "Closed"],
|
|
377
|
+
[
|
|
378
|
+
null,
|
|
379
|
+
null,
|
|
380
|
+
hexlify(txHash)
|
|
381
|
+
],
|
|
382
|
+
async (event) => {
|
|
383
|
+
const result = this.parseWithdrawalEvent(event);
|
|
384
|
+
if(result!=null) return result;
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
result ??= {
|
|
388
|
+
type: SpvWithdrawalStateType.NOT_FOUND
|
|
389
|
+
};
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async getWithdrawalStates(btcTxIds: string[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
|
|
394
|
+
const result: {[btcTxId: string]: SpvWithdrawalState} = {};
|
|
395
|
+
|
|
396
|
+
for(let i=0;i<btcTxIds.length;i+=this.Chain.config.maxLogTopics) {
|
|
397
|
+
const checkBtcTxIds = btcTxIds.slice(i, i+this.Chain.config.maxLogTopics);
|
|
398
|
+
const checkBtcTxIdsSet = new Set(checkBtcTxIds);
|
|
399
|
+
|
|
400
|
+
await this.Events.findInContractEvents(
|
|
401
|
+
["Fronted", "Claimed", "Closed"],
|
|
402
|
+
[
|
|
403
|
+
null,
|
|
404
|
+
null,
|
|
405
|
+
checkBtcTxIds.map(btcTxId => hexlify(Buffer.from(btcTxId, "hex").reverse()))
|
|
406
|
+
],
|
|
407
|
+
async (event) => {
|
|
408
|
+
const _event = event as TypedEventLog<SpvVaultManager["filters"]["Fronted" | "Claimed" | "Closed"]>;
|
|
409
|
+
const btcTxId = Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
|
|
410
|
+
if(!checkBtcTxIdsSet.has(btcTxId)) {
|
|
411
|
+
this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`)
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const eventResult = this.parseWithdrawalEvent(event);
|
|
415
|
+
if(eventResult==null) return null;
|
|
416
|
+
checkBtcTxIdsSet.delete(btcTxId);
|
|
417
|
+
result[btcTxId] = eventResult;
|
|
418
|
+
if(checkBtcTxIdsSet.size===0) return true; //All processed
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for(let btcTxId of btcTxIds) {
|
|
424
|
+
result[btcTxId] ??= {
|
|
425
|
+
type: SpvWithdrawalStateType.NOT_FOUND
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
getWithdrawalData(btcTx: BtcTx): Promise<EVMSpvWithdrawalData> {
|
|
433
|
+
return Promise.resolve(new EVMSpvWithdrawalData(btcTx));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
//OP_RETURN data encoding/decoding
|
|
437
|
+
fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
|
|
438
|
+
return EVMSpvVaultContract.fromOpReturnData(data);
|
|
439
|
+
}
|
|
440
|
+
static fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
|
|
441
|
+
let rawAmount0: bigint = 0n;
|
|
442
|
+
let rawAmount1: bigint = 0n;
|
|
443
|
+
let executionHash: string = null;
|
|
444
|
+
if(data.length===28) {
|
|
445
|
+
rawAmount0 = data.readBigInt64BE(20).valueOf();
|
|
446
|
+
} else if(data.length===36) {
|
|
447
|
+
rawAmount0 = data.readBigInt64BE(20).valueOf();
|
|
448
|
+
rawAmount1 = data.readBigInt64BE(28).valueOf();
|
|
449
|
+
} else if(data.length===60) {
|
|
450
|
+
rawAmount0 = data.readBigInt64BE(20).valueOf();
|
|
451
|
+
executionHash = data.slice(28, 60).toString("hex");
|
|
452
|
+
} else if(data.length===68) {
|
|
453
|
+
rawAmount0 = data.readBigInt64BE(20).valueOf();
|
|
454
|
+
rawAmount1 = data.readBigInt64BE(28).valueOf();
|
|
455
|
+
executionHash = data.slice(36, 68).toString("hex");
|
|
456
|
+
} else {
|
|
457
|
+
throw new Error("Invalid OP_RETURN data length!");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const recipient = "0x"+data.slice(0, 20).toString("hex");
|
|
461
|
+
if(!EVMAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
|
|
462
|
+
|
|
463
|
+
return {executionHash, rawAmounts: [rawAmount0, rawAmount1], recipient: getAddress(recipient)};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
|
|
467
|
+
return EVMSpvVaultContract.toOpReturnData(recipient, rawAmounts, executionHash);
|
|
468
|
+
}
|
|
469
|
+
static toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
|
|
470
|
+
if(!EVMAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
|
|
471
|
+
if(rawAmounts.length < 1) throw new Error("At least 1 amount needs to be specified");
|
|
472
|
+
if(rawAmounts.length > 2) throw new Error("At most 2 amounts need to be specified");
|
|
473
|
+
rawAmounts.forEach(val => {
|
|
474
|
+
if(val < 0n) throw new Error("Negative raw amount specified");
|
|
475
|
+
if(val >= 2n**64n) throw new Error("Raw amount overflow");
|
|
476
|
+
});
|
|
477
|
+
if(executionHash!=null) {
|
|
478
|
+
if(Buffer.from(executionHash, "hex").length !== 32)
|
|
479
|
+
throw new Error("Invalid execution hash");
|
|
480
|
+
}
|
|
481
|
+
const recipientBuffer = Buffer.from(recipient.substring(2).padStart(40, "0"), "hex");
|
|
482
|
+
const amount0Buffer = BigIntBufferUtils.toBuffer(rawAmounts[0], "be", 8);
|
|
483
|
+
const amount1Buffer = rawAmounts[1]==null || rawAmounts[1]===0n ? Buffer.alloc(0) : BigIntBufferUtils.toBuffer(rawAmounts[1], "be", 8);
|
|
484
|
+
const executionHashBuffer = executionHash==null ? Buffer.alloc(0) : Buffer.from(executionHash, "hex");
|
|
485
|
+
|
|
486
|
+
return Buffer.concat([
|
|
487
|
+
recipientBuffer,
|
|
488
|
+
amount0Buffer,
|
|
489
|
+
amount1Buffer,
|
|
490
|
+
executionHashBuffer
|
|
491
|
+
]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
//Actions
|
|
495
|
+
async claim(signer: EVMSigner, vault: EVMSpvVaultData, txs: {tx: EVMSpvWithdrawalData, storedHeader?: EVMBtcStoredHeader}[], synchronizer?: RelaySynchronizer<any, any, any>, initAta?: boolean, txOptions?: TransactionConfirmationOptions): Promise<string> {
|
|
496
|
+
const result = await this.txsClaim(signer.getAddress(), vault, txs, synchronizer, initAta, txOptions?.feeRate);
|
|
497
|
+
const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
498
|
+
return signature;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async deposit(signer: EVMSigner, vault: EVMSpvVaultData, rawAmounts: bigint[], txOptions?: TransactionConfirmationOptions): Promise<string> {
|
|
502
|
+
const result = await this.txsDeposit(signer.getAddress(), vault, rawAmounts, txOptions?.feeRate);
|
|
503
|
+
const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
504
|
+
return txHashes[txHashes.length - 1];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async frontLiquidity(signer: EVMSigner, vault: EVMSpvVaultData, realWithdrawalTx: EVMSpvWithdrawalData, withdrawSequence: number, txOptions?: TransactionConfirmationOptions): Promise<string> {
|
|
508
|
+
const result = await this.txsFrontLiquidity(signer.getAddress(), vault, realWithdrawalTx, withdrawSequence, txOptions?.feeRate);
|
|
509
|
+
const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
510
|
+
return txHashes[txHashes.length - 1];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async open(signer: EVMSigner, vault: EVMSpvVaultData, txOptions?: TransactionConfirmationOptions): Promise<string> {
|
|
514
|
+
const result = await this.txsOpen(signer.getAddress(), vault, txOptions?.feeRate);
|
|
515
|
+
const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
516
|
+
return signature;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
//Transactions
|
|
520
|
+
async txsClaim(
|
|
521
|
+
signer: string, vault: EVMSpvVaultData, txs: {
|
|
522
|
+
tx: EVMSpvWithdrawalData,
|
|
523
|
+
storedHeader?: EVMBtcStoredHeader
|
|
524
|
+
}[], synchronizer?: RelaySynchronizer<any, any, any>,
|
|
525
|
+
initAta?: boolean, feeRate?: string
|
|
526
|
+
): Promise<EVMTx[]> {
|
|
527
|
+
if(!vault.isOpened()) throw new Error("Cannot claim from a closed vault!");
|
|
528
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
529
|
+
|
|
530
|
+
const txsWithMerkleProofs: {
|
|
531
|
+
tx: EVMSpvWithdrawalData,
|
|
532
|
+
reversedTxId: Buffer,
|
|
533
|
+
pos: number,
|
|
534
|
+
blockheight: number,
|
|
535
|
+
merkle: Buffer[],
|
|
536
|
+
storedHeader?: EVMBtcStoredHeader
|
|
537
|
+
}[] = [];
|
|
538
|
+
for(let tx of txs) {
|
|
539
|
+
const merkleProof = await this.bitcoinRpc.getMerkleProof(tx.tx.btcTx.txid, tx.tx.btcTx.blockhash);
|
|
540
|
+
this.logger.debug("txsClaim(): merkle proof computed: ", merkleProof);
|
|
541
|
+
txsWithMerkleProofs.push({
|
|
542
|
+
...merkleProof,
|
|
543
|
+
...tx
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const evmTxs: EVMTx[] = [];
|
|
548
|
+
const storedHeaders: {[blockhash: string]: EVMBtcStoredHeader} = await EVMBtcRelay.getCommitedHeadersAndSynchronize(
|
|
549
|
+
signer, this.btcRelay, txsWithMerkleProofs.filter(tx => tx.storedHeader==null).map(tx => {
|
|
550
|
+
return {
|
|
551
|
+
blockhash: tx.tx.btcTx.blockhash,
|
|
552
|
+
blockheight: tx.blockheight,
|
|
553
|
+
requiredConfirmations: vault.getConfirmations()
|
|
554
|
+
}
|
|
555
|
+
}), evmTxs, synchronizer, feeRate
|
|
556
|
+
);
|
|
557
|
+
if(storedHeaders==null) throw new Error("Cannot fetch committed header!");
|
|
558
|
+
|
|
559
|
+
for(let tx of txsWithMerkleProofs) {
|
|
560
|
+
evmTxs.push(await this.Claim(signer, vault, tx.tx, tx.storedHeader ?? storedHeaders[tx.tx.btcTx.blockhash], tx.merkle, tx.pos, feeRate));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.logger.debug("txsClaim(): "+evmTxs.length+" claim TXs created claiming "+txs.length+" txs, owner: "+vault.getOwner()+
|
|
564
|
+
" vaultId: "+vault.getVaultId().toString(10));
|
|
565
|
+
|
|
566
|
+
return evmTxs;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async txsDeposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate?: string): Promise<EVMTx[]> {
|
|
570
|
+
if(!vault.isOpened()) throw new Error("Cannot deposit to a closed vault!");
|
|
571
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
572
|
+
|
|
573
|
+
const txs: EVMTx[] = [];
|
|
574
|
+
|
|
575
|
+
let realAmount0: bigint = 0n;
|
|
576
|
+
let realAmount1: bigint = 0n;
|
|
577
|
+
|
|
578
|
+
//Approve first
|
|
579
|
+
const requiredApprovals: {[address: string]: bigint} = {};
|
|
580
|
+
if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
|
|
581
|
+
if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
582
|
+
realAmount0 = rawAmounts[0] * vault.token0.multiplier;
|
|
583
|
+
requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
|
|
587
|
+
if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
588
|
+
realAmount1 = rawAmounts[1] * vault.token1.multiplier;
|
|
589
|
+
requiredApprovals[vault.token1.token.toLowerCase()] ??= 0n;
|
|
590
|
+
requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const requiredApprovalTxns = await Promise.all(
|
|
595
|
+
Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate))
|
|
596
|
+
);
|
|
597
|
+
requiredApprovalTxns.forEach(tx => tx!=null && txs.push(tx));
|
|
598
|
+
|
|
599
|
+
txs.push(await this.Deposit(signer, vault, rawAmounts, feeRate));
|
|
600
|
+
|
|
601
|
+
this.logger.debug("txsDeposit(): deposit TX created,"+
|
|
602
|
+
" token0: "+vault.token0.token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
|
|
603
|
+
" token1: "+vault.token1.token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
|
|
604
|
+
|
|
605
|
+
return txs;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async txsFrontLiquidity(signer: string, vault: EVMSpvVaultData, realWithdrawalTx: EVMSpvWithdrawalData, withdrawSequence: number, feeRate?: string): Promise<EVMTx[]> {
|
|
609
|
+
if(!vault.isOpened()) throw new Error("Cannot front on a closed vault!");
|
|
610
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
611
|
+
|
|
612
|
+
const txs: EVMTx[] = [];
|
|
613
|
+
|
|
614
|
+
let realAmount0 = 0n;
|
|
615
|
+
let realAmount1 = 0n;
|
|
616
|
+
|
|
617
|
+
//Approve first
|
|
618
|
+
const rawAmounts = realWithdrawalTx.getFrontingAmount();
|
|
619
|
+
//Approve first
|
|
620
|
+
const requiredApprovals: {[address: string]: bigint} = {};
|
|
621
|
+
if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
|
|
622
|
+
if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
623
|
+
realAmount0 = rawAmounts[0] * vault.token0.multiplier;
|
|
624
|
+
requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
|
|
628
|
+
if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
629
|
+
realAmount1 = rawAmounts[1] * vault.token1.multiplier;
|
|
630
|
+
requiredApprovals[vault.token1.token.toLowerCase()] ??= 0n;
|
|
631
|
+
requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const requiredApprovalTxns = await Promise.all(
|
|
636
|
+
Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate))
|
|
637
|
+
);
|
|
638
|
+
requiredApprovalTxns.forEach(tx => tx!=null && txs.push(tx));
|
|
639
|
+
|
|
640
|
+
txs.push(await this.Front(signer, vault, realWithdrawalTx, withdrawSequence, feeRate));
|
|
641
|
+
|
|
642
|
+
this.logger.debug("txsFrontLiquidity(): front TX created,"+
|
|
643
|
+
" token0: "+vault.token0.token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
|
|
644
|
+
" token1: "+vault.token1.token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
|
|
645
|
+
|
|
646
|
+
return txs;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async txsOpen(signer: string, vault: EVMSpvVaultData, feeRate?: string): Promise<EVMTx[]> {
|
|
650
|
+
if(vault.isOpened()) throw new Error("Cannot open an already opened vault!");
|
|
651
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
652
|
+
|
|
653
|
+
const tx = await this.Open(signer, vault, feeRate);
|
|
654
|
+
|
|
655
|
+
this.logger.debug("txsOpen(): open TX created, owner: "+vault.getOwner()+
|
|
656
|
+
" vaultId: "+vault.getVaultId().toString(10));
|
|
657
|
+
|
|
658
|
+
return [tx];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
getClaimGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
|
|
662
|
+
let totalGas = EVMSpvVaultContract.GasCosts.CLAIM_BASE;
|
|
663
|
+
|
|
664
|
+
if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
|
|
665
|
+
const transferFee = vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
|
|
666
|
+
EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
|
|
667
|
+
totalGas += transferFee;
|
|
668
|
+
if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
|
|
669
|
+
if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
|
|
670
|
+
}
|
|
671
|
+
if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
|
|
672
|
+
const transferFee = vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
|
|
673
|
+
EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
|
|
674
|
+
totalGas += transferFee;
|
|
675
|
+
if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
|
|
676
|
+
if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
|
|
677
|
+
}
|
|
678
|
+
if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.CLAIM_EXECUTION_SCHEDULE;
|
|
679
|
+
|
|
680
|
+
return totalGas;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
getFrontGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
|
|
684
|
+
let totalGas = EVMSpvVaultContract.GasCosts.FRONT_BASE;
|
|
685
|
+
|
|
686
|
+
if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
|
|
687
|
+
totalGas += vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
|
|
688
|
+
EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
|
|
689
|
+
}
|
|
690
|
+
if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
|
|
691
|
+
totalGas += vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
|
|
692
|
+
EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
|
|
693
|
+
}
|
|
694
|
+
if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.FRONT_EXECUTION_SCHEDULE;
|
|
695
|
+
|
|
696
|
+
return totalGas;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async getClaimFee(signer: string, vault: EVMSpvVaultData, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
|
|
700
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
701
|
+
return EVMFees.getGasFee(this.getClaimGas(signer, vault, withdrawalData), feeRate);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async getFrontFee(signer: string, vault?: EVMSpvVaultData, withdrawalData?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
|
|
705
|
+
vault ??= EVMSpvVaultData.randomVault();
|
|
706
|
+
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
707
|
+
let totalFee = EVMFees.getGasFee(this.getFrontGas(signer, vault, withdrawalData), feeRate);
|
|
708
|
+
if(withdrawalData==null || (withdrawalData.rawAmounts[0]!=null && withdrawalData.rawAmounts[0]>0n)) {
|
|
709
|
+
if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
710
|
+
totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if(withdrawalData==null || (withdrawalData.rawAmounts[1]!=null && withdrawalData.rawAmounts[1]>0n)) {
|
|
714
|
+
if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
|
|
715
|
+
if(vault.token1.token.toLowerCase()!==vault.token0.token.toLowerCase() || withdrawalData==null || withdrawalData.rawAmounts[0]==null || withdrawalData.rawAmounts[0]===0n) {
|
|
716
|
+
totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return totalFee;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
}
|