@atomiqlabs/chain-evm 1.0.0-dev.27 → 1.0.0-dev.28

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.
@@ -4,7 +4,6 @@ exports.CitreaInitializer = exports.initializeCitrea = exports.CitreaAssets = vo
4
4
  const base_1 = require("@atomiqlabs/base");
5
5
  const ethers_1 = require("ethers");
6
6
  const EVMChainInterface_1 = require("../../evm/chain/EVMChainInterface");
7
- const EVMSpvVaultContract_1 = require("../../evm/spv_swap/EVMSpvVaultContract");
8
7
  const EVMChainEventsBrowser_1 = require("../../evm/events/EVMChainEventsBrowser");
9
8
  const EVMSwapData_1 = require("../../evm/swaps/EVMSwapData");
10
9
  const EVMSpvVaultData_1 = require("../../evm/spv_swap/EVMSpvVaultData");
@@ -13,6 +12,7 @@ const CitreaFees_1 = require("./CitreaFees");
13
12
  const CitreaBtcRelay_1 = require("./CitreaBtcRelay");
14
13
  const CitreaSwapContract_1 = require("./CitreaSwapContract");
15
14
  const CitreaTokens_1 = require("./CitreaTokens");
15
+ const CitreaSpvVaultContract_1 = require("./CitreaSpvVaultContract");
16
16
  const CitreaChainIds = {
17
17
  MAINNET: null,
18
18
  TESTNET4: 5115
@@ -102,7 +102,7 @@ function initializeCitrea(options, bitcoinRpc, network) {
102
102
  ...options?.handlerContracts?.claim
103
103
  }
104
104
  });
105
- const spvVaultContract = new EVMSpvVaultContract_1.EVMSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
105
+ const spvVaultContract = new CitreaSpvVaultContract_1.CitreaSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
106
106
  const chainEvents = new EVMChainEventsBrowser_1.EVMChainEventsBrowser(chainInterface, swapContract, spvVaultContract);
