@atomiqlabs/chain-evm 1.0.0-dev.22 → 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.
Files changed (59) hide show
  1. package/dist/chains/citrea/CitreaBtcRelay.d.ts +21 -0
  2. package/dist/chains/citrea/CitreaBtcRelay.js +43 -0
  3. package/dist/chains/citrea/CitreaChainType.d.ts +4 -4
  4. package/dist/chains/citrea/CitreaFees.d.ts +29 -0
  5. package/dist/chains/citrea/CitreaFees.js +67 -0
  6. package/dist/chains/citrea/CitreaInitializer.d.ts +3 -3
  7. package/dist/chains/citrea/CitreaInitializer.js +15 -8
  8. package/dist/chains/citrea/CitreaSpvVaultContract.d.ts +15 -0
  9. package/dist/chains/citrea/CitreaSpvVaultContract.js +75 -0
  10. package/dist/chains/citrea/CitreaSwapContract.d.ts +22 -0
  11. package/dist/chains/citrea/CitreaSwapContract.js +96 -0
  12. package/dist/chains/citrea/CitreaTokens.d.ts +9 -0
  13. package/dist/chains/citrea/CitreaTokens.js +20 -0
  14. package/dist/evm/btcrelay/EVMBtcRelay.d.ts +8 -1
  15. package/dist/evm/btcrelay/EVMBtcRelay.js +15 -11
  16. package/dist/evm/chain/EVMChainInterface.d.ts +6 -6
  17. package/dist/evm/chain/EVMChainInterface.js +1 -2
  18. package/dist/evm/chain/modules/EVMAddresses.d.ts +1 -0
  19. package/dist/evm/chain/modules/EVMAddresses.js +5 -0
  20. package/dist/evm/chain/modules/EVMFees.d.ts +8 -7
  21. package/dist/evm/chain/modules/EVMFees.js +3 -3
  22. package/dist/evm/chain/modules/EVMTokens.d.ts +2 -0
  23. package/dist/evm/chain/modules/EVMTokens.js +10 -2
  24. package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +17 -3
  25. package/dist/evm/spv_swap/EVMSpvVaultContract.js +83 -13
  26. package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +1 -0
  27. package/dist/evm/spv_swap/EVMSpvVaultData.js +21 -0
  28. package/dist/evm/swaps/EVMSwapContract.d.ts +4 -4
  29. package/dist/evm/swaps/EVMSwapContract.js +4 -4
  30. package/dist/evm/swaps/modules/EVMLpVault.js +2 -2
  31. package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +1 -0
  32. package/dist/evm/swaps/modules/EVMSwapClaim.js +40 -4
  33. package/dist/evm/swaps/modules/EVMSwapInit.d.ts +3 -3
  34. package/dist/evm/swaps/modules/EVMSwapInit.js +43 -9
  35. package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +2 -2
  36. package/dist/evm/swaps/modules/EVMSwapRefund.js +42 -7
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +1 -0
  39. package/package.json +2 -2
  40. package/src/chains/citrea/CitreaBtcRelay.ts +58 -0
  41. package/src/chains/citrea/CitreaChainType.ts +6 -6
  42. package/src/chains/citrea/CitreaFees.ts +77 -0
  43. package/src/chains/citrea/CitreaInitializer.ts +17 -6
  44. package/src/chains/citrea/CitreaSpvVaultContract.ts +76 -0
  45. package/src/chains/citrea/CitreaSwapContract.ts +103 -0
  46. package/src/chains/citrea/CitreaTokens.ts +22 -0
  47. package/src/evm/btcrelay/EVMBtcRelay.ts +17 -12
  48. package/src/evm/chain/EVMChainInterface.ts +7 -8
  49. package/src/evm/chain/modules/EVMAddresses.ts +6 -0
  50. package/src/evm/chain/modules/EVMFees.ts +10 -11
  51. package/src/evm/chain/modules/EVMTokens.ts +13 -2
  52. package/src/evm/spv_swap/EVMSpvVaultContract.ts +84 -14
  53. package/src/evm/spv_swap/EVMSpvVaultData.ts +24 -1
  54. package/src/evm/swaps/EVMSwapContract.ts +4 -4
  55. package/src/evm/swaps/modules/EVMLpVault.ts +2 -2
  56. package/src/evm/swaps/modules/EVMSwapClaim.ts +36 -4
  57. package/src/evm/swaps/modules/EVMSwapInit.ts +44 -10
  58. package/src/evm/swaps/modules/EVMSwapRefund.ts +38 -7
  59. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-evm",
