diff --git a/.changeset/tidy-kings-kick.md b/.changeset/tidy-kings-kick.md new file mode 100644 index 0000000..b670a31 --- /dev/null +++ b/.changeset/tidy-kings-kick.md @@ -0,0 +1,5 @@ +--- +"@across-protocol/app-sdk": patch +--- + +Allow passing of pre encoded message field in getQuote diff --git a/apps/example/lib/stake.ts b/apps/example/lib/stake.ts index 1e98412..3df7971 100644 --- a/apps/example/lib/stake.ts +++ b/apps/example/lib/stake.ts @@ -1,4 +1,3 @@ -import { CrossChainAction } from "@across-protocol/app-sdk"; import { Address, encodeFunctionData } from "viem"; import { optimism } from "viem/chains"; diff --git a/packages/sdk/src/actions/getQuote.ts b/packages/sdk/src/actions/getQuote.ts index 107105d..0832282 100644 --- a/packages/sdk/src/actions/getQuote.ts +++ b/packages/sdk/src/actions/getQuote.ts @@ -1,4 +1,4 @@ -import { Address, Hex } from "viem"; +import { Address, Hex, isHex } from "viem"; import { Amount, CrossChainAction } from "../types/index.js"; import { getMultiCallHandlerAddress, @@ -129,6 +129,10 @@ export async function getQuote(params: GetQuoteParams): Promise { let message: Hex = "0x"; let recipient = _recipient; + if (isHex(crossChainMessage)) { + message = crossChainMessage; + } + if (crossChainMessage && typeof crossChainMessage === "object") { if (crossChainMessage.actions.length === 0) { throw new Error("No 'crossChainMessage.actions' provided"); diff --git a/packages/sdk/test/unit/actions/getQuote.test.ts b/packages/sdk/test/unit/actions/getQuote.test.ts new file mode 100644 index 0000000..2a65b2c --- /dev/null +++ b/packages/sdk/test/unit/actions/getQuote.test.ts @@ -0,0 +1,108 @@ +import { assert, assertType, describe, test } from "vitest"; +import { testClient } from "../../common/sdk.js"; +import { + buildMulticallHandlerMessage, + getMultiCallHandlerAddress, + type Quote, + type Route, +} from "../../../src/index.js"; +import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; +import { arbitrum, optimism } from "viem/chains"; + +// arbitrum USDC +const inputToken = { + address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + symbol: "USDC", + name: "USD Coin", + decimals: 6, +} as const; + +// optimism USDC +const outputToken = { + address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + symbol: "USDC", + name: "USD Coin", + decimals: 6, +} as const; + +const testEoa = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; +const inputAmount = 100; +const inputAmountBN = parseUnits(inputAmount.toString(), inputToken.decimals); +const outputAmountBN = inputAmountBN / 2n; // 50% of input ensures this doesn't fail, since we cannot recompute after getting the initial quote + +// ARBITRUM => OPTIMISM +const testRoute: Route = { + originChainId: arbitrum.id, + destinationChainId: optimism.id, + inputToken: inputToken.address, + outputToken: outputToken.address, + inputTokenSymbol: inputToken.symbol, + outputTokenSymbol: outputToken.symbol, + isNative: false, +}; + +describe("getQuote with Raw Pre-Encoded Message", () => { + test("Gets a quote using a raw pre-encoded message from a test EOA", async () => { + // pre compute a simple transfer message + const calldata = buildMulticallHandlerMessage({ + fallbackRecipient: testEoa, + actions: [ + { + target: outputToken.address, + value: 0n, + callData: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [testEoa, outputAmountBN], + }), + }, + ], + }); + + // Invoke getQuote with the raw message + const _quote = await testClient.getQuote({ + route: testRoute, + recipient: getMultiCallHandlerAddress(testRoute.destinationChainId), + inputAmount: inputAmountBN, + crossChainMessage: calldata, + // Removed 'sender' as it's not a parameter for getQuote + }); + + // Assert that a quote was returned + assert(_quote, "No quote returned for the provided route and message"); + assertType(_quote); + + assert.equal( + _quote.deposit.message, + calldata, + "Message should be equal to pre-encoded calldata", + ); + + // Optional: Additional assertions to verify quote details + assert.equal( + _quote.deposit.inputAmount.toString(), + inputAmountBN.toString(), + "Input amounts should match", + ); + assert.equal( + _quote.deposit.originChainId, + testRoute.originChainId, + "Origin chain IDs should match", + ); + assert.equal( + _quote.deposit.destinationChainId, + testRoute.destinationChainId, + "Destination chain IDs should match", + ); + assert.equal( + _quote.deposit.inputToken, + testRoute.inputToken, + "Input tokens should match", + ); + assert.equal( + _quote.deposit.outputToken, + testRoute.outputToken, + "Output tokens should match", + ); + }); +});