107
107
  return {
108
108
  chainId: "CITREA",
@@ -1,6 +1,15 @@
1
1
  import { EVMSpvVaultContract } from "../../evm/spv_swap/EVMSpvVaultContract";
2
2
  import { EVMSpvWithdrawalData } from "../../evm/spv_swap/EVMSpvWithdrawalData";
3
+ import { EVMSpvVaultData } from "../../evm/spv_swap/EVMSpvVaultData";
3
4
  export declare class CitreaSpvVaultContract extends EVMSpvVaultContract<"CITREA"> {
4
- getClaimFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
5
- getFrontFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
5
+ static readonly StateDiffSize: {
6
+ BASE_DIFF_SIZE: number;
7
+ ERC_20_TRANSFER_DIFF_SIZE: number;
8
+ NATIVE_SELF_TRANSFER_DIFF_SIZE: number;
9
+ NATIVE_TRANSFER_DIFF_SIZE: number;
10
+ EXECUTION_SCHEDULE_DIFF_SIZE: number;
11
+ };
12
+ private calculateStateDiff;
13
+ getClaimFee(signer: string, vault?: EVMSpvVaultData, data?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
14
+ getFrontFee(signer: string, vault?: EVMSpvVaultData, data?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
6
15
  }
@@ -2,15 +2,74 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CitreaSpvVaultContract = void 0;
4
4
  const EVMSpvVaultContract_1 = require("../../evm/spv_swap/EVMSpvVaultContract");
5
- const EVMFees_1 = require("../../evm/chain/modules/EVMFees");
5
+ const EVMSpvVaultData_1 = require("../../evm/spv_swap/EVMSpvVaultData");
6
+ const lib_esm_1 = require("ethers/lib.esm");
7
+ const ethers_1 = require("ethers");
8
+ const CitreaFees_1 = require("./CitreaFees");
9
+ const EVMAddresses_1 = require("../../evm/chain/modules/EVMAddresses");
6
10
  class CitreaSpvVaultContract extends EVMSpvVaultContract_1.EVMSpvVaultContract {
7
- async getClaimFee(signer, withdrawalData, feeRate) {
11
+ calculateStateDiff(signer, tokenStateChanges) {
12
+ let stateDiffSize = 0;
13
+ tokenStateChanges.forEach(val => {
14
+ const [address, token] = val.split(":");
15
+ if (token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase()) {
16
+ stateDiffSize += address.toLowerCase() === signer?.toLowerCase() ? CitreaSpvVaultContract.StateDiffSize.NATIVE_SELF_TRANSFER_DIFF_SIZE : CitreaSpvVaultContract.StateDiffSize.NATIVE_TRANSFER_DIFF_SIZE;
17
+ }
18
+ else {
19
+ stateDiffSize += CitreaSpvVaultContract.StateDiffSize.ERC_20_TRANSFER_DIFF_SIZE;
20
+ }
21
+ });
22
+ return stateDiffSize;
23
+ }
24
+ async getClaimFee(signer, vault, data, feeRate) {
25
+ vault ?? (vault = EVMSpvVaultData_1.EVMSpvVaultData.randomVault());
8
26
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
9
- return EVMFees_1.EVMFees.getGasFee(EVMSpvVaultContract_1.EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
27
+ const tokenStateChanges = new Set();
28
+ let diffSize = CitreaSpvVaultContract.StateDiffSize.BASE_DIFF_SIZE;
29
+ const recipient = data != null ? data.recipient : EVMAddresses_1.EVMAddresses.randomAddress();
30
+ if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
31
+ tokenStateChanges.add(recipient.toLowerCase() + ":" + vault.token0.token.toLowerCase());
32
+ if (data == null || data.frontingFeeRate > 0n)
33
+ tokenStateChanges.add(ethers_1.ZeroAddress + ":" + vault.token0.token.toLowerCase()); //Also needs to pay out to fronter
34
+ if (data == null || data.callerFeeRate > 0n)
35
+ tokenStateChanges.add(signer + ":" + vault.token0.token.toLowerCase()); //Also needs to pay out to caller
36
+ }
37
+ if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
38
+ tokenStateChanges.add(recipient.toLowerCase() + ":" + vault.token1.token.toLowerCase());
39
+ if (data == null || data.frontingFeeRate > 0n)
40
+ tokenStateChanges.add(ethers_1.ZeroAddress + ":" + vault.token1.token.toLowerCase()); //Also needs to pay out to fronter
41
+ if (data == null || data.callerFeeRate > 0n)
42
+ tokenStateChanges.add(signer + ":" + vault.token1.token.toLowerCase()); //Also needs to pay out to caller
43
+ }
44
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
45
+ if (data == null || (data.executionHash != null && data.executionHash !== lib_esm_1.ZeroHash))
46
+ diffSize += CitreaSpvVaultContract.StateDiffSize.EXECUTION_SCHEDULE_DIFF_SIZE;
47
+ const gasFee = await super.getClaimFee(signer, vault, data, feeRate);
48
+ return gasFee + CitreaFees_1.CitreaFees.getGasFee(0, feeRate, diffSize);
10
49
  }
11
- async getFrontFee(signer, withdrawalData, feeRate) {
50
+ async getFrontFee(signer, vault, data, feeRate) {
51
+ vault ?? (vault = EVMSpvVaultData_1.EVMSpvVaultData.randomVault());
12
52
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
13
- return EVMFees_1.EVMFees.getGasFee(EVMSpvVaultContract_1.EVMSpvVaultContract.GasCosts.FRONT, feeRate);
53
+ const tokenStateChanges = new Set();
54
+ let diffSize = CitreaSpvVaultContract.StateDiffSize.BASE_DIFF_SIZE;
55
+ if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
56
+ tokenStateChanges.add(signer + ":" + vault.token0.token.toLowerCase());
57
+ }
58
+ if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
59
+ tokenStateChanges.add(signer + ":" + vault.token1.token.toLowerCase());
60
+ }
61
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
62
+ if (data == null || (data.executionHash != null && data.executionHash !== lib_esm_1.ZeroHash))
63
+ diffSize += CitreaSpvVaultContract.StateDiffSize.EXECUTION_SCHEDULE_DIFF_SIZE;
64
+ const gasFee = await super.getFrontFee(signer, vault, data, feeRate);
65
+ return gasFee + CitreaFees_1.CitreaFees.getGasFee(0, feeRate, diffSize);
14
66
  }
15
67
  }
16
68
  exports.CitreaSpvVaultContract = CitreaSpvVaultContract;
69
+ CitreaSpvVaultContract.StateDiffSize = {
70
+ BASE_DIFF_SIZE: 50,
71
+ ERC_20_TRANSFER_DIFF_SIZE: 50,
72
+ NATIVE_SELF_TRANSFER_DIFF_SIZE: 20,
73
+ NATIVE_TRANSFER_DIFF_SIZE: 30,
74
+ EXECUTION_SCHEDULE_DIFF_SIZE: 40
75
+ };
@@ -13,10 +13,10 @@ export declare class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
13
13
  /**
14
14
  * Get the estimated solana fee of the commit transaction
15
15
  */
16
- getCommitFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
16
+ getCommitFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
17
17
  getClaimFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
18
18
  /**
19
19
  * Get the estimated solana transaction fee of the refund transaction
20
20
  */
21
- getRefundFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
21
+ getRefundFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
22
22
  }
@@ -20,7 +20,7 @@ class CitreaSwapContract extends EVMSwapContract_1.EVMSwapContract {
20
20
  /**
21
21
  * Get the estimated solana fee of the commit transaction
22
22
  */
23
- async getCommitFee(swapData, feeRate) {
23
+ async getCommitFee(signer, swapData, feeRate) {
24
24
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
25
25
  const tokenStateChanges = new Set();
26
26
  let diffSize = CitreaSwapContract.StateDiffSize.BASE_DIFF_SIZE;
@@ -30,11 +30,10 @@ class CitreaSwapContract extends EVMSwapContract_1.EVMSwapContract {
30
30
  else {
31
31
  tokenStateChanges.add(swapData.getOfferer().toLowerCase() + ":" + swapData.getToken().toLowerCase());
32
32
  }
33
- //TODO: Since we don't know who is sending the transaction, we calculate with the worst case
34
33
  if (swapData.getTotalDeposit() > 0n) {
35
- tokenStateChanges.add(swapData.getClaimer().toLowerCase() + ":" + swapData.getDepositToken().toLowerCase());
34
+ tokenStateChanges.add(signer.toLowerCase() + ":" + swapData.getDepositToken().toLowerCase());
36
35
  }
37
- diffSize += this.calculateStateDiff(null, tokenStateChanges);
36
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
38
37
  const gasFee = await this.Init.getInitFee(swapData, feeRate);
39
38
  return gasFee + CitreaFees_1.CitreaFees.getGasFee(0, feeRate, diffSize);
40
39
  }
@@ -63,7 +62,7 @@ class CitreaSwapContract extends EVMSwapContract_1.EVMSwapContract {
63
62
  /**
64
63
  * Get the estimated solana transaction fee of the refund transaction
65
64
  */
66
- async getRefundFee(swapData, feeRate) {
65
+ async getRefundFee(signer, swapData, feeRate) {
67
66
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
68
67
  const tokenStateChanges = new Set();
69
68
  let diffSize = CitreaSwapContract.StateDiffSize.BASE_DIFF_SIZE;
@@ -75,14 +74,13 @@ class CitreaSwapContract extends EVMSwapContract_1.EVMSwapContract {
75
74
  else {
76
75
  tokenStateChanges.add(swapData.getOfferer().toLowerCase() + ":" + swapData.getToken().toLowerCase());
77
76
  }
78
- //TODO: Since we don't know if the refund is cooperative or not and also not the signer, we calculate with the worst case
79
77
  if (swapData.getSecurityDeposit() > 0) {
80
78
  tokenStateChanges.add(swapData.getOfferer().toLowerCase() + ":" + swapData.getDepositToken().toLowerCase());
81
79
  }
82
80
  if (swapData.getClaimerBounty() > swapData.getSecurityDeposit()) {
83
81
  tokenStateChanges.add(swapData.getClaimer().toLowerCase() + ":" + swapData.getDepositToken().toLowerCase());
84
82
  }
85
- diffSize += this.calculateStateDiff(null, tokenStateChanges);
83
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
86
84
  const gasFee = await this.Refund.getRefundFee(swapData, feeRate);
87
85
  return gasFee + CitreaFees_1.CitreaFees.getGasFee(0, feeRate, diffSize);
88
86
  }
@@ -54,8 +54,7 @@ class EVMChainInterface {
54
54
  return this.Transactions.offBeforeTxSigned(callback);
55
55
  }
56
56
  randomAddress() {
57
- const wallet = ethers_1.Wallet.createRandom();
58
- return wallet.address;
57
+ return EVMAddresses_1.EVMAddresses.randomAddress();
59
58
  }
60
59
  randomSigner() {
61
60
  const wallet = ethers_1.Wallet.createRandom();
@@ -6,4 +6,5 @@ export declare class EVMAddresses extends EVMModule<any> {
6
6
  * @param value
7
7
  */
8
8
  static isValidAddress(value: string): boolean;
9
+ static randomAddress(): string;
9
10
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EVMAddresses = void 0;
4
4
  const EVMModule_1 = require("../EVMModule");
5
5
  const ethers_1 = require("ethers");
6
+ const lib_esm_1 = require("ethers/lib.esm");
6
7
  class EVMAddresses extends EVMModule_1.EVMModule {
7
8
  ///////////////////
8
9
  //// Address utils
@@ -22,5 +23,9 @@ class EVMAddresses extends EVMModule_1.EVMModule {
22
23
  return false;
23
24
  }
24
25
  }
26
+ static randomAddress() {
27
+ const wallet = lib_esm_1.Wallet.createRandom();
28
+ return wallet.address;
29
+ }
25
30
  }
26
31
  exports.EVMAddresses = EVMAddresses;
@@ -16,10 +16,17 @@ export declare function packOwnerAndVaultId(owner: string, vaultId: bigint): str
16
16
  export declare function unpackOwnerAndVaultId(data: string): [string, bigint];
17
17
  export declare class EVMSpvVaultContract<ChainId extends string> extends EVMContractBase<SpvVaultManager> implements SpvVaultContract<EVMTx, EVMSigner, ChainId, EVMSpvVaultData, EVMSpvWithdrawalData> {
18
18
  static readonly GasCosts: {
19
- DEPOSIT: number;
19
+ DEPOSIT_BASE: number;
20
+ DEPOSIT_ERC20: number;
20
21
  OPEN: number;
21
- FRONT: number;
22
- CLAIM: number;
22
+ CLAIM_BASE: number;
23
+ CLAIM_NATIVE_TRANSFER: number;
24
+ CLAIM_ERC20_TRANSFER: number;
25
+ CLAIM_EXECUTION_SCHEDULE: number;
26
+ FRONT_BASE: number;
27
+ FRONT_NATIVE_TRANSFER: number;
28
+ FRONT_ERC20_TRANSFER: number;
29
+ FRONT_EXECUTION_SCHEDULE: number;
23
30
  };
24
31
  readonly chainId: ChainId;
25
32
  readonly btcRelay: EVMBtcRelay<any>;
@@ -64,6 +71,8 @@ export declare class EVMSpvVaultContract<ChainId extends string> extends EVMCont
64
71
  txsDeposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate?: string): Promise<EVMTx[]>;
65
72
  txsFrontLiquidity(signer: string, vault: EVMSpvVaultData, realWithdrawalTx: EVMSpvWithdrawalData, withdrawSequence: number, feeRate?: string): Promise<EVMTx[]>;
66
73
  txsOpen(signer: string, vault: EVMSpvVaultData, feeRate?: string): Promise<EVMTx[]>;
67
- getClaimFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
68
- getFrontFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
74
+ getClaimGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number;
75
+ getFrontGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number;
76
+ getClaimFee(signer: string, vault: EVMSpvVaultData, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
77
+ getFrontFee(signer: string, vault?: EVMSpvVaultData, withdrawalData?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint>;
69
78
  }
@@ -49,14 +49,25 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
49
49
  return tx;
50
50
  }
51
51
  async Deposit(signer, vault, rawAmounts, feeRate) {
52
+ let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
52
53
  let value = 0n;
53
- if (vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase())
54
+ if (vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase()) {
54
55
  value += rawAmounts[0] * vault.token0.multiplier;
55
- if (vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase())
56
+ }
57
+ else {
58
+ if (rawAmounts[0] > 0n)
59
+ totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
60
+ }
61
+ if (vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase()) {
56
62
  value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
63
+ }
64
+ else {
65
+ if (rawAmounts[1] != null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase() !== vault.token1.token.toLowerCase())
66
+ totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
67
+ }
57
68
  const tx = await this.contract.deposit.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), rawAmounts[0], rawAmounts[1] ?? 0n, { value });
58
69
  tx.from = signer;
59
- EVMFees_1.EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.DEPOSIT, feeRate);
70
+ EVMFees_1.EVMFees.applyFeeRate(tx, totalGas, feeRate);
60
71
  return tx;
61
72
  }
62
73
  async Front(signer, vault, data, withdrawalSequence, feeRate) {
@@ -68,13 +79,13 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
68
79
  value += (frontingAmount[1] ?? 0n) * vault.token1.multiplier;
69
80
  const tx = await this.contract.front.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), withdrawalSequence, data.getTxHash(), data.serializeToStruct(), { value });
70
81
  tx.from = signer;
71
- EVMFees_1.EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.FRONT, feeRate);
82
+ EVMFees_1.EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
72
83
  return tx;
73
84
  }
74
85
  async Claim(signer, vault, data, blockheader, merkle, position, feeRate) {
75
86
  const tx = await this.contract.claim.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), "0x" + data.btcTx.hex, blockheader.serializeToStruct(), merkle, position);
76
87
  tx.from = signer;
77
- EVMFees_1.EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
88
+ EVMFees_1.EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
78
89
  return tx;
79
90
  }