3
- "version": "1.0.0-dev.22",
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"
@@ -0,0 +1,58 @@
1
+ import {EVMBtcRelay} from "../../evm/btcrelay/EVMBtcRelay";
2
+ import {BtcBlock} from "@atomiqlabs/base";
3
+ import {getLogger} from "../../utils/Utils";
4
+ import {CitreaFees} from "./CitreaFees";
5
+
6
+ const logger = getLogger("CitreaBtcRelay: ");
7
+
8
+ export class CitreaBtcRelay<B extends BtcBlock> extends EVMBtcRelay<B> {
9
+
10
+ public static StateDiffSize = {
11
+ STATE_DIFF_PER_BLOCKHEADER: 22,
12
+ STATE_DIFF_BASE: 30
13
+ }
14
+
15
+ /**
16
+ * Estimate required synchronization fee (worst case) to synchronize btc relay to the required blockheight
17
+ *
18
+ * @param requiredBlockheight
19
+ * @param feeRate
20
+ */
21
+ public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
22
+ feeRate ??= await this.Chain.Fees.getFeeRate();
23
+ const tipData = await this.getTipData();
24
+ const currBlockheight = tipData.blockheight;
25
+
26
+ const blockheightDelta = requiredBlockheight-currBlockheight;
27
+
28
+ if(blockheightDelta<=0) return 0n;
29
+
30
+ const numTxs = Math.ceil(blockheightDelta / this.maxHeadersPerTx);
31
+
32
+ const synchronizationFee = (BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate))
33
+ + CitreaFees.getGasFee(
34
+ EVMBtcRelay.GasCosts.GAS_BASE_MAIN * numTxs,
35
+ feeRate,
36
+ CitreaBtcRelay.StateDiffSize.STATE_DIFF_BASE * numTxs
37
+ );
38
+ logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
39
+ " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
40
+
41
+ return synchronizationFee;
42
+ }
43
+
44
+ /**
45
+ * Returns fee required (in native token) to synchronize a single block to btc relay
46
+ *
47
+ * @param feeRate
48
+ */
49
+ public async getFeePerBlock(feeRate?: string): Promise<bigint> {
50
+ feeRate ??= await this.Chain.Fees.getFeeRate();
51
+ return CitreaFees.getGasFee(
52
+ EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER,
53
+ feeRate,
54
+ CitreaBtcRelay.StateDiffSize.STATE_DIFF_PER_BLOCKHEADER
55
+ );
56
+ }
57
+
58
+ }
@@ -3,13 +3,13 @@ import {EVMPreFetchVerification} from "../../evm/swaps/modules/EVMSwapInit";
3
3
  import {EVMTx} from "../../evm/chain/modules/EVMTransactions";
4
4
  import {EVMSigner} from "../../evm/wallet/EVMSigner";
5
5
  import {EVMSwapData} from "../../evm/swaps/EVMSwapData";
6
- import {EVMSwapContract} from "../../evm/swaps/EVMSwapContract";
7
6
  import {EVMChainInterface} from "../../evm/chain/EVMChainInterface";
8
7
  import {EVMChainEventsBrowser} from "../../evm/events/EVMChainEventsBrowser";
9
- import {EVMBtcRelay} from "../../evm/btcrelay/EVMBtcRelay";
10
8
  import { EVMSpvVaultData } from "../../evm/spv_swap/EVMSpvVaultData";
11
9
  import { EVMSpvWithdrawalData } from "../../evm/spv_swap/EVMSpvWithdrawalData";
12
- import {EVMSpvVaultContract} from "../../evm/spv_swap/EVMSpvVaultContract";
10
+ import {CitreaSwapContract} from "./CitreaSwapContract";
11
+ import {CitreaBtcRelay} from "./CitreaBtcRelay";
12
+ import {CitreaSpvVaultContract} from "./CitreaSpvVaultContract";
13
13
 
