Skip to content

Commit

Permalink
feat: implement estimateAmplifierFee with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
npty committed Nov 8, 2024
1 parent 345c15f commit eadfa07
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 24 deletions.
48 changes: 47 additions & 1 deletion src/libs/AxelarQueryAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
TransferFeeResponse,
} from "@axelar-network/axelarjs-types/axelar/nexus/v1beta1/query";
import { throwIfInvalidChainIds } from "../utils";
import { loadChains } from "../chains";
import { importS3Config, loadChains } from "../chains";
import s3 from "./TransactionRecoveryApi/constants/s3";
import { BigNumber, BigNumberish, ethers } from "ethers";
import { ChainInfo } from "../chains/types";
Expand Down Expand Up @@ -535,6 +535,52 @@ export class AxelarQueryAPI {
}
}

/**
* Estimates the fee for an Amplifier transaction by splitting it into two hops:
* 1. Source chain to Axelar
* 2. Axelar to destination chain
*
* @param params Parameters for the end-to-end transfer
* @param options Optional parameters for fee estimation
* @returns Promise containing the estimated fees
* @throws {Error} If config loading fails or required parameters are missing
*/
public async estimateAmplifierFee(params: HopParams, options?: EstimateMultihopFeeOptions) {
const config = await importS3Config(this.environment);
const axelarChainId = config["axelar"]?.axelarId || "axelar";

const hops = [
{
sourceChain: params.sourceChain,
destinationChain: axelarChainId,
gasLimit: params.gasLimit,
sourceTokenSymbol: params.sourceTokenSymbol,
sourceTokenAddress: params.sourceTokenAddress,
sourceContractAddress: params.sourceContractAddress,
},
{
sourceChain: axelarChainId,
destinationChain: params.destinationChain,
gasLimit: params.gasLimit,
symbol: params.symbol,
minGasPrice: params.minGasPrice,
executeData: params.executeData,
destinationContractAddress: params.destinationContractAddress,
amountInUnits: params.amountInUnits,
},
];

// Clean up undefined values
const cleanHop = (hop: HopParams): HopParams => {
return Object.fromEntries(
Object.entries(hop).filter(([_, value]) => value !== undefined)

Check warning on line 576 in src/libs/AxelarQueryAPI.ts

View workflow job for this annotation

GitHub Actions / ESLint

src/libs/AxelarQueryAPI.ts#L576

'_' is defined but never used (@typescript-eslint/no-unused-vars)
) as HopParams;
};

// Estimate fees for both hops
return this.estimateMultihopFee(hops.map(cleanHop), options);
}

/**
* Get the denom for an asset given its symbol on a chain
* @param symbol
Expand Down
148 changes: 125 additions & 23 deletions src/libs/test/AxelarQueryAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,29 +282,131 @@ describe("AxelarQueryAPI", () => {
});
});

// describe.only("estimateMultihopFee", () => {
// const amplifierApi = new AxelarQueryAPI({
// environment: Environment.DEVNET,
// });
//
// test("It should return estimated gas fee", async () => {
// const fee = await amplifierApi.estimateMultihopFee([
// {
// destinationChain: "avalanche-fuji",
// sourceChain: "axelarnet",
// gasLimit: "700000",
// },
// {
// destinationChain: "sui-test2",
// sourceChain: "axelarnet",
// gasLimit: "700000",
// },
// ]);
//
// expect(fee).toBeDefined();
// });
// });
//
describe("estimateAmplifierFee", () => {
let amplifierApi: AxelarQueryAPI;
let mockImportS3Config: SpyInstance;
let mockEstimateMultihopFee: SpyInstance;

beforeEach(async () => {
amplifierApi = new AxelarQueryAPI({
environment: Environment.DEVNET,
});

mockImportS3Config = vi
.spyOn(await import("../../chains"), "importS3Config")
.mockResolvedValue({
axelar: {
axelarId: "axelar-test",
},
});

// Mock estimateMultihopFee to avoid duplication of its tests
mockEstimateMultihopFee = vi
.spyOn(amplifierApi, "estimateMultihopFee")
.mockResolvedValue("1");
});

test("should correctly split into two hops using config axelarId", async () => {
const params = {
sourceChain: "ethereum",
destinationChain: "avalanche",
gasLimit: "700000",
sourceTokenSymbol: "USDC",
};

await amplifierApi.estimateAmplifierFee(params);

expect(mockEstimateMultihopFee).toHaveBeenCalledWith(
[
{
sourceChain: "ethereum",
destinationChain: "axelar-test",
gasLimit: "700000",
sourceTokenSymbol: "USDC",
},
{
sourceChain: "axelar-test",
destinationChain: "avalanche",
gasLimit: "700000",
},
],
undefined
);
});

test("should use default axelarId when not in config", async () => {
mockImportS3Config.mockResolvedValueOnce({});

const params = {
sourceChain: "ethereum",
destinationChain: "avalanche",
gasLimit: "700000",
};

await amplifierApi.estimateAmplifierFee(params);

expect(mockEstimateMultihopFee).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ destinationChain: "axelar" }),
expect.objectContaining({ sourceChain: "axelar" }),
]),
undefined
);
});

test("should pass through all relevant parameters to correct hops", async () => {
const params = {
sourceChain: "ethereum",
destinationChain: "avalanche",
gasLimit: "700000",
sourceTokenSymbol: "USDC",
sourceContractAddress: "0xsource",
destinationContractAddress: "0xdest",
executeData: "0xdata",
amountInUnits: "1000000",
};

await amplifierApi.estimateAmplifierFee(params);

expect(mockEstimateMultihopFee).toHaveBeenCalledWith(
[
{
sourceChain: "ethereum",
destinationChain: "axelar-test",
gasLimit: "700000",
sourceTokenSymbol: "USDC",
sourceContractAddress: "0xsource",
},
{
sourceChain: "axelar-test",
destinationChain: "avalanche",
gasLimit: "700000",
destinationContractAddress: "0xdest",
executeData: "0xdata",
amountInUnits: "1000000",
},
],
undefined
);
});

test("should pass through showDetailedFees option", async () => {
const params = {
sourceChain: "ethereum",
destinationChain: "avalanche",
gasLimit: "700000",
};

await amplifierApi.estimateAmplifierFee(params, {
showDetailedFees: true,
});

expect(mockEstimateMultihopFee).toHaveBeenCalledWith(expect.any(Array), {
showDetailedFees: true,
});
});
});

describe("estimateGasFee", () => {
test("It should return estimated gas amount that makes sense for USDC", async () => {
const gasAmount = await api.estimateGasFee(
Expand Down

0 comments on commit eadfa07

Please sign in to comment.