80
91
  async checkWithdrawalTx(tx) {
@@ -392,19 +403,78 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
392
403
  " vaultId: " + vault.getVaultId().toString(10));
393
404
  return [tx];
394
405
  }
395
- async getClaimFee(signer, withdrawalData, feeRate) {
406
+ getClaimGas(signer, vault, data) {
407
+ let totalGas = EVMSpvVaultContract.GasCosts.CLAIM_BASE;
408
+ if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
409
+ const transferFee = vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
410
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
411
+ totalGas += transferFee;
412
+ if (data == null || data.frontingFeeRate > 0n)
413
+ totalGas += transferFee; //Also needs to pay out to fronter
414
+ if (data == null || (data.callerFeeRate > 0n && !data.isRecipient(signer)))
415
+ totalGas += transferFee; //Also needs to pay out to caller
416
+ }
417
+ if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
418
+ const transferFee = vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
419
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
420
+ totalGas += transferFee;
421
+ if (data == null || data.frontingFeeRate > 0n)
422
+ totalGas += transferFee; //Also needs to pay out to fronter
423
+ if (data == null || (data.callerFeeRate > 0n && !data.isRecipient(signer)))
424
+ totalGas += transferFee; //Also needs to pay out to caller
425
+ }
426
+ if (data == null || (data.executionHash != null && data.executionHash !== ethers_1.ZeroHash))
427
+ totalGas += EVMSpvVaultContract.GasCosts.CLAIM_EXECUTION_SCHEDULE;
428
+ return totalGas;
429
+ }
430
+ getFrontGas(signer, vault, data) {
431
+ let totalGas = EVMSpvVaultContract.GasCosts.FRONT_BASE;
432
+ if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
433
+ totalGas += vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
434
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
435
+ }
436
+ if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
437
+ totalGas += vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
438
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
439
+ }
440
+ if (data == null || (data.executionHash != null && data.executionHash !== ethers_1.ZeroHash))
441
+ totalGas += EVMSpvVaultContract.GasCosts.FRONT_EXECUTION_SCHEDULE;
442
+ return totalGas;
443
+ }
444
+ async getClaimFee(signer, vault, withdrawalData, feeRate) {
396
445
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
397
- return EVMFees_1.EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
446
+ return EVMFees_1.EVMFees.getGasFee(this.getClaimGas(signer, vault, withdrawalData), feeRate);
398
447
  }