14
14
  export type CitreaChainType = ChainType<
15
15
  "CITREA",
@@ -18,11 +18,11 @@ export type CitreaChainType = ChainType<
18
18
  EVMTx,
19
19
  EVMSigner,
20
20
  EVMSwapData,
21
- EVMSwapContract<"CITREA">,
21
+ CitreaSwapContract,
22
22
  EVMChainInterface<"CITREA", 5115>,
23
23
  EVMChainEventsBrowser,
24
- EVMBtcRelay<any>,
24
+ CitreaBtcRelay<any>,
25
25
  EVMSpvVaultData,
26
26
  EVMSpvWithdrawalData,
27
- EVMSpvVaultContract<"CITREA">
27
+ CitreaSpvVaultContract
28
28
  >;
@@ -0,0 +1,77 @@
1
+ import {EVMFees} from "../../evm/chain/modules/EVMFees";
2
+ import {getLogger} from "../../utils/Utils";
3
+
4
+ export class CitreaFees extends EVMFees {
5
+
6
+ public static readonly StateDiffSize = {
7
+ APPROVE_DIFF_SIZE: 40,
8
+ };
9
+
10
+ protected readonly logger = getLogger("CitreaFees: ");
11
+
12
+ private _blockFeeCache: {
13
+ timestamp: number,
14
+ feeRate: Promise<{baseFee: bigint, l1Fee: bigint}>
15
+ } = null;
16
+
17
+ /**
18
+ * Gets evm fee rate
19
+ *
20
+ * @private
21
+ * @returns {Promise<bigint>} L1 gas price denominated in Wei
22
+ */
23
+ private async __getFeeRate(): Promise<{baseFee: bigint, l1Fee: bigint}> {
24
+ const res = await this.provider.send("eth_getBlockByNumber", ["latest", false]);
25
+ const l1Fee = BigInt(res.l1FeeRate);
26
+ const baseFee = BigInt(res.baseFeePerGas) * this.feeMultiplierPPM / 1_000_000n;
27
+
28
+ this.logger.debug("__getFeeRate(): Base fee rate: "+baseFee.toString(10)+", l1 fee rate: "+l1Fee.toString(10));
29
+
30
+ return {baseFee, l1Fee};
31
+ }
32
+
33
+ /**
34
+ * Gets the gas price with caching, format: <gas price in Wei>;<transaction version: v1/v3>
35
+ *
36
+ * @private
37
+ */
38
+ public async getFeeRate(): Promise<string> {
39
+ if(this._blockFeeCache==null || Date.now() - this._blockFeeCache.timestamp > this.MAX_FEE_AGE) {
40
+ let obj = {
41
+ timestamp: Date.now(),
42
+ feeRate: null
43
+ };
44
+ obj.feeRate = this.__getFeeRate().catch(e => {
45
+ if(this._blockFeeCache===obj) this._blockFeeCache=null;
46
+ throw e;
47
+ });
48
+ this._blockFeeCache = obj;
49
+ }
50
+
51
+ let {baseFee, l1Fee} = await this._blockFeeCache.feeRate;
52
+ if(baseFee>this.maxFeeRatePerGas) baseFee = this.maxFeeRatePerGas;
53
+
54
+ const fee = baseFee.toString(10)+","+this.priorityFee.toString(10)+","+l1Fee.toString(10);
55
+
56
+ this.logger.debug("getFeeRate(): calculated fee: "+fee);
57
+
58
+ return fee;
59
+ }
60
+
61
+
62
+ /**
63
+ * Calculates the total gas fee paid for a given gas limit and state diff size at a given fee rate
64
+ *
65
+ * @param gas
66
+ * @param stateDiffSize
67
+ * @param feeRate
68
+ */
69
+ public static getGasFee(gas: number, feeRate: string, stateDiffSize: number = 0): bigint {
70
+ if(feeRate==null) return 0n;
71
+
72
+ const [maxFee, priorityFee, l1StateDiffFee] = feeRate.split(",");
73
+
74
+ return (BigInt(gas) * BigInt(maxFee)) + (BigInt(stateDiffSize) * BigInt(l1StateDiffFee ?? 0n));
75
+ }
76
+
77
+ }
@@ -10,6 +10,11 @@ import {EVMChainEventsBrowser} from "../../evm/events/EVMChainEventsBrowser";
10
10
  import {EVMSwapData} from "../../evm/swaps/EVMSwapData";
