diff --git a/CHANGELOG.md b/CHANGELOG.md index 5868a904..d88d07e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.16.0] - 2024-MAY-1 + +- Upgraded axelar-cgp-solidity to `6.3.0` to enable support for Node.js version >= 20 +- Fixed an issue with the generation of `commandId` +- Changed the type of `executeData` from `0x${string}` to `string` to ensure compatibility with most libraries and clients +- Corrected the documentation for the `estimateGasFee` function regarding gas limit calculation +- Resolved an issue with the calculation of L1 gas fees when the source and destination tokens have different decimal places + ## [0.15.0] - 2024-MARCH-20 - Improved the accuracy of `estimateGasFee` function by incorporating L1 rollup fee for destination L2 chain. diff --git a/package.json b/package.json index 1eb8f7aa..9a472160 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axelar-network/axelarjs-sdk", - "version": "0.15.0", + "version": "0.16.0", "description": "The JavaScript SDK for Axelar Network", "repository": { "type": "git", @@ -41,7 +41,7 @@ "author": "Axelar Network", "license": "MIT", "dependencies": { - "@axelar-network/axelar-cgp-solidity": "^4.5.0", + "@axelar-network/axelar-cgp-solidity": "^6.3.0", "@axelar-network/axelarjs-types": "^0.33.0", "@cosmjs/json-rpc": "^0.30.1", "@cosmjs/stargate": "0.31.0-alpha.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f838a98..7067b174 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@axelar-network/axelar-cgp-solidity': - specifier: ^4.5.0 - version: 4.5.0 + specifier: ^6.3.0 + version: 6.3.0 '@axelar-network/axelarjs-types': specifier: ^0.33.0 version: 0.33.0 @@ -165,6 +165,19 @@ packages: /@axelar-network/axelar-cgp-solidity@4.5.0: resolution: {integrity: sha512-4F4rmHei0cmzeUR7/mW4Bap5rc/KlPV2crD9HA7HTRfl15mVcN6/3z8p+pAm9We6bOrQplNW9KBZ3HJFP3C1Gw==} engines: {node: ^16.0.0 || ^18.0.0} + dev: true + + /@axelar-network/axelar-cgp-solidity@6.3.0: + resolution: {integrity: sha512-dHE2UgaFZvQvL0ythMi+aBsr4t+eR6zrVGmCU5BxslEqi6Tx+Wmjwg1O1p15EqoztalSqRKj+r57n3GtgeUpig==} + engines: {node: '>=18'} + dependencies: + '@axelar-network/axelar-gmp-sdk-solidity': 5.7.0 + dev: false + + /@axelar-network/axelar-gmp-sdk-solidity@5.7.0: + resolution: {integrity: sha512-JlokiWFxvR6bFQtDjdErtk0mZrr3GH1A8bKps1zVP/Bu4XOHR0WsrWGPVhWIbvT8a8Ag3dva4hskBYgdq+pLig==} + engines: {node: '>=18'} + dev: false /@axelar-network/axelar-local-dev@1.0.2: resolution: {integrity: sha512-sb+8gLNF/xMztN2m05mm8Rh1hpG298DPOqEtOZsQz0LTbhzFe/yKoIgOuO++0G9w5o4u/skTQA1SOSpMWC0M/w==} diff --git a/src/libs/AxelarQueryAPI.ts b/src/libs/AxelarQueryAPI.ts index b5194ab4..c3361cb9 100644 --- a/src/libs/AxelarQueryAPI.ts +++ b/src/libs/AxelarQueryAPI.ts @@ -296,7 +296,7 @@ export class AxelarQueryAPI { ); } - // Calculate the L1 execution fee. This value is in ETH. + // Calculate the L1 execution fee. This value is in ETH wei. l1ExecutionFee = await this.estimateL1GasFee(destChainId, { executeData: executeData || DEFAULT_L1_EXECUTE_DATA, l1GasPrice: destToken.l1_gas_price_in_units, @@ -310,10 +310,18 @@ export class AxelarQueryAPI { const ethTokenPrice = Number(ethereumToken.token_price.usd); const ethToSrcTokenPriceRatio = ethTokenPrice / srcTokenPrice; - const actualL1ExecutionFee = l1ExecutionFee + let actualL1ExecutionFee = l1ExecutionFee .mul(Math.ceil(ethToSrcTokenPriceRatio * 1000)) .div(1000); + if (sourceToken.decimals !== destToken.decimals) { + actualL1ExecutionFee = BigNumberUtils.convertTokenAmount( + actualL1ExecutionFee, + destToken.decimals, + sourceToken.decimals + ); + } + l1ExecutionFee = BigNumber.from(actualL1ExecutionFee.toString()); // Calculate the L1 execution fee with the gas multiplier diff --git a/src/libs/BigNumberUtils.ts b/src/libs/BigNumberUtils.ts index a1f4c3d8..73822e4a 100644 --- a/src/libs/BigNumberUtils.ts +++ b/src/libs/BigNumberUtils.ts @@ -16,4 +16,12 @@ export class BigNumberUtils { public static divideToGetWei(bn: BigNumber | string, number: string, units: number): BigNumber { return BigNumber.from(bn).div(parseUnits(number, units)); } + + public static convertTokenAmount( + ethAmount: BigNumber, + sourceDecimals: number, + targetDecimals: number + ) { + return ethAmount.mul(parseUnits("1", targetDecimals)).div(parseUnits("1", sourceDecimals)); + } } diff --git a/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts b/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts index b3a71309..1ff57367 100644 --- a/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts +++ b/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts @@ -173,8 +173,9 @@ export class AxelarGMPRecoveryAPI extends AxelarRecoveryApi { }); } - public getCidFromSrcTxHash(destChainId: string, txHash: string, eventIndex: number) { - return getCommandId(destChainId, txHash, eventIndex, this.environment, rpcInfo); + public getCidFromSrcTxHash(destChainId: string, messageId: string, eventIndex: number) { + const chainId = rpcInfo[this.environment].networkInfo[destChainId.toLowerCase()]?.chainId; + return getCommandId(messageId, eventIndex, chainId); } public async doesTxMeetConfirmHt(chain: string, txHash: string, provider?: JsonRpcProvider) { @@ -244,8 +245,10 @@ export class AxelarGMPRecoveryAPI extends AxelarRecoveryApi { }; } - const commandId = this.getCidFromSrcTxHash(destChainId, srcTxHash, eventIndex); const eventResponse = await this.axelarQueryApi.getEVMEvent(srcChainId, srcTxHash, eventIndex); + const isCallContract = eventResponse?.event?.contractCall ? true : false; + const messageId = isCallContract ? `${srcTxHash}-${eventIndex}` : srcTxHash; + const commandId = this.getCidFromSrcTxHash(destChainId, messageId, eventIndex); if (!eventResponse || this.isEVMEventFailed(eventResponse)) { const errorMessage = this.isEVMEventFailed(eventResponse) @@ -1149,7 +1152,7 @@ export class AxelarGMPRecoveryAPI extends AxelarRecoveryApi { const executeParams = response.data as ExecuteParams; const gasLimitBuffer = evmWalletDetails?.gasLimitBuffer || 0; - const { destinationChain, destinationContractAddress, srcTxInfo } = executeParams; + const { destinationChain, destinationContractAddress } = executeParams; const signer = this.getSigner( destinationChain, diff --git a/src/libs/TransactionRecoveryApi/helpers/getCommandId.ts b/src/libs/TransactionRecoveryApi/helpers/getCommandId.ts index 06a8bb37..f2bd1413 100644 --- a/src/libs/TransactionRecoveryApi/helpers/getCommandId.ts +++ b/src/libs/TransactionRecoveryApi/helpers/getCommandId.ts @@ -1,24 +1,18 @@ -import { arrayify, keccak256 } from "ethers/lib/utils"; -import { Environment } from "src/libs/types"; -import { RPCInfoType } from "../constants/chain"; +import { arrayify, concat, hexlify, hexZeroPad, keccak256 } from "ethers/lib/utils"; -export const getCommandId = ( - chainName: string, - txHash: string, - sourceEventIndex: number, - environment: Environment, - rpcInfo: RPCInfoType -) => { - const chainID: number = rpcInfo[environment].networkInfo[chainName.toLowerCase()]?.chainId; - if (!chainID) return ""; - const seiArr = arrayify(sourceEventIndex).reverse(); - const txHashWithEventIndex = new Uint8Array([ - ...arrayify(txHash), - ...new Uint8Array(8).map((a, i) => seiArr[i] || a), - ]); - const chainIdByteArray = arrayify(chainID); - const dataToHash = new Uint8Array(txHashWithEventIndex.length + chainIdByteArray.length); - dataToHash.set(txHashWithEventIndex, 0); - dataToHash.set(chainIdByteArray, txHashWithEventIndex.length); - return keccak256(dataToHash).slice(2); // remove 0x prefix +const stringToCharcodeArray = (text: string) => Array.from(text, (char) => char.charCodeAt(0)); + +// This function is specifically designed for use with EVM-based chains. Its behavior may not be as expected if used with Cosmos-based chains or other types of chains. +export const getCommandId = (messageId: string, sourceEventIndex: number, chainId: number) => { + if (messageId.includes("-")) { + return keccak256(concat([stringToCharcodeArray(messageId), hexlify(chainId)])); + } else { + return keccak256( + concat([ + messageId, + arrayify(hexZeroPad(hexlify(sourceEventIndex), 8)).reverse(), + hexlify(chainId), + ]) + ); + } }; diff --git a/src/libs/test/AxelarQueryAPI.spec.ts b/src/libs/test/AxelarQueryAPI.spec.ts index 86298a23..af6035de 100644 --- a/src/libs/test/AxelarQueryAPI.spec.ts +++ b/src/libs/test/AxelarQueryAPI.spec.ts @@ -15,7 +15,10 @@ const MOCK_EXECUTE_DATA = "0x1a98b2e0e68ba0eb84262d4bcf91955ec2680b37bcedd59a1f48e18d183dac9961bf9d1400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000d40000000000000000000000000000000000000000000000000000000000deac2c6000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bc000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004607cad6135d7a119185ebe062d3b369b1b536ef000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000000000000000a8000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000001000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f4052150000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd3000000000000000000000000000000000000000000000000000000000deac2c6000000000000000000000000000000000000000000000000000000000dc647500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f40521500000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd3000000000000000000000000000000000000000000000000000000000de83dbf000000000000000000000000000000000000000000000000015d8c7908dbe7130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004607cad6135d7a119185ebe062d3b369b1b536ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000761786c5553444300000000000000000000000000000000000000000000000000"; describe("AxelarQueryAPI", () => { - const api = new AxelarQueryAPI({ environment: Environment.TESTNET }); + const api = new AxelarQueryAPI({ + environment: Environment.TESTNET, + axelarRpcUrl: "https://axelar-testnet-rpc.qubelabs.io:443", + }); beforeEach(() => { vitest.clearAllMocks(); @@ -222,6 +225,32 @@ describe("AxelarQueryAPI", () => { } }); + test("It should include L1 fee for L2 destination chain for cosmos source chains", async () => { + // Mainnet + const mainnetL2Chains = ["optimism"]; + const mainnetApi = new AxelarQueryAPI({ environment: Environment.MAINNET }); + const mainnetQueries = []; + for (const mainnetL2Chain of mainnetL2Chains) { + const query = await mainnetApi.estimateGasFee( + "osmosis", + mainnetL2Chain as EvmChain, + 500000, + "auto", + undefined, + undefined, + undefined + ); + mainnetQueries.push(query); + } + + const mainnetResponses = await Promise.all(mainnetQueries); + + mainnetResponses.forEach((response) => { + expect(response).toBeDefined(); + expect(String(response).length).toBeLessThanOrEqual(6); + }); + }); + test("It should return estimated gas amount that makes sense for native token", async () => { const gasAmount = await api.estimateGasFee( CHAINS.TESTNET.AVALANCHE as EvmChain, @@ -378,7 +407,10 @@ describe("AxelarQueryAPI", () => { let api: AxelarQueryAPI; beforeEach(async () => { - api = new AxelarQueryAPI({ environment: Environment.TESTNET }); + api = new AxelarQueryAPI({ + environment: Environment.TESTNET, + axelarRpcUrl: "https://axelar-testnet-lcd.qubelabs.io", + }); }); test("it should retrieve the gas receiver address remotely", async () => { @@ -392,7 +424,10 @@ describe("AxelarQueryAPI", () => { let api: AxelarQueryAPI; beforeEach(async () => { - api = new AxelarQueryAPI({ environment: Environment.TESTNET }); + api = new AxelarQueryAPI({ + environment: Environment.TESTNET, + axelarRpcUrl: "https://axelar-testnet-rpc.qubelabs.io:443", + }); vitest.clearAllMocks(); /** diff --git a/src/libs/test/TransactionRecoveryAPI/AxelarGMPRecoveryAPI.spec.ts b/src/libs/test/TransactionRecoveryAPI/AxelarGMPRecoveryAPI.spec.ts index 63e752ca..860ccbc8 100644 --- a/src/libs/test/TransactionRecoveryAPI/AxelarGMPRecoveryAPI.spec.ts +++ b/src/libs/test/TransactionRecoveryAPI/AxelarGMPRecoveryAPI.spec.ts @@ -687,6 +687,32 @@ describe("AxelarGMPRecoveryAPI", () => { }); }); + describe("getCidFromSrcTxHash", () => { + const mainnetApi = new AxelarGMPRecoveryAPI({ environment: Environment.MAINNET }); + + // https://axelarscan.io/gmp/0x3828bf893801f337e08d15b89efc9c3c2d9196fe7f83f3b7640425b24d122cb2:12 + it("should return the correct commandId from evm -> evm for ContractCallWithToken event", () => { + expect( + mainnetApi.getCidFromSrcTxHash( + "celo", + "0x3828bf893801f337e08d15b89efc9c3c2d9196fe7f83f3b7640425b24d122cb2", + 8 + ) + ).toEqual("0xa45da101fcfed541b8251cb8a288b5b7dd84086377eb9cf3f8d4a99f11e062e0"); + }); + + // https://axelarscan.io/gmp/0x92f676751feccab46a048a16aaf81b26620a3683933b56a722ce742de8ea7429:349 + it("should return the correct commandId from evm -> evm for ContractCall event", () => { + expect( + mainnetApi.getCidFromSrcTxHash( + "blast", + "0x92f676751feccab46a048a16aaf81b26620a3683933b56a722ce742de8ea7429-5", + 5 + ) + ).toEqual("0xe6868c6e94240fa6a37cc71d265106a00ad8fa0652319f145e3235f703046574"); + }); + }); + describe.skip("calculateNativeGasFee", () => { const api = new AxelarGMPRecoveryAPI({ environment: Environment.TESTNET }); diff --git a/src/libs/test/TransactionRecoveryAPI/EncodingTests.spec.ts b/src/libs/test/TransactionRecoveryAPI/EncodingTests.spec.ts index 0f4fc179..b6494f28 100644 --- a/src/libs/test/TransactionRecoveryAPI/EncodingTests.spec.ts +++ b/src/libs/test/TransactionRecoveryAPI/EncodingTests.spec.ts @@ -31,8 +31,6 @@ describe("AxelarDepositRecoveryAPI", () => { const txHash = "0x2c9083bebd1f82b86b7b0d3298885f90767b584742df9ec3a9c9f15872a1fff9"; const eventIndex = await api.getEventIndex("ethereum-2" as EvmChain, txHash); const res = await api.getCidFromSrcTxHash(EvmChain.MOONBEAM, txHash, eventIndex as number); - console.log("eventIndex", eventIndex); - console.log("res", res); expect(res).toEqual("58c46960e6483f61bf206d1bd1819917d2b009f58d7050e05b4be1d13247b4ed"); }, 60000); });