399
- async getFrontFee(signer, withdrawalData, feeRate) {
448
+ async getFrontFee(signer, vault, withdrawalData, feeRate) {
449
+ vault ?? (vault = EVMSpvVaultData_1.EVMSpvVaultData.randomVault());
400
450
  feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
401
- return EVMFees_1.EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.FRONT, feeRate);
451
+ let totalFee = EVMFees_1.EVMFees.getGasFee(this.getFrontGas(signer, vault, withdrawalData), feeRate);
452
+ if (withdrawalData == null || (withdrawalData.rawAmounts[0] != null && withdrawalData.rawAmounts[0] > 0n)) {
453
+ if (vault.token0.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
454
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
455
+ }
456
+ }
457
+ if (withdrawalData == null || (withdrawalData.rawAmounts[1] != null && withdrawalData.rawAmounts[1] > 0n)) {
458
+ if (vault.token1.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
459
+ if (vault.token1.token.toLowerCase() !== vault.token0.token.toLowerCase() || withdrawalData == null || withdrawalData.rawAmounts[0] == null || withdrawalData.rawAmounts[0] === 0n) {
460
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
461
+ }
462
+ }
463
+ }
464
+ return totalFee;
402
465
  }
403
466
  }
404
467
  exports.EVMSpvVaultContract = EVMSpvVaultContract;
405
468
  EVMSpvVaultContract.GasCosts = {
406
- DEPOSIT: 150000 + 21000,
407
- OPEN: 100000 + 21000,
408
- FRONT: 250000 + 21000,
409
- CLAIM: 250000 + 21000
469
+ DEPOSIT_BASE: 15000 + 21000,
470
+ DEPOSIT_ERC20: 40000,
471
+ OPEN: 80000 + 21000,
472
+ CLAIM_BASE: 85000 + 21000,
473
+ CLAIM_NATIVE_TRANSFER: 7500,
474
+ CLAIM_ERC20_TRANSFER: 40000,
475
+ CLAIM_EXECUTION_SCHEDULE: 30000,
476
+ FRONT_BASE: 75000 + 21000,
477
+ FRONT_NATIVE_TRANSFER: 7500,
478
+ FRONT_ERC20_TRANSFER: 40000,
479
+ FRONT_EXECUTION_SCHEDULE: 30000
410
480
  };