11
11
  import {EVMSpvVaultData} from "../../evm/spv_swap/EVMSpvVaultData";
12
12
  import {EVMSpvWithdrawalData} from "../../evm/spv_swap/EVMSpvWithdrawalData";
13
+ import {CitreaFees} from "./CitreaFees";
14
+ import {CitreaBtcRelay} from "./CitreaBtcRelay";
15
+ import {CitreaSwapContract} from "./CitreaSwapContract";
16
+ import {CitreaTokens} from "./CitreaTokens";
17
+ import {CitreaSpvVaultContract} from "./CitreaSpvVaultContract";
13
18
 
14
19
  const CitreaChainIds = {
15
20
  MAINNET: null,
@@ -57,12 +62,17 @@ const CitreaContractAddresses = {
57
62
  }
58
63
  };
59
64
 
60
- export type CitreaAssetsType = BaseTokenType<"CBTC">;
65
+ export type CitreaAssetsType = BaseTokenType<"CBTC" | "USDC">;
61
66
  export const CitreaAssets: CitreaAssetsType = {
62
67
  CBTC: {
63
68
  address: "0x0000000000000000000000000000000000000000",
64
69
  decimals: 18,
65
70
  displayDecimals: 8
71
+ },
72
+ USDC: {
73
+ address: "0x2C8abD2A528D19AFc33d2ebA507c0F405c131335",
74
+ decimals: 6,
75
+ displayDecimals: 6
66
76
  }
67
77
  } as const;
68
78
 
@@ -86,7 +96,7 @@ export type CitreaOptions = {
86
96
  }
87
97
  }
88
98
 
89
- fees?: EVMFees
99
+ fees?: CitreaFees
90
100
  }
91
101
 