@@ -35,4 +35,5 @@ export declare class EVMSpvVaultData extends SpvVaultData<EVMSpvWithdrawalData>
35
35
  updateState(withdrawalTxOrEvent: SpvVaultClaimEvent | SpvVaultCloseEvent | SpvVaultOpenEvent | SpvVaultDepositEvent | EVMSpvWithdrawalData): void;
36
36
  getDepositCount(): number;
37
37
  getVaultParamsStruct(): SpvVaultParametersStruct;
38
+ static randomVault(): EVMSpvVaultData;
38
39
  }
@@ -6,6 +6,7 @@ const buffer_1 = require("buffer");
6
6
  const EVMSpvWithdrawalData_1 = require("./EVMSpvWithdrawalData");
7
7
  const ethers_1 = require("ethers");
8
8
  const ethers_2 = require("ethers");
9
+ const EVMAddresses_1 = require("../chain/modules/EVMAddresses");
9
10
  function getVaultParamsCommitment(vaultParams) {
10
11
  return (0, ethers_2.keccak256)(ethers_2.AbiCoder.defaultAbiCoder().encode(["address", "address", "address", "uint192", "uint192", "uint256"], [vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]));
11
12
  }
@@ -154,6 +155,26 @@ class EVMSpvVaultData extends base_1.SpvVaultData {
154
155
  confirmations: this.confirmations
155
156
  };
156
157
  }
158
+ static randomVault() {
159
+ const spvVaultParams = {
160
+ btcRelayContract: EVMAddresses_1.EVMAddresses.randomAddress(),
161
+ token0: EVMAddresses_1.EVMAddresses.randomAddress(),
162
+ token1: EVMAddresses_1.EVMAddresses.randomAddress(),
163
+ token0Multiplier: 1n,
164
+ token1Multiplier: 1n,
165
+ confirmations: 3n,
166
+ };
167
+ return new EVMSpvVaultData(EVMAddresses_1.EVMAddresses.randomAddress(), 0n, {
168
+ spvVaultParametersCommitment: getVaultParamsCommitment(spvVaultParams),
169
+ utxoTxHash: (0, ethers_1.randomBytes)(32),
170
+ utxoVout: 0n,
171
+ openBlockheight: 0n,
172
+ withdrawCount: 0n,
173
+ depositCount: 0n,
174
+ token0Amount: 0n,
175
+ token1Amount: 0n
176
+ }, spvVaultParams);
177
+ }
157
178
  }
158
179
  exports.EVMSpvVaultData = EVMSpvVaultData;
159
180
  base_1.SpvVaultData.deserializers["EVM"] = EVMSpvVaultData;
@@ -182,11 +182,11 @@ export declare class EVMSwapContract<ChainId extends string = string> extends EV
182
182
  getClaimFeeRate(signer: string, swapData: EVMSwapData): Promise<string>;
183
183
  getClaimFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
184
184
  /**
185
- * Get the estimated solana fee of the commit transaction
185
+ * Get the estimated fee of the commit transaction
186
186
  */
187
- getCommitFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
187
+ getCommitFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
188
188
  /**
189
- * Get the estimated solana transaction fee of the refund transaction
189
+ * Get the estimated transaction fee of the refund transaction
190
190
  */
191
- getRefundFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
191
+ getRefundFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint>;
192
192
  }
@@ -358,15 +358,15 @@ class EVMSwapContract extends EVMContractBase_1.EVMContractBase {
358
358
  return this.Claim.getClaimFee(swapData, feeRate);
359
359
  }
360
360
  /**
361
- * Get the estimated solana fee of the commit transaction
361
+ * Get the estimated fee of the commit transaction
362
362
  */
363
- getCommitFee(swapData, feeRate) {
363
+ getCommitFee(signer, swapData, feeRate) {
364
364
  return this.Init.getInitFee(swapData, feeRate);
365
365
  }
366
366
  /**
367
- * Get the estimated solana transaction fee of the refund transaction
367
+ * Get the estimated transaction fee of the refund transaction
368
368
  */
369
- getRefundFee(swapData, feeRate) {
369
+ getRefundFee(signer, swapData, feeRate) {
370
370
  return this.Refund.getRefundFee(swapData, feeRate);
371
371
  }
372
372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-evm",
3
- "version": "1.0.0-dev.27",
3
+ "version": "1.0.0-dev.28",
4
4
  "description": "EVM specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "author": "adambor",
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "@atomiqlabs/base": "^10.0.0-dev.1",
26
+ "@atomiqlabs/base": "^10.0.0-dev.2",
27
27
  "@noble/hashes": "^1.8.0",
28
28
  "@scure/btc-signer": "1.6.0",
29
29
  "buffer": "6.0.3"
@@ -14,6 +14,7 @@ import {CitreaFees} from "./CitreaFees";
14
14
  import {CitreaBtcRelay} from "./CitreaBtcRelay";
15
15
  import {CitreaSwapContract} from "./CitreaSwapContract";
16
16
  import {CitreaTokens} from "./CitreaTokens";
17
+ import {CitreaSpvVaultContract} from "./CitreaSpvVaultContract";
17
18
 
18
19
  const CitreaChainIds = {
19
20
  MAINNET: null,
@@ -147,7 +148,7 @@ export function initializeCitrea(
147
148
  }
148
149
  );
149
150
 
150
- const spvVaultContract = new EVMSpvVaultContract(
151
+ const spvVaultContract = new CitreaSpvVaultContract(
151
152
  chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract,
152
153
  options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight
153
154
  )
@@ -1,18 +1,76 @@
1
1
  import {EVMSpvVaultContract} from "../../evm/spv_swap/EVMSpvVaultContract";
2
2
  import {EVMSpvWithdrawalData} from "../../evm/spv_swap/EVMSpvWithdrawalData";
3
- import {EVMFees} from "../../evm/chain/modules/EVMFees";
3
+ import {EVMSpvVaultData} from "../../evm/spv_swap/EVMSpvVaultData";
4
+ import {ZeroHash} from "ethers/lib.esm";
5
+ import {ZeroAddress} from "ethers";
6
+ import {CitreaFees} from "./CitreaFees";
7
+ import {EVMAddresses} from "../../evm/chain/modules/EVMAddresses";
4
8
 
5
9
 
6
10
  export class CitreaSpvVaultContract extends EVMSpvVaultContract<"CITREA"> {
7
11
 
8
- async getClaimFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
12
+ public static readonly StateDiffSize = {
13
+ BASE_DIFF_SIZE: 50,
14
+ ERC_20_TRANSFER_DIFF_SIZE: 50,
15
+ NATIVE_SELF_TRANSFER_DIFF_SIZE: 20,
16
+ NATIVE_TRANSFER_DIFF_SIZE: 30,
17
+ EXECUTION_SCHEDULE_DIFF_SIZE: 40
18
+ };
19
+
20
+ private calculateStateDiff(signer: string, tokenStateChanges: Set<string>): number {
21
+ let stateDiffSize = 0;
22
+ tokenStateChanges.forEach(val => {
23
+ const [address, token] = val.split(":");
24
+ if(token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
25
+ stateDiffSize += address.toLowerCase()===signer?.toLowerCase() ? CitreaSpvVaultContract.StateDiffSize.NATIVE_SELF_TRANSFER_DIFF_SIZE : CitreaSpvVaultContract.StateDiffSize.NATIVE_TRANSFER_DIFF_SIZE;
26
+ } else {
27
+ stateDiffSize += CitreaSpvVaultContract.StateDiffSize.ERC_20_TRANSFER_DIFF_SIZE;
28
+ }
29
+ });
30
+ return stateDiffSize;
31
+ }
32
+
33
+ async getClaimFee(signer: string, vault?: EVMSpvVaultData, data?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
34
+ vault ??= EVMSpvVaultData.randomVault();
9
35
  feeRate ??= await this.Chain.Fees.getFeeRate();
10
- return EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
36
+ const tokenStateChanges: Set<string> = new Set();
37
+
38
+ let diffSize = CitreaSpvVaultContract.StateDiffSize.BASE_DIFF_SIZE;
39
+ const recipient = data!=null ? data.recipient : EVMAddresses.randomAddress();
40
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
41
+ tokenStateChanges.add(recipient.toLowerCase()+":"+vault.token0.token.toLowerCase());
42
+ if (data==null || data.frontingFeeRate > 0n) tokenStateChanges.add(ZeroAddress+":"+vault.token0.token.toLowerCase()); //Also needs to pay out to fronter
43
+ if (data==null || data.callerFeeRate > 0n) tokenStateChanges.add(signer+":"+vault.token0.token.toLowerCase()); //Also needs to pay out to caller
44
+ }
45
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
46
+ tokenStateChanges.add(recipient.toLowerCase()+":"+vault.token1.token.toLowerCase());
47
+ if (data==null || data.frontingFeeRate > 0n) tokenStateChanges.add(ZeroAddress+":"+vault.token1.token.toLowerCase()); //Also needs to pay out to fronter
48
+ if (data==null || data.callerFeeRate > 0n) tokenStateChanges.add(signer+":"+vault.token1.token.toLowerCase()); //Also needs to pay out to caller
49
+ }
50
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
51
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) diffSize += CitreaSpvVaultContract.StateDiffSize.EXECUTION_SCHEDULE_DIFF_SIZE;
52
+
53
+ const gasFee = await super.getClaimFee(signer, vault, data, feeRate);
54
+ return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
11
55
  }
12
56
 
13
- async getFrontFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
57
+ async getFrontFee(signer: string, vault?: EVMSpvVaultData, data?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
58
+ vault ??= EVMSpvVaultData.randomVault();
14
59
  feeRate ??= await this.Chain.Fees.getFeeRate();
15
- return EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.FRONT, feeRate);
60
+ const tokenStateChanges: Set<string> = new Set();
61
+
62
+ let diffSize = CitreaSpvVaultContract.StateDiffSize.BASE_DIFF_SIZE;
63
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
64
+ tokenStateChanges.add(signer+":"+vault.token0.token.toLowerCase());
65
+ }
66
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
67
+ tokenStateChanges.add(signer+":"+vault.token1.token.toLowerCase());
68
+ }
69
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
70
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) diffSize += CitreaSpvVaultContract.StateDiffSize.EXECUTION_SCHEDULE_DIFF_SIZE;
71
+
72
+ const gasFee = await super.getFrontFee(signer, vault, data, feeRate);
73
+ return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
16
74
  }
17
75
 
18
76
  }
@@ -29,7 +29,7 @@ export class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
29
29
  /**
30
30
  * Get the estimated solana fee of the commit transaction
31
31
  */