92
102
  export function initializeCitrea(
@@ -112,19 +122,20 @@ export function initializeCitrea(
112
122
  new JsonRpcProvider(options.rpcUrl, {name: "Citrea", chainId}) :
113
123
  options.rpcUrl;
114
124
 
115
- const Fees = options.fees ?? new EVMFees(provider, 2n * 1_000_000_000n, 1_000_000n);
125
+ const Fees = options.fees ?? new CitreaFees(provider, 2n * 1_000_000_000n, 1_000_000n);
116
126
 
117
127
  const chainInterface = new EVMChainInterface("CITREA", chainId, provider, {
118
128
  safeBlockTag: "latest",
119
129
  maxLogsBlockRange: options.maxLogsBlockRange ?? 500
120
130
  }, options.retryPolicy, Fees);
131
+ chainInterface.Tokens = new CitreaTokens(chainInterface); //Override with custom token module allowing l1 state diff based fee calculation
121
132
 
122
- const btcRelay = new EVMBtcRelay(
133
+ const btcRelay = new CitreaBtcRelay(
123
134
  chainInterface, bitcoinRpc, network, options.btcRelayContract ?? defaultContractAddresses.btcRelayContract,
124
135
  options.btcRelayDeploymentHeight ?? defaultContractAddresses.btcRelayDeploymentHeight
125
136
  );
126
137
 
127
- const swapContract = new EVMSwapContract(
138
+ const swapContract = new CitreaSwapContract(
128
139
  chainInterface, btcRelay, options.swapContract ?? defaultContractAddresses.swapContract, {
129
140
  refund: {
130
141
  ...defaultContractAddresses.handlerContracts.refund,
@@ -137,7 +148,7 @@ export function initializeCitrea(
137
148
  }
138
149
  );
139
150
 
140
- const spvVaultContract = new EVMSpvVaultContract(
151
+ const spvVaultContract = new CitreaSpvVaultContract(
141
152
  chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract,
142
153
  options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight
143
154
  )
@@ -0,0 +1,76 @@
1
+ import {EVMSpvVaultContract} from "../../evm/spv_swap/EVMSpvVaultContract";
2
+ import {EVMSpvWithdrawalData} from "../../evm/spv_swap/EVMSpvWithdrawalData";
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";
8
+
9
+
10
+ export class CitreaSpvVaultContract extends EVMSpvVaultContract<"CITREA"> {
11
+
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();
35
+ feeRate ??= await this.Chain.Fees.getFeeRate();
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);
55
+ }
56
+
57
+ async getFrontFee(signer: string, vault?: EVMSpvVaultData, data?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
58
+ vault ??= EVMSpvVaultData.randomVault();
59
+ feeRate ??= await this.Chain.Fees.getFeeRate();
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);
74
+ }
75
+
76
+ }
@@ -0,0 +1,103 @@
1
+ import {EVMSwapContract} from "../../evm/swaps/EVMSwapContract";
2
+ import {EVMSwapData} from "../../evm/swaps/EVMSwapData";
3
+ import {CitreaFees} from "./CitreaFees";
4
+
5
+ export class CitreaSwapContract extends EVMSwapContract<"CITREA"> {
6
+
7
+ public static readonly StateDiffSize = {
8
+ BASE_DIFF_SIZE: 35,
9
+ REPUTATION_UPDATE_DIFF_SIZE: 25,
10
+ LP_VAULT_UPDATE_DIFF_SIZE: 25,
11
+ ERC_20_TRANSFER_DIFF_SIZE: 50,
12
+ NATIVE_SELF_TRANSFER_DIFF_SIZE: 20,
13
+ NATIVE_TRANSFER_DIFF_SIZE: 30
14
+ };
15
+
16
+ private calculateStateDiff(signer: string, tokenStateChanges: Set<string>): number {
17
+ let stateDiffSize = 0;
18
+ tokenStateChanges.forEach(val => {
19
+ const [address, token] = val.split(":");
20
+ if(token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
21
+ stateDiffSize += address.toLowerCase()===signer?.toLowerCase() ? CitreaSwapContract.StateDiffSize.NATIVE_SELF_TRANSFER_DIFF_SIZE : CitreaSwapContract.StateDiffSize.NATIVE_TRANSFER_DIFF_SIZE;
22
+ } else {
23
+ stateDiffSize += CitreaSwapContract.StateDiffSize.ERC_20_TRANSFER_DIFF_SIZE;
24
+ }
25
+ });
26
+ return stateDiffSize;
27
+ }
28
+
29
+ /**
30
+ * Get the estimated solana fee of the commit transaction
31
+ */
32
+ async getCommitFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
33
+ feeRate ??= await this.Chain.Fees.getFeeRate();
34
+
35
+ const tokenStateChanges: Set<string> = new Set();
36
+
37
+ let diffSize = CitreaSwapContract.StateDiffSize.BASE_DIFF_SIZE;
38
+ if(!swapData.isPayIn()) {
39
+ diffSize += CitreaSwapContract.StateDiffSize.LP_VAULT_UPDATE_DIFF_SIZE;
40
+ } else {
41
+ tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getToken().toLowerCase());
42
+ }
43
+ if(swapData.getTotalDeposit()>0n) {
44
+ tokenStateChanges.add(signer.toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
45
+ }
46
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
47
+
48
+ const gasFee = await this.Init.getInitFee(swapData, feeRate);
49
+ return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
50
+ }
51
+
52
+ async getClaimFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
53
+ feeRate ??= await this.Chain.Fees.getFeeRate();
54
+
55
+ const tokenStateChanges: Set<string> = new Set();
56
+
57
+ let diffSize = CitreaSwapContract.StateDiffSize.BASE_DIFF_SIZE;
58
+ if(swapData.reputation) diffSize += CitreaSwapContract.StateDiffSize.REPUTATION_UPDATE_DIFF_SIZE;
59
+ if(!swapData.isPayOut()) {
60
+ diffSize += CitreaSwapContract.StateDiffSize.LP_VAULT_UPDATE_DIFF_SIZE;
61
+ } else {
62
+ tokenStateChanges.add(swapData.getClaimer().toLowerCase()+":"+swapData.getToken().toLowerCase());
63
+ }
64
+ if(swapData.getClaimerBounty() > 0) {
65
+ tokenStateChanges.add(signer.toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
66
+ }
67
+ if(swapData.getSecurityDeposit() > swapData.getClaimerBounty()) {
68
+ tokenStateChanges.add(swapData.getClaimer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
69
+ }
70
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
71
+
72
+ const gasFee = await this.Claim.getClaimFee(swapData, feeRate);
73
+ return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
74
+ }
75
+
76
+ /**
77
+ * Get the estimated solana transaction fee of the refund transaction
78
+ */
79
+ async getRefundFee(signer: string, swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
80
+ feeRate ??= await this.Chain.Fees.getFeeRate();
81
+
82
+ const tokenStateChanges: Set<string> = new Set();
83
+
84
+ let diffSize = CitreaSwapContract.StateDiffSize.BASE_DIFF_SIZE;
85
+ if(swapData.reputation) diffSize += CitreaSwapContract.StateDiffSize.REPUTATION_UPDATE_DIFF_SIZE;
86
+ if(!swapData.isPayIn()) {
87
+ diffSize += CitreaSwapContract.StateDiffSize.LP_VAULT_UPDATE_DIFF_SIZE;
88
+ } else {
89
+ tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getToken().toLowerCase());
90
+ }
91
+ if(swapData.getSecurityDeposit() > 0) {
92
+ tokenStateChanges.add(swapData.getOfferer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
93
+ }
94
+ if(swapData.getClaimerBounty() > swapData.getSecurityDeposit()) {
95
+ tokenStateChanges.add(swapData.getClaimer().toLowerCase()+":"+swapData.getDepositToken().toLowerCase());
96
+ }
97
+ diffSize += this.calculateStateDiff(signer, tokenStateChanges);
98
+
99
+ const gasFee = await this.Refund.getRefundFee(swapData, feeRate);
100
+ return gasFee + CitreaFees.getGasFee(0, feeRate, diffSize);
101
+ }
102
+
103
+ }
@@ -0,0 +1,22 @@
1
+ import {EVMTokens} from "../../evm/chain/modules/EVMTokens";
2
+ import {CitreaFees} from "./CitreaFees";
3
+
4
+
5
+ export class CitreaTokens extends EVMTokens {
6
+
7
+ public static readonly StateDiffSize = {
8
+ APPROVE_DIFF_SIZE: 35,
9
+ TRANSFER_DIFF_SIZE: 55
10
+ };
11
+
12
+ async getApproveFee(feeRate?: string): Promise<bigint> {
13
+ feeRate ??= await this.root.Fees.getFeeRate();
14
+ return CitreaFees.getGasFee(EVMTokens.GasCosts.APPROVE, feeRate, CitreaTokens.StateDiffSize.APPROVE_DIFF_SIZE);
15
+ }
16
+
17
+ async getTransferFee(feeRate?: string): Promise<bigint> {
18
+ feeRate ??= await this.root.Fees.getFeeRate();
19
+ return CitreaFees.getGasFee(EVMTokens.GasCosts.APPROVE, feeRate, CitreaTokens.StateDiffSize.TRANSFER_DIFF_SIZE);
20
+ }
21
+
22
+ }
@@ -23,25 +23,27 @@ function serializeBlockHeader(e: BtcBlock): EVMBtcHeader {
23
23
  });
24
24
  }
25
25
 
26
- const GAS_PER_BLOCKHEADER = 30_000;
27
- const GAS_BASE_MAIN = 15_000;
28
- const GAS_PER_BLOCKHEADER_FORK = 65_000;
29
- const GAS_PER_BLOCKHEADER_FORKED = 10_000;
30
- const GAS_BASE_FORK = 25_000;
31
-
32
26
  const logger = getLogger("EVMBtcRelay: ");
33
27
 
34
28
  export class EVMBtcRelay<B extends BtcBlock>
35
29
  extends EVMContractBase<BtcRelayTypechain>
36
30
  implements BtcRelay<EVMBtcStoredHeader, EVMTx, B, EVMSigner> {
37
31
 
32
+ public static GasCosts = {
33
+ GAS_PER_BLOCKHEADER: 30_000,
34
+ GAS_BASE_MAIN: 15_000 + 21_000,
35
+ GAS_PER_BLOCKHEADER_FORK: 65_000,
36
+ GAS_PER_BLOCKHEADER_FORKED: 10_000,
37
+ GAS_BASE_FORK: 25_000 + 21_000
38
+ }
39
+
38
40
  public async SaveMainHeaders(signer: string, mainHeaders: EVMBtcHeader[], storedHeader: EVMBtcStoredHeader, feeRate: string): Promise<EVMTx> {
39
41
  const tx = await this.contract.submitMainBlockheaders.populateTransaction(Buffer.concat([
40
42
  storedHeader.serialize(),
41
43
  Buffer.concat(mainHeaders.map(header => header.serializeCompact()))
42
44
  ]));
43
45
  tx.from = signer;
44
- EVMFees.applyFeeRate(tx, GAS_BASE_MAIN + (GAS_PER_BLOCKHEADER * mainHeaders.length), feeRate);
46
+ EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * mainHeaders.length), feeRate);
45
47
  return tx;
46
48
  }
47
49
 
@@ -51,7 +53,7 @@ export class EVMBtcRelay<B extends BtcBlock>
51
53
  Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
52
54
  ]));