32
- async getCommitFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
32
+ async getCommitFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
33
33
  feeRate ??= await this.Chain.Fees.getFeeRate();
34
34
 
35
35
  const tokenStateChanges: Set<string> = new Set();
@@ -40,11 +40,10 @@ export class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
40
40
  } else {
41
41
  tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getToken().toLowerCase());
42
42
  }
43
- //TODO: Since we don't know who is sending the transaction, we calculate with the worst case
44
43
  if(swapData.getTotalDeposit()>0n) {
45
- tokenStateChanges.add(swapData.getClaimer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
44
+ tokenStateChanges.add(signer.toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
46
45
  }
47
- diffSize += this.calculateStateDiff(null, tokenStateChanges);
46
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
48
47
 
49
48
  const gasFee = await this.Init.getInitFee(swapData, feeRate);
50
49
  return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
@@ -77,7 +76,7 @@ export class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
77
76
  /**
78
77
  * Get the estimated solana transaction fee of the refund transaction
79
78
  */
80
- async getRefundFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
79
+ async getRefundFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
81
80
  feeRate ??= await this.Chain.Fees.getFeeRate();
82
81
 
83
82
  const tokenStateChanges: Set<string> = new Set();
@@ -89,14 +88,13 @@ export class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
89
88
  } else {
90
89
  tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getToken().toLowerCase());
91
90
  }
92
- //TODO: Since we don't know if the refund is cooperative or not and also not the signer, we calculate with the worst case
93
91
  if(swapData.getSecurityDeposit() > 0) {
94
92
  tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
95
93
  }
96
94
  if(swapData.getClaimerBounty() > swapData.getSecurityDeposit()) {
97
95
  tokenStateChanges.add(swapData.getClaimer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
98
96
  }
99
- diffSize += this.calculateStateDiff(null, tokenStateChanges);
97
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
100
98
 
101
99
  const gasFee = await this.Refund.getRefundFee(swapData, feeRate);
102
100
  return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
@@ -100,8 +100,7 @@ export class EVMChainInterface<ChainId extends string = string, EVMChainId exten
100
100
  }
101
101
 
102
102
  randomAddress(): string {
103
- const wallet = Wallet.createRandom();
104
- return wallet.address;
103
+ return EVMAddresses.randomAddress();
105
104
  }
106
105
 
107
106
  randomSigner(): EVMSigner {
@@ -1,5 +1,6 @@
1
1
  import {EVMModule} from "../EVMModule";
2
2
  import {isAddress} from "ethers";
3
+ import {Wallet} from "ethers/lib.esm";
3
4
 
4
5
 
5
6
  export class EVMAddresses extends EVMModule<any> {
@@ -21,4 +22,9 @@ export class EVMAddresses extends EVMModule<any> {
21
22
  }
22
23
  }
23
24
 
25
+ static randomAddress(): string {
26
+ const wallet = Wallet.createRandom();
27
+ return wallet.address;
28
+ }
29
+
24
30
  }
@@ -55,10 +55,20 @@ export class EVMSpvVaultContract<ChainId extends string>
55
55
  >
56
56
  {
57
57
  public static readonly GasCosts = {
58
- DEPOSIT: 150_000 + 21_000,
59
- OPEN: 100_000 + 21_000,
60
- FRONT: 250_000 + 21_000,
61
- CLAIM: 250_000 + 21_000
58
+ DEPOSIT_BASE: 15_000 + 21_000,
59
+ DEPOSIT_ERC20: 40_000,
60
+
61
+ OPEN: 80_000 + 21_000,
62
+
63
+ CLAIM_BASE: 85_000 + 21_000,
64
+ CLAIM_NATIVE_TRANSFER: 7_500,
65
+ CLAIM_ERC20_TRANSFER: 40_000,
66
+ CLAIM_EXECUTION_SCHEDULE: 30_000,
67
+
68
+ FRONT_BASE: 75_000 + 21_000,
69
+ FRONT_NATIVE_TRANSFER: 7_500,
70
+ FRONT_ERC20_TRANSFER: 40_000,
71
+ FRONT_EXECUTION_SCHEDULE: 30_000
62
72
  };
63
73
 
64
74
  readonly chainId: ChainId;
@@ -96,18 +106,26 @@ export class EVMSpvVaultContract<ChainId extends string>
96
106
  }
97
107
 
98
108
  protected async Deposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate: string): Promise<TransactionRequest> {
109
+ let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
99
110
  let value = 0n;
100
- if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
111
+ if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
101
112
  value += rawAmounts[0] * vault.token0.multiplier;
102
- if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
113
+ } else {
114
+ if(rawAmounts[0] > 0n) totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
115
+ }
116
+ if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
103
117
  value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
118
+ } else {
119
+ if(rawAmounts[1]!=null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase()!==vault.token1.token.toLowerCase())
120
+ totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
121
+ }
104
122
 
105
123
  const tx = await this.contract.deposit.populateTransaction(
106
124
  vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
107
125
  rawAmounts[0], rawAmounts[1] ?? 0n, { value }
108
126
  );
109
127
  tx.from = signer;
110
- EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.DEPOSIT, feeRate);
128
+ EVMFees.applyFeeRate(tx, totalGas, feeRate);
111
129
 
112
130
  return tx;
113
131
  }
@@ -128,7 +146,7 @@ export class EVMSpvVaultContract<ChainId extends string>
128
146
  { value }
129
147
  );
130
148
  tx.from = signer;
131
- EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.FRONT, feeRate);
149
+ EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
132
150
 
133
151
  return tx;
134
152
  }
@@ -143,7 +161,7 @@ export class EVMSpvVaultContract<ChainId extends string>
143
161
  )
144
162
 
145
163
  tx.from = signer;
146
- EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
164
+ EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
147
165
 
148
166
  return tx;
149
167
  }
@@ -520,14 +538,66 @@ export class EVMSpvVaultContract<ChainId extends string>
520
538
  return [tx];
521
539
  }
522
540
 
523
- async getClaimFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
541
+ getClaimGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
542
+ let totalGas = EVMSpvVaultContract.GasCosts.CLAIM_BASE;
543
+
544
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
545
+ const transferFee = vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
546
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
547
+ totalGas += transferFee;
548
+ if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
549
+ if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
550
+ }
551
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
552
+ const transferFee = vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
553
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
554
+ totalGas += transferFee;
555
+ if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
556
+ if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
557
+ }
558
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.CLAIM_EXECUTION_SCHEDULE;
559
+
560
+ return totalGas;
561
+ }
562
+
563
+ getFrontGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
564
+ let totalGas = EVMSpvVaultContract.GasCosts.FRONT_BASE;
565
+
566
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
567
+ totalGas += vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
568
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
569
+ }
570
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
571
+ totalGas += vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
572
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
573
+ }
574
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.FRONT_EXECUTION_SCHEDULE;
575
+
576
+ return totalGas;
577
+ }
578
+
579
+ async getClaimFee(signer: string, vault: EVMSpvVaultData, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
524
580
  feeRate ??= await this.Chain.Fees.getFeeRate();
525
- return EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.CLAIM, feeRate);
581
+ return EVMFees.getGasFee(this.getClaimGas(signer, vault, withdrawalData), feeRate);
526
582
  }
527
583
 
528
- async getFrontFee(signer: string, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
584
+ async getFrontFee(signer: string, vault?: EVMSpvVaultData, withdrawalData?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
585
+ vault ??= EVMSpvVaultData.randomVault();
529
586
  feeRate ??= await this.Chain.Fees.getFeeRate();
530
- return EVMFees.getGasFee(EVMSpvVaultContract.GasCosts.FRONT, feeRate);
587
+ let totalFee = EVMFees.getGasFee(this.getFrontGas(signer, vault, withdrawalData), feeRate);
588
+ if(withdrawalData==null || (withdrawalData.rawAmounts[0]!=null && withdrawalData.rawAmounts[0]>0n)) {
589
+ if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
590
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
591
+ }
592
+ }
593
+ if(withdrawalData==null || (withdrawalData.rawAmounts[1]!=null && withdrawalData.rawAmounts[1]>0n)) {
594
+ if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
595
+ if(vault.token1.token.toLowerCase()!==vault.token0.token.toLowerCase() || withdrawalData==null || withdrawalData.rawAmounts[0]==null || withdrawalData.rawAmounts[0]===0n) {
596
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
597
+ }
598
+ }
599
+ }
600
+ return totalFee;
531
601
  }
532
602
 
533
603
  }
@@ -11,8 +11,10 @@ import {
11
11
  SpvVaultParametersStruct,
12
12
  SpvVaultStateStruct
13
13
  } from "./SpvVaultContractTypechain";
14
- import {hexlify} from "ethers";
14
+ import {hexlify, randomBytes} from "ethers";
15
15
  import {AbiCoder, keccak256} from "ethers";
16
+ import {EVMAddresses} from "../chain/modules/EVMAddresses";
17
+ import type {AddressLike, BigNumberish, BytesLike} from "ethers/lib.esm";
16
18
 
17
19
  export function getVaultParamsCommitment(vaultParams: SpvVaultParametersStruct) {
18
20
  return keccak256(AbiCoder.defaultAbiCoder().encode(
@@ -196,6 +198,27 @@ export class EVMSpvVaultData extends SpvVaultData<EVMSpvWithdrawalData> {
196
198
  }
197
199
  }
198
200
 
201
+ static randomVault(): EVMSpvVaultData {
202
+ const spvVaultParams = {
203
+ btcRelayContract: EVMAddresses.randomAddress(),
204
+ token0: EVMAddresses.randomAddress(),
205
+ token1: EVMAddresses.randomAddress(),
206
+ token0Multiplier: 1n,
207
+ token1Multiplier: 1n,
208
+ confirmations: 3n,
209
+ }
210
+ return new EVMSpvVaultData(EVMAddresses.randomAddress(), 0n, {
211
+ spvVaultParametersCommitment: getVaultParamsCommitment(spvVaultParams),
212
+ utxoTxHash: randomBytes(32),
213
+ utxoVout: 0n,
214
+ openBlockheight: 0n,
215
+ withdrawCount: 0n,
216
+ depositCount: 0n,
217
+ token0Amount: 0n,
218
+ token1Amount: 0n
219
+ }, spvVaultParams);
220
+ }
221
+
199
222
  }
200
223
 
201
224
  SpvVaultData.deserializers["EVM"] = EVMSpvVaultData;
@@ -574,16 +574,16 @@ export class EVMSwapContract<ChainId extends string = string>
574
574
  }
575
575
 
576
576
  /**
577
- * Get the estimated solana fee of the commit transaction
577
+ * Get the estimated fee of the commit transaction
578
578
  */
579
- getCommitFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
579
+ getCommitFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
580
580
  return this.Init.getInitFee(swapData, feeRate);
581
581
  }
582
582
 
583
583
  /**
584
- * Get the estimated solana transaction fee of the refund transaction
584
+ * Get the estimated transaction fee of the refund transaction
585
585
  */
586
- getRefundFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
586
+ getRefundFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
587
587
  return this.Refund.getRefundFee(swapData, feeRate);
588
588
  }
589
589