53
55
  tx.from = signer;
54
- EVMFees.applyFeeRate(tx, GAS_BASE_MAIN + (GAS_PER_BLOCKHEADER * forkHeaders.length), feeRate);
56
+ EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_MAIN + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER * forkHeaders.length), feeRate);
55
57
  return tx;
56
58
  }
57
59
 
@@ -61,7 +63,7 @@ export class EVMBtcRelay<B extends BtcBlock>
61
63
  Buffer.concat(forkHeaders.map(header => header.serializeCompact()))
62
64
  ]));
63
65
  tx.from = signer;
64
- EVMFees.applyFeeRate(tx, GAS_BASE_FORK + (GAS_PER_BLOCKHEADER_FORK * forkHeaders.length) + (GAS_PER_BLOCKHEADER_FORKED * totalForkHeaders), feeRate);
66
+ EVMFees.applyFeeRate(tx, EVMBtcRelay.GasCosts.GAS_BASE_FORK + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORK * forkHeaders.length) + (EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER_FORKED * totalForkHeaders), feeRate);
65
67
  return tx;
66
68
  }
67
69
 
@@ -398,6 +400,8 @@ export class EVMBtcRelay<B extends BtcBlock>
398
400
  * @param feeRate
399
401
  */
400
402
  public async estimateSynchronizeFee(requiredBlockheight: number, feeRate?: string): Promise<bigint> {
403
+ feeRate ??= await this.Chain.Fees.getFeeRate();
404
+
401
405
  const tipData = await this.getTipData();
402
406
  const currBlockheight = tipData.blockheight;
403
407
 
@@ -405,7 +409,8 @@ export class EVMBtcRelay<B extends BtcBlock>
405
409
 
406
410
  if(blockheightDelta<=0) return 0n;
407
411
 
408
- const synchronizationFee = BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate);
412
+ const synchronizationFee = (BigInt(blockheightDelta) * await this.getFeePerBlock(feeRate))
413
+ + EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_BASE_MAIN * Math.ceil(blockheightDelta / this.maxHeadersPerTx), feeRate);
409
414
  logger.debug("estimateSynchronizeFee(): required blockheight: "+requiredBlockheight+
410
415
  " blockheight delta: "+blockheightDelta+" fee: "+synchronizationFee.toString(10));
411
416
 
@@ -413,13 +418,13 @@ export class EVMBtcRelay<B extends BtcBlock>
413
418
  }
414
419
 
415
420
  /**
416
- * Returns fee required (in SOL) to synchronize a single block to btc relay
421
+ * Returns fee required (in native token) to synchronize a single block to btc relay
417
422
  *
418
423
  * @param feeRate
419
424
  */
420
425
  public async getFeePerBlock(feeRate?: string): Promise<bigint> {
421
426
  feeRate ??= await this.Chain.Fees.getFeeRate();
422
- return EVMFees.getGasFee(GAS_PER_BLOCKHEADER, feeRate);
427
+ return EVMFees.getGasFee(EVMBtcRelay.GasCosts.GAS_PER_BLOCKHEADER, feeRate);
423
428
  }
424
429
 
425
430
  /**
@@ -33,13 +33,13 @@ export class EVMChainInterface<ChainId extends string = string, EVMChainId exten
33
33
  public readonly config: EVMConfiguration;
34
34
 
35
35
  public Fees: EVMFees;
36
- public readonly Tokens: EVMTokens;
37
- public readonly Transactions: EVMTransactions;
38
- public readonly Signatures: EVMSignatures;
39
- public readonly Events: EVMEvents;
40
- public readonly Blocks: EVMBlocks;
36
+ public Tokens: EVMTokens;
37
+ public Transactions: EVMTransactions;
38
+ public Signatures: EVMSignatures;
39
+ public Events: EVMEvents;
40
+ public Blocks: EVMBlocks;
41
41
 
42
- protected readonly logger: LoggerType;
42
+ protected logger: LoggerType;
43
43
 
44
44
  constructor(
45
45
  chainId: ChainId,
@@ -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
  }
@@ -1,7 +1,5 @@
1
1
  import { getLogger } from "../../../utils/Utils";
2
- import {Provider, TransactionRequest} from "ethers";
3
-
4
- const MAX_FEE_AGE = 5000;
2
+ import {JsonRpcApiProvider, TransactionRequest} from "ethers";
5
3
 
6
4
  export type EVMFeeRate = {
7
5
  maxFeePerGas: bigint;
@@ -9,14 +7,15 @@ export type EVMFeeRate = {
9
7
  };
10
8
 
11
9
  export class EVMFees {
10
+ protected MAX_FEE_AGE = 5000;
12
11
 
13
- private readonly logger = getLogger("EVMFees: ");
12
+ protected readonly logger = getLogger("EVMFees: ");
14
13
 
15
- private readonly provider: Provider;
16
- private readonly maxFeeRatePerGas: bigint;
17
- private readonly priorityFee: bigint;
14
+ protected readonly provider: JsonRpcApiProvider;
15
+ protected readonly maxFeeRatePerGas: bigint;
16
+ protected readonly priorityFee: bigint;
18
17
 
19
- private readonly feeMultiplierPPM: bigint;
18
+ protected readonly feeMultiplierPPM: bigint;
20
19
 
21
20
  private blockFeeCache: {
22
21
  timestamp: number,
@@ -24,7 +23,7 @@ export class EVMFees {
24
23
  } = null;
25
24
 
26
25
  constructor(
27
- provider: Provider,
26
+ provider: JsonRpcApiProvider,
28
27
  maxFeeRatePerGas: bigint = 500n * 1_000_000_000n,
29
28
  priorityFee: bigint = 1n * 1_000_000_000n,
30
29
  feeMultiplier: number = 1.25,
@@ -56,7 +55,7 @@ export class EVMFees {
56
55
  * @private
57
56
  */
58
57
  public async getFeeRate(): Promise<string> {
59
- if(this.blockFeeCache==null || Date.now() - this.blockFeeCache.timestamp > MAX_FEE_AGE) {
58
+ if(this.blockFeeCache==null || Date.now() - this.blockFeeCache.timestamp > this.MAX_FEE_AGE) {
60
59
  let obj = {
61
60
  timestamp: Date.now(),
62
61
  feeRate: null
@@ -99,7 +98,7 @@ export class EVMFees {
99
98
 
100
99
  tx.maxFeePerGas = BigInt(baseFee) + BigInt(priorityFee);
101
100
  tx.maxPriorityFeePerGas = BigInt(priorityFee);
102
- tx.gasLimit = BigInt(gas) + 21_000n;
101
+ tx.gasLimit = BigInt(gas);
103
102
  }
104
103
 
105
104
  }