From 1ecb4b7bd1dc2886d1a5875aa3a16752b82ec8bf Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 24 Sep 2024 18:31:04 +0200 Subject: [PATCH 01/11] feat: simulateTxOnTenderly helper --- packages/sdk/src/utils/tenderly.ts | 89 ++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packages/sdk/src/utils/tenderly.ts diff --git a/packages/sdk/src/utils/tenderly.ts b/packages/sdk/src/utils/tenderly.ts new file mode 100644 index 0000000..8b98a08 --- /dev/null +++ b/packages/sdk/src/utils/tenderly.ts @@ -0,0 +1,89 @@ +import { HttpError } from "../errors"; +import { isOk } from "./fetch"; + +export type TenderlySimulateTxParams = { + accountSlug: string; + projectSlug: string; + accessKey: string; + // Tenderly required params + networkId: string; + from: string; + to: string; + value: string; + data: string; + gas?: bigint; + blockNumber?: "latest" | number; + // Optional params + enableShare?: boolean; +}; + +// Only subset with fields we use. For full response body see +// https://docs.tenderly.co/reference/api#/operations/simulateTransaction#response-body +type TenderlySimulateTxResponse = { + simulation: { + id: string; + }; +}; + +/** + * Simulates a transaction on Tenderly and returns a shareable URL. + * + * @param params - The parameters for the simulation. + * @returns A URL to the simulation result. + */ +export async function simulateTxOnTenderly(params: TenderlySimulateTxParams) { + const simUrl = `https://api.tenderly.co/api/v1/account/${params.accountSlug}/project/${params.projectSlug}/simulate`; + const simRes = await fetch(simUrl, { + method: "POST", + headers: { + "X-Access-Key": params.accessKey, + }, + body: JSON.stringify({ + network_id: params.networkId, + from: params.from, + to: params.to, + input: params.data, + gas: params.gas ? Number(params.gas) : undefined, + block_number: params.blockNumber, + save: true, + }), + }); + + if (!isOk(simRes)) { + throw new HttpError({ + status: simRes.status, + url: simUrl, + message: `Failed to simulate tx on Tenderly - ${simRes.status} ${simRes.statusText} - ${await simRes.text()}`, + }); + } + + const simData = (await simRes.json()) as TenderlySimulateTxResponse; + const simulationId = simData.simulation.id; + + // Enable sharing for the simulation + if (params.enableShare) { + const enableShareUrl = `https://api.tenderly.co/api/v1/account/${params.accountSlug}/project/${params.projectSlug}/simulations/${simulationId}/share`; + const enableShareRes = await fetch(enableShareUrl, { + method: "POST", + headers: { + "X-Access-Key": params.accessKey, + }, + }); + + if (!isOk(enableShareRes)) { + throw new HttpError({ + status: enableShareRes.status, + url: enableShareUrl, + message: `Failed to enable sharing Tenderly simulation - ${enableShareRes.status} ${enableShareRes.statusText} - ${await enableShareRes.text()}`, + }); + } + } + + const url = params.enableShare + ? `https://www.tdly.co/shared/simulation/${simulationId}` + : `https://dashboard.tenderly.co/${params.accountSlug}/${params.projectSlug}/simulator/${simulationId}`; + return { + simulationId, + simulationUrl: url, + }; +} From 4acab9c3066715902b31bc054d911c998a64357a Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 24 Sep 2024 18:32:14 +0200 Subject: [PATCH 02/11] refactor: add fetch api helpers --- .../sdk/src/actions/getAvailableRoutes.ts | 22 ++-- .../sdk/src/actions/getFillByDepositTx.ts | 26 +--- packages/sdk/src/actions/getLimits.ts | 22 ++-- packages/sdk/src/actions/getSuggestedFees.ts | 32 +---- packages/sdk/src/actions/simulateDepositTx.ts | 82 +++++-------- packages/sdk/src/errors/acrossApi.ts | 17 +++ packages/sdk/src/errors/index.ts | 99 +++++++++++++-- packages/sdk/src/utils/fetch.ts | 114 +++++++++++++++++- packages/sdk/src/utils/getSupportedChains.ts | 22 +--- packages/sdk/src/utils/index.ts | 1 + 10 files changed, 280 insertions(+), 157 deletions(-) create mode 100644 packages/sdk/src/errors/acrossApi.ts diff --git a/packages/sdk/src/actions/getAvailableRoutes.ts b/packages/sdk/src/actions/getAvailableRoutes.ts index 1b287c7..2171c3b 100644 --- a/packages/sdk/src/actions/getAvailableRoutes.ts +++ b/packages/sdk/src/actions/getAvailableRoutes.ts @@ -1,5 +1,5 @@ import { Address } from "viem"; -import { buildSearchParams, LoggerT } from "../utils"; +import { LoggerT, fetchAcrossApi } from "../utils"; import { Route } from "../types"; import { MAINNET_API_URL } from "../constants"; @@ -23,22 +23,14 @@ export async function getAvailableRoutes({ logger, ...params }: GetAvailableRoutesParams): Promise { - const searchParams = params - ? buildSearchParams(params) - : ""; - - const url = `${apiUrl}/available-routes?${searchParams}`; - - logger?.debug("Fetching available routes for params:", params, `URL: ${url}`); - - const res = await fetch(url); - - const data = (await res.json()) as AvailableRoutesApiResponse; - - logger?.debug("Routes data: ", data); + const routes = await fetchAcrossApi( + `${apiUrl}/available-routes`, + params, + logger, + ); // Transform to internal type consistency - return data.map((route) => ({ + return routes.map((route) => ({ isNative: route.isNative, originChainId: route.originChainId, inputToken: route.originToken as Address, diff --git a/packages/sdk/src/actions/getFillByDepositTx.ts b/packages/sdk/src/actions/getFillByDepositTx.ts index 5eb1b4a..1fca6ce 100644 --- a/packages/sdk/src/actions/getFillByDepositTx.ts +++ b/packages/sdk/src/actions/getFillByDepositTx.ts @@ -1,4 +1,4 @@ -import { buildSearchParams, isOk } from "../utils"; +import { fetchIndexerApi } from "../utils"; import { DepositStatus } from "./waitForDepositTx"; import { Hash, @@ -10,14 +10,9 @@ import { import { Quote } from "./getQuote"; import { spokePoolAbi } from "../abis/SpokePool"; import { MAINNET_INDEXER_API } from "../constants"; -import { HttpError, IndexerError, NoFillLogError } from "../errors"; +import { NoFillLogError } from "../errors"; import { IndexerStatusResponse } from "../types"; -type DepositStatusQueryParams = { - depositId: DepositStatus["depositId"]; - originChainId: number; -}; - export type GetFillByDepositTxParams = Pick & { depositId: DepositStatus["depositId"]; depositTransactionHash: Hash; @@ -39,24 +34,13 @@ export async function getFillByDepositTx( } = params; try { - const url = `${indexerUrl}/deposit/status?${buildSearchParams( + const data = await fetchIndexerApi( + `${indexerUrl}/deposit/status`, { depositId, originChainId: deposit.originChainId, }, - )}`; - - const res = await fetch(url); - - if (!isOk(res)) { - throw new HttpError(res.status, url, await res.text()); - } - - const data = (await res.json()) as IndexerStatusResponse; - - if (data?.error) { - throw new IndexerError(url, data?.message, data?.error); - } + ); if (data?.status === "filled" && data?.fillTx) { const fillTxReceipt = await destinationChainClient.getTransactionReceipt({ diff --git a/packages/sdk/src/actions/getLimits.ts b/packages/sdk/src/actions/getLimits.ts index 6d16839..0216bcc 100644 --- a/packages/sdk/src/actions/getLimits.ts +++ b/packages/sdk/src/actions/getLimits.ts @@ -1,7 +1,6 @@ import { Address } from "viem"; -import { buildSearchParams, isOk, LoggerT } from "../utils"; +import { fetchAcrossApi, LoggerT } from "../utils"; import { MAINNET_API_URL } from "../constants"; -import { HttpError } from "../errors"; type LimitsQueryParams = { destinationChainId: number; @@ -21,19 +20,12 @@ export async function getLimits({ logger, ...params }: GetLimitsParams) { - const searchParams = buildSearchParams(params); - const url = `${apiUrl}/limits?${searchParams}`; - - logger?.debug("Fetching Limits for params:", params, `URL: ${url}`); - - const res = await fetch(url); - - if (!isOk(res)) { - logger?.error("Unable to fetch limits:", `URL: ${url}`); - throw new HttpError(res.status, url, await res.text()); - } - - return (await res.json()) as LimitsResponse; + const limits = await fetchAcrossApi( + `${apiUrl}/limits`, + params, + logger, + ); + return limits; } export type LimitsResponse = { diff --git a/packages/sdk/src/actions/getSuggestedFees.ts b/packages/sdk/src/actions/getSuggestedFees.ts index 0a72637..2f70451 100644 --- a/packages/sdk/src/actions/getSuggestedFees.ts +++ b/packages/sdk/src/actions/getSuggestedFees.ts @@ -1,5 +1,5 @@ import { Address } from "viem"; -import { buildSearchParams, isOk, LoggerT } from "../utils"; +import { LoggerT, fetchAcrossApi } from "../utils"; import { Amount, Route } from "../types"; import { MAINNET_API_URL } from "../constants"; @@ -11,7 +11,7 @@ type SuggestedFeesQueryParams = Partial> & relayer?: Address; skipAmountLimit?: boolean; timestamp?: number; - depositMethod?: string; // "depositV3" | "depositExclusive" + depositMethod?: "depositV3" | "depositExclusive"; }; export type GetSuggestedFeesParams = SuggestedFeesQueryParams & { @@ -24,35 +24,15 @@ export async function getSuggestedFees({ logger, ...params }: GetSuggestedFeesParams) { - const searchParams = buildSearchParams({ - ...params, - depositMethod: "depositExclusive", - }); - - const url = `${apiUrl}/suggested-fees?${searchParams}`; - - logger?.debug( - "Fetching suggested fees for params:", + const data = await fetchAcrossApi( + `${apiUrl}/suggested-fees`, { - ...params, depositMethod: "depositExclusive", + ...params, }, - `URL: ${url}`, + logger, ); - const res = await fetch(url); - - if (!isOk(res)) { - logger?.error(`Failed to fetch suggested fees`); - throw new Error( - `Failed to fetch suggested fees: ${res.status}, ${await res.text()}`, - ); - } - - const data = (await res.json()) as SuggestedFeesResponse; - - logger?.debug("Suggested Fees Data", data); - const outputAmount = BigInt(params.amount) - BigInt(data.totalRelayFee.total); return { // @todo: more data transformations for easier consumptions diff --git a/packages/sdk/src/actions/simulateDepositTx.ts b/packages/sdk/src/actions/simulateDepositTx.ts index 97ba099..ebee073 100644 --- a/packages/sdk/src/actions/simulateDepositTx.ts +++ b/packages/sdk/src/actions/simulateDepositTx.ts @@ -1,6 +1,4 @@ import { - BaseError, - ContractFunctionRevertedError, PublicClient, SimulateContractReturnType, WalletClient, @@ -58,11 +56,6 @@ export async function simulateDepositTx(params: SimulateDepositTxParams) { ); } - logger?.debug( - "simulateTransaction.ts", - `Simulating with address ${account.address} on chain ${connectedChainId}`, - ); - // TODO: Add support for `SpokePoolVerifier` contract let fillDeadline = _fillDeadline; @@ -73,57 +66,38 @@ export async function simulateDepositTx(params: SimulateDepositTxParams) { abi: spokePoolAbi, functionName: "fillDeadlineBuffer", }); - - logger?.debug( - "simulateTransaction.ts", - `fillDeadlineBuffer from spokePool: ${fillDeadlineBuffer}`, - ); fillDeadline = getCurrentTimeSeconds() - 60 + fillDeadlineBuffer; } const useExclusiveRelayer = exclusiveRelayer !== zeroAddress && exclusivityDeadline > 0; - logger?.debug(`Using exclusive relayer ${useExclusiveRelayer}`); - - try { - const result = await publicClient.simulateContract({ - account: walletClient.account, - abi: spokePoolAbi, - address: spokePoolAddress, - functionName: useExclusiveRelayer ? "depositExclusive" : "depositV3", - args: [ - account.address, - recipient ?? account.address, - inputToken, - outputToken, - BigInt(inputAmount), - outputAmount, - BigInt(destinationChainId), - exclusiveRelayer, - quoteTimestamp, - fillDeadline, - exclusivityDeadline, - message, - ], - value: isNative ? BigInt(inputAmount) : 0n, - dataSuffix: getIntegratorDataSuffix(integratorId), - }); - - logger?.debug("Simulation result", result); - - return result as unknown as SimulateContractReturnType; - } catch (err) { - logger?.error("Deposit tx simulation error", err); - - if (err instanceof BaseError) { - const revertError = err.walk( - (err) => err instanceof ContractFunctionRevertedError, - ); - if (revertError instanceof ContractFunctionRevertedError) { - throw revertError; - } - } - throw err; - } + logger?.debug(`Using exclusive relayer: ${useExclusiveRelayer}`); + + const result = await publicClient.simulateContract({ + account: walletClient.account, + abi: spokePoolAbi, + address: spokePoolAddress, + functionName: useExclusiveRelayer ? "depositExclusive" : "depositV3", + args: [ + account.address, + recipient ?? account.address, + inputToken, + outputToken, + BigInt(inputAmount), + outputAmount, + BigInt(destinationChainId), + exclusiveRelayer, + quoteTimestamp, + fillDeadline, + exclusivityDeadline, + message, + ], + value: isNative ? BigInt(inputAmount) : 0n, + dataSuffix: getIntegratorDataSuffix(integratorId), + }); + + logger?.debug("Simulation result", result); + + return result as unknown as SimulateContractReturnType; } diff --git a/packages/sdk/src/errors/acrossApi.ts b/packages/sdk/src/errors/acrossApi.ts new file mode 100644 index 0000000..795c8fc --- /dev/null +++ b/packages/sdk/src/errors/acrossApi.ts @@ -0,0 +1,17 @@ +export type AcrossErrorCodeType = keyof typeof AcrossErrorCode; + +// TODO: These error codes should be sourced automatically from the Across API +// See https://github.com/across-protocol/frontend-v2/blob/b4ce24f4be6b4f305d3874c8d9368cd39d1b5d92/api/_errors.ts +export const AcrossErrorCode = { + // Status: 40X + INVALID_PARAM: "INVALID_PARAM", + MISSING_PARAM: "MISSING_PARAM", + SIMULATION_ERROR: "SIMULATION_ERROR", + AMOUNT_TOO_LOW: "AMOUNT_TOO_LOW", + AMOUNT_TOO_HIGH: "AMOUNT_TOO_HIGH", + ROUTE_NOT_ENABLED: "ROUTE_NOT_ENABLED", + + // Status: 50X + UPSTREAM_RPC_ERROR: "UPSTREAM_RPC_ERROR", + UPSTREAM_HTTP_ERROR: "UPSTREAM_HTTP_ERROR", +} as const; diff --git a/packages/sdk/src/errors/index.ts b/packages/sdk/src/errors/index.ts index b380d56..dac9e21 100644 --- a/packages/sdk/src/errors/index.ts +++ b/packages/sdk/src/errors/index.ts @@ -1,4 +1,7 @@ -import { Hash } from "viem"; +import { Address, Hash, Hex } from "viem"; +import { AcrossErrorCodeType } from "./acrossApi"; + +export type { AcrossErrorCodeType }; export class DepositRevert extends Error { constructor(message?: string) { @@ -9,12 +12,94 @@ export class DepositRevert extends Error { export class HttpError extends Error { public readonly url: string; - public readonly code: number; - constructor(code: number, url: string, message?: string, cause?: Error) { - super(message, { cause }); - this.name = "HTTP Error"; - this.code = code; - this.url = url; + public readonly status: number; + constructor( + params: { + status: number; + url: string; + name?: string; + message?: string; + }, + opts?: ErrorOptions, + ) { + super(params.message, opts); + this.name = params.name ?? "HttpError"; + this.url = params.url; + this.status = params.status; + } +} + +export class AcrossApiError extends HttpError { + constructor( + params: { + name?: string; + status: number; + url: string; + message?: string; + code: AcrossErrorCodeType; + }, + opts?: ErrorOptions, + ) { + super( + { + name: "AcrossApiError", + ...params, + }, + opts, + ); + } +} + +export class AcrossApiSimulationError extends AcrossApiError { + public readonly transaction: { + from: Address; + to: Address; + data: Hex; + value?: string; + }; + + constructor( + params: { + url: string; + message?: string; + transaction: { + from: Address; + to: Address; + data: Hex; + value?: string; + }; + }, + opts?: ErrorOptions, + ) { + super( + { + ...params, + name: "AcrossApiSimulationError", + status: 400, + code: "SIMULATION_ERROR", + }, + opts, + ); + this.transaction = params.transaction; + } +} + +export class SimulationError extends Error { + public readonly simulationId: string; + public readonly simulationUrl: string; + + constructor( + params: { + message?: string; + simulationId: string; + simulationUrl: string; + }, + opts?: ErrorOptions, + ) { + super(params.message, opts); + this.name = "SimulationError"; + this.simulationId = params.simulationId; + this.simulationUrl = params.simulationUrl; } } diff --git a/packages/sdk/src/utils/fetch.ts b/packages/sdk/src/utils/fetch.ts index 8b8d10c..d49b44c 100644 --- a/packages/sdk/src/utils/fetch.ts +++ b/packages/sdk/src/utils/fetch.ts @@ -1,3 +1,15 @@ +import { Address, Hex } from "viem"; +import { + HttpError, + AcrossApiError, + AcrossErrorCodeType, + AcrossApiSimulationError, + IndexerError, +} from "../errors"; +import { LoggerT } from "./logger"; + +type ParamBaseValue = number | bigint | string | boolean; + /** * Builds a URL search string from an object of query parameters. * @@ -5,9 +17,6 @@ * * @returns queryString - A properly formatted query string for use in URLs, (without the leading '?'). */ - -type ParamBaseValue = number | bigint | string | boolean; - export function buildSearchParams< T extends Record>, >(params: T): string { @@ -30,3 +39,102 @@ export function isOk(res: Response) { } return false; } + +function makeFetcher( + name: string, + apiErrorHandler?: ( + response: Response, + data: any, + url: string, + ) => Promise, +) { + return async ( + apiUrl: string, + params: ReqParams, + logger?: LoggerT, + ): Promise => { + const searchParams = buildSearchParams( + params as Record>, + ); + const url = `${apiUrl}?${searchParams}`; + + logger?.debug(`Fetching ${name}...`, url); + + const res = await fetch(url); + + // Try to parse the response as JSON. If it fails, parse it as text. + let data: ResBody; + try { + data = (await res.json()) as ResBody; + } catch (e) { + data = (await res.text()) as ResBody; + } + + // If the response is OK, return the data + if (isOk(res)) { + logger?.debug("OK response", data); + return data; + } + + logger?.debug("Error response", { + status: res.status, + }); + + if (apiErrorHandler) { + apiErrorHandler(res, data, url); + } + + throw new HttpError({ + status: res.status, + message: typeof data === "string" ? data : JSON.stringify(data), + url, + }); + }; +} + +export const fetchAcrossApi = makeFetcher( + "Across API", + async (res, data, url) => { + // Check for Across API errors + if ( + typeof data === "object" && + data !== null && + "type" in data && + data.type === "AcrossApiError" + ) { + const acrossApiError = data as unknown as { + message: string; + code: AcrossErrorCodeType; + transaction: { + from: Address; + to: Address; + data: Hex; + }; + }; + + if (acrossApiError.code === "SIMULATION_ERROR") { + throw new AcrossApiSimulationError({ + message: acrossApiError.message, + url, + transaction: acrossApiError.transaction, + }); + } + + throw new AcrossApiError({ + status: res.status, + message: acrossApiError.message, + url, + code: acrossApiError.code, + }); + } + }, +); + +export const fetchIndexerApi = makeFetcher( + "Indexer API", + async (res, data, url) => { + if (typeof data === "object" && data !== null && "error" in data) { + throw new IndexerError(url, data?.message, data?.error); + } + }, +); diff --git a/packages/sdk/src/utils/getSupportedChains.ts b/packages/sdk/src/utils/getSupportedChains.ts index 2997de8..c38e0a6 100644 --- a/packages/sdk/src/utils/getSupportedChains.ts +++ b/packages/sdk/src/utils/getSupportedChains.ts @@ -1,7 +1,6 @@ import { MAINNET_API_URL } from "../constants"; -import { HttpError } from "../errors"; import { TokenInfo } from "../types"; -import { buildSearchParams, isOk, LoggerT } from "."; +import { LoggerT, fetchAcrossApi } from "."; export type ChainsQueryParams = Partial<{ inputTokenSymbol: string; @@ -20,20 +19,11 @@ export async function getSupportedChains({ apiUrl = MAINNET_API_URL, ...params }: GetSupportedChainsParams) { - const url = `${apiUrl}/chains?${buildSearchParams(params)}`; - - logger?.debug(`Fetching supported chains with endpoint ${url}`); - - const res = await fetch(url); - - if (!isOk(res)) { - throw new HttpError(res.status, url, await res.text()); - } - - const data = (await res.json()) as ChainsQueryResponse; - - logger?.debug("SUPPORTED CHAINS", data); - + const data = await fetchAcrossApi( + `${apiUrl}/chains`, + params, + logger, + ); return data; } diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts index 2130e23..2cbcc0f 100644 --- a/packages/sdk/src/utils/index.ts +++ b/packages/sdk/src/utils/index.ts @@ -5,3 +5,4 @@ export * from "./timestamp"; export * from "./hex"; export * from "./configurePublicClients"; export * from "./getSupportedChains"; +export * from "./tenderly"; From 4aea5babc50eafa3a0b81bd441cdc70908d00a6d Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 24 Sep 2024 18:33:00 +0200 Subject: [PATCH 03/11] feat: wrap top-level client methods with tenderly --- packages/sdk/src/client.ts | 287 ++++++++++++++++++++++++++++++++----- 1 file changed, 250 insertions(+), 37 deletions(-) diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index ed52719..c29a105 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -1,4 +1,9 @@ -import { Chain, WalletClient } from "viem"; +import { + Chain, + ContractFunctionExecutionError, + encodeFunctionData, + WalletClient, +} from "viem"; import { getAvailableRoutes, getSuggestedFees, @@ -34,8 +39,14 @@ import { configurePublicClients, GetSupportedChainsParams, getSupportedChains, + simulateTxOnTenderly, + TenderlySimulateTxParams, } from "./utils"; -import { ConfigError } from "./errors"; +import { + AcrossApiSimulationError, + ConfigError, + SimulationError, +} from "./errors"; import { ConfiguredPublicClient, ConfiguredPublicClientMap } from "./types"; const CLIENT_DEFAULTS = { @@ -54,7 +65,10 @@ export type AcrossClientOptions = { useTestnet?: boolean; logger?: LoggerT; pollingInterval?: number; // milliseconds seconds - // tenderlyApiKey?: string + tenderlySimOnError?: boolean; + tenderlyAccessKey?: string; + tenderlyAccountSlug?: string; + tenderlyProjectSlug?: string; }; export class AcrossClient { @@ -67,6 +81,20 @@ export class AcrossClient { indexerUrl: string; logger: LoggerT; + // Tenderly related options + tenderlySimOnError?: boolean; + tenderlyAccessKey?: string; + tenderlyAccountSlug?: string; + tenderlyProjectSlug?: string; + + get isTenderlyEnabled() { + return Boolean( + this.tenderlyAccessKey && + this.tenderlyAccountSlug && + this.tenderlyProjectSlug, + ); + } + public actions: { getAvailableRoutes: AcrossClient["getAvailableRoutes"]; waitForDepositTx: AcrossClient["waitForDepositTx"]; @@ -82,7 +110,7 @@ export class AcrossClient { public utils: { getSupportedChains: AcrossClient["getSupportedChains"]; - // ... utils go here + simulateTxOnTenderly: AcrossClient["simulateTxOnTenderly"]; }; private constructor(args: AcrossClientOptions) { @@ -99,6 +127,10 @@ export class AcrossClient { this.logger = args?.logger ?? new DefaultLogger(args?.logLevel ?? CLIENT_DEFAULTS.logLevel); + this.tenderlySimOnError = args.tenderlySimOnError; + this.tenderlyAccessKey = args.tenderlyAccessKey; + this.tenderlyAccountSlug = args.tenderlyAccountSlug; + this.tenderlyProjectSlug = args.tenderlyProjectSlug; // bind methods this.actions = { getSuggestedFees: this.getSuggestedFees.bind(this), @@ -115,6 +147,7 @@ export class AcrossClient { // bind utils this.utils = { getSupportedChains: this.getSupportedChains.bind(this), + simulateTxOnTenderly: this.simulateTxOnTenderly.bind(this), }; this.logger.debug("Client created with args: \n", args); @@ -136,16 +169,6 @@ export class AcrossClient { return this.instance; } - getSupportedChains( - params: Omit, - ) { - return getSupportedChains({ - ...params, - logger: this.logger, - apiUrl: this.apiUrl, - }); - } - getPublicClient(chainId: number): ConfiguredPublicClient { const client = this.publicClients.get(chainId); if (!client) { @@ -171,16 +194,59 @@ export class AcrossClient { ); } - return executeQuote({ - ...params, - integratorId: this.integratorId, - logger: this.logger, - walletClient: this.walletClient, - originClient: this.getPublicClient(params.deposit.originChainId), - destinationClient: this.getPublicClient( - params.deposit.destinationChainId, - ), - }); + try { + await executeQuote({ + ...params, + integratorId: this.integratorId, + logger: this.logger, + walletClient: this.walletClient, + originClient: this.getPublicClient(params.deposit.originChainId), + destinationClient: this.getPublicClient( + params.deposit.destinationChainId, + ), + }); + } catch (e) { + if ( + !this.tenderlySimOnError || + !this.isTenderlyEnabled || + !( + e instanceof AcrossApiSimulationError || + e instanceof ContractFunctionExecutionError + ) + ) { + throw e; + } + const isFillSimulationError = e instanceof AcrossApiSimulationError; + const simParams = isFillSimulationError + ? { + networkId: params.deposit.destinationChainId.toString(), + from: e.transaction.from, + to: e.transaction.to, + data: e.transaction.data, + } + : { + networkId: params.deposit.originChainId.toString(), + from: e.sender!, + to: e.contractAddress!, + data: encodeFunctionData({ + abi: e.abi, + functionName: e.functionName, + args: e.args, + }), + }; + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly({ + value: params.deposit.isNative + ? String(params.deposit.inputAmount) + : "0", + ...simParams, + }); + const reason = isFillSimulationError ? e.message : e.shortMessage; + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while executing quote: ${reason}`, + }); + } } async getAvailableRoutes( @@ -196,19 +262,97 @@ export class AcrossClient { async getSuggestedFees( params: Omit, ) { - return getSuggestedFees({ - ...params, - apiUrl: this.apiUrl, - logger: this.logger, - }); + try { + const fees = await getSuggestedFees({ + ...params, + apiUrl: this.apiUrl, + logger: this.logger, + }); + return fees; + } catch (e) { + if ( + this.tenderlySimOnError && + this.isTenderlyEnabled && + e instanceof AcrossApiSimulationError + ) { + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( + { + networkId: params.originChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }, + ); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching suggested fees: ${e.message}`, + }); + } + throw e; + } } async getLimits(params: Omit) { - return getLimits({ ...params, apiUrl: this.apiUrl, logger: this.logger }); + try { + return getLimits({ ...params, apiUrl: this.apiUrl, logger: this.logger }); + } catch (e) { + if ( + this.tenderlySimOnError && + this.isTenderlyEnabled && + e instanceof AcrossApiSimulationError + ) { + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( + { + networkId: params.originChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }, + ); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching limits: ${e.message}`, + }); + } + throw e; + } } async getQuote(params: Omit) { - return getQuote({ ...params, logger: this.logger, apiUrl: this.apiUrl }); + try { + const quote = await getQuote({ + ...params, + logger: this.logger, + apiUrl: this.apiUrl, + }); + return quote; + } catch (e) { + if ( + this.tenderlySimOnError && + this.isTenderlyEnabled && + e instanceof AcrossApiSimulationError + ) { + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( + { + networkId: params.route.originChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }, + ); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching quote: ${e.message}`, + }); + } + throw e; + } } async getDepositLogs(params: GetDepositLogsParams) { @@ -221,12 +365,43 @@ export class AcrossClient { "integratorId" | "publicClient" | "logger" >, ) { - return simulateDepositTx({ - ...params, - integratorId: this.integratorId, - publicClient: this.getPublicClient(params.deposit.originChainId), - logger: this.logger, - }); + try { + const result = await simulateDepositTx({ + ...params, + integratorId: this.integratorId, + publicClient: this.getPublicClient(params.deposit.originChainId), + logger: this.logger, + }); + return result; + } catch (e) { + if ( + this.tenderlySimOnError && + this.isTenderlyEnabled && + e instanceof ContractFunctionExecutionError + ) { + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( + { + networkId: params.deposit.originChainId.toString(), + from: e.sender!, + to: e.contractAddress!, + data: encodeFunctionData({ + abi: e.abi, + functionName: e.functionName, + args: e.args, + }), + value: params.deposit.isNative + ? String(params.deposit.inputAmount) + : "0", + }, + ); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `deposit simulation failed: ${e.shortMessage}`, + }); + } + throw e; + } } async waitForDepositTx({ @@ -264,6 +439,44 @@ export class AcrossClient { ), }); } + + /* -------------------------------- Utilities ------------------------------- */ + + async getSupportedChains( + params: Omit, + ) { + return getSupportedChains({ + ...params, + logger: this.logger, + apiUrl: this.apiUrl, + }); + } + + async simulateTxOnTenderly( + params: Omit< + TenderlySimulateTxParams, + "enableShare" | "accessKey" | "accountSlug" | "projectSlug" + >, + ) { + if ( + !this.tenderlyAccessKey || + !this.tenderlyAccountSlug || + !this.tenderlyProjectSlug + ) { + throw new ConfigError( + "Tenderly credentials not set. Client needs to be configured with " + + "'tenderlyAccessKey', 'tenderlyAccountSlug', and 'tenderlyProjectSlug'.", + ); + } + + return simulateTxOnTenderly({ + ...params, + accessKey: this.tenderlyAccessKey, + accountSlug: this.tenderlyAccountSlug, + projectSlug: this.tenderlyProjectSlug, + enableShare: true, + }); + } } export function getClient() { From 2effffe10c6f39ba8397e320a4709837a4c385a5 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Tue, 24 Sep 2024 18:33:12 +0200 Subject: [PATCH 04/11] feat: adjust example script --- apps/example/scripts/sdk.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/example/scripts/sdk.ts b/apps/example/scripts/sdk.ts index bb695e5..1de3862 100644 --- a/apps/example/scripts/sdk.ts +++ b/apps/example/scripts/sdk.ts @@ -49,6 +49,10 @@ async function main() { integratorId: "TEST", logLevel: "DEBUG", walletClient, + tenderlyAccessKey: process.env.TENDERLY_ACCESS_KEY, + tenderlyAccountSlug: process.env.TENDERLY_ACCOUNT_SLUG, + tenderlyProjectSlug: process.env.TENDERLY_PROJECT_SLUG, + tenderlySimOnError: true, }); // do call to find info for displaying input/output tokens and destination chains @@ -196,8 +200,8 @@ async function main() { (token) => token.symbol === "DAI", )!; const routes = await client.actions.getAvailableRoutes({ - originChainId: arbitrum.id, - destinationChainId: mainnet.id, + originChainId: optimism.id, + destinationChainId: arbitrum.id, }); const crossChainRoute = routes.find( @@ -205,7 +209,7 @@ async function main() { )!; // quote - const inputAmount = parseUnits("10", inputTokenDetails.decimals); + const inputAmount = parseUnits("200", inputTokenDetails.decimals); const userAddress = "0x924a9f036260DdD5808007E1AA95f08eD08aA569"; // Aave v2 Lending Pool: https://etherscan.io/address/0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9 const aaveAddress = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9"; @@ -254,6 +258,14 @@ async function main() { }, }); console.log(quoteRes); + + // simulate deposit tx - should fail + const { request: simulateDepositTxRequest } = + await client.actions.simulateDepositTx({ + walletClient, + deposit: quoteRes.deposit, + }); + console.log("Simulation result:", simulateDepositTxRequest); } main(); From 354e810034f67913aa38ef8bd69154396d9f9084 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 10:49:12 +0200 Subject: [PATCH 05/11] fix: handling of `AcrossApiSimulationError` --- packages/sdk/src/actions/getLimits.ts | 7 ++- packages/sdk/src/actions/getSuggestedFees.ts | 12 +++- packages/sdk/src/client.ts | 11 +++- packages/sdk/src/errors/index.ts | 2 +- packages/sdk/src/utils/fetch.ts | 65 +++++++++----------- 5 files changed, 54 insertions(+), 43 deletions(-) diff --git a/packages/sdk/src/actions/getLimits.ts b/packages/sdk/src/actions/getLimits.ts index 0216bcc..99b1386 100644 --- a/packages/sdk/src/actions/getLimits.ts +++ b/packages/sdk/src/actions/getLimits.ts @@ -1,12 +1,17 @@ -import { Address } from "viem"; +import { Address, Hex } from "viem"; import { fetchAcrossApi, LoggerT } from "../utils"; import { MAINNET_API_URL } from "../constants"; +import { Amount } from "../types"; type LimitsQueryParams = { destinationChainId: number; inputToken: Address; outputToken: Address; originChainId: number; + amount?: Amount; + message?: Hex; + recipient?: Address; + relayer?: Address; }; export type GetLimitsParams = LimitsQueryParams & { diff --git a/packages/sdk/src/actions/getSuggestedFees.ts b/packages/sdk/src/actions/getSuggestedFees.ts index 2f70451..dc42dcb 100644 --- a/packages/sdk/src/actions/getSuggestedFees.ts +++ b/packages/sdk/src/actions/getSuggestedFees.ts @@ -3,8 +3,16 @@ import { LoggerT, fetchAcrossApi } from "../utils"; import { Amount, Route } from "../types"; import { MAINNET_API_URL } from "../constants"; -type SuggestedFeesQueryParams = Partial> & - Pick & { +type SuggestedFeesQueryParams = Partial< + Omit +> & + Pick< + Route, + | "originChainId" + | "destinationChainId" + | "inputTokenSymbol" + | "outputTokenSymbol" + > & { amount: Amount; recipient?: Address; message?: string; diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index c29a105..864cd1e 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -277,7 +277,7 @@ export class AcrossClient { ) { const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( { - networkId: params.originChainId.toString(), + networkId: params.destinationChainId.toString(), to: e.transaction.to, data: e.transaction.data, from: e.transaction.from, @@ -296,7 +296,12 @@ export class AcrossClient { async getLimits(params: Omit) { try { - return getLimits({ ...params, apiUrl: this.apiUrl, logger: this.logger }); + const limits = await getLimits({ + ...params, + apiUrl: this.apiUrl, + logger: this.logger, + }); + return limits; } catch (e) { if ( this.tenderlySimOnError && @@ -305,7 +310,7 @@ export class AcrossClient { ) { const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( { - networkId: params.originChainId.toString(), + networkId: params.destinationChainId.toString(), to: e.transaction.to, data: e.transaction.data, from: e.transaction.from, diff --git a/packages/sdk/src/errors/index.ts b/packages/sdk/src/errors/index.ts index dac9e21..391a468 100644 --- a/packages/sdk/src/errors/index.ts +++ b/packages/sdk/src/errors/index.ts @@ -42,8 +42,8 @@ export class AcrossApiError extends HttpError { ) { super( { - name: "AcrossApiError", ...params, + name: params.name ?? "AcrossApiError", }, opts, ); diff --git a/packages/sdk/src/utils/fetch.ts b/packages/sdk/src/utils/fetch.ts index d49b44c..ef77cbb 100644 --- a/packages/sdk/src/utils/fetch.ts +++ b/packages/sdk/src/utils/fetch.ts @@ -42,11 +42,7 @@ export function isOk(res: Response) { function makeFetcher( name: string, - apiErrorHandler?: ( - response: Response, - data: any, - url: string, - ) => Promise, + apiErrorHandler?: (response: Response, data: any, url: string) => void, ) { return async ( apiUrl: string, @@ -92,43 +88,40 @@ function makeFetcher( }; } -export const fetchAcrossApi = makeFetcher( - "Across API", - async (res, data, url) => { - // Check for Across API errors - if ( - typeof data === "object" && - data !== null && - "type" in data && - data.type === "AcrossApiError" - ) { - const acrossApiError = data as unknown as { - message: string; - code: AcrossErrorCodeType; - transaction: { - from: Address; - to: Address; - data: Hex; - }; +export const fetchAcrossApi = makeFetcher("Across API", (res, data, url) => { + // Check for Across API errors + if ( + typeof data === "object" && + data !== null && + "type" in data && + data.type === "AcrossApiError" + ) { + const acrossApiError = data as unknown as { + message: string; + code: AcrossErrorCodeType; + transaction: { + from: Address; + to: Address; + data: Hex; }; + }; - if (acrossApiError.code === "SIMULATION_ERROR") { - throw new AcrossApiSimulationError({ - message: acrossApiError.message, - url, - transaction: acrossApiError.transaction, - }); - } - - throw new AcrossApiError({ - status: res.status, + if (acrossApiError.code === "SIMULATION_ERROR") { + throw new AcrossApiSimulationError({ message: acrossApiError.message, url, - code: acrossApiError.code, + transaction: acrossApiError.transaction, }); } - }, -); + + throw new AcrossApiError({ + status: res.status, + message: acrossApiError.message, + url, + code: acrossApiError.code, + }); + } +}); export const fetchIndexerApi = makeFetcher( "Indexer API", From c7e6873f74ff5c5a54058302beaaf37309e33710 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 11:29:08 +0200 Subject: [PATCH 06/11] review requests --- packages/sdk/src/client.ts | 200 ++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 101 deletions(-) diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 864cd1e..004a9ba 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -65,10 +65,12 @@ export type AcrossClientOptions = { useTestnet?: boolean; logger?: LoggerT; pollingInterval?: number; // milliseconds seconds - tenderlySimOnError?: boolean; - tenderlyAccessKey?: string; - tenderlyAccountSlug?: string; - tenderlyProjectSlug?: string; + tenderly?: { + simOnError?: boolean; + accessKey: string; + accountSlug: string; + projectSlug: string; + }; }; export class AcrossClient { @@ -82,16 +84,18 @@ export class AcrossClient { logger: LoggerT; // Tenderly related options - tenderlySimOnError?: boolean; - tenderlyAccessKey?: string; - tenderlyAccountSlug?: string; - tenderlyProjectSlug?: string; + tenderly?: { + simOnError?: boolean; + accessKey: string; + accountSlug: string; + projectSlug: string; + }; get isTenderlyEnabled() { return Boolean( - this.tenderlyAccessKey && - this.tenderlyAccountSlug && - this.tenderlyProjectSlug, + this.tenderly?.accessKey && + this.tenderly?.accountSlug && + this.tenderly?.projectSlug, ); } @@ -127,10 +131,7 @@ export class AcrossClient { this.logger = args?.logger ?? new DefaultLogger(args?.logLevel ?? CLIENT_DEFAULTS.logLevel); - this.tenderlySimOnError = args.tenderlySimOnError; - this.tenderlyAccessKey = args.tenderlyAccessKey; - this.tenderlyAccountSlug = args.tenderlyAccountSlug; - this.tenderlyProjectSlug = args.tenderlyProjectSlug; + this.tenderly = args.tenderly; // bind methods this.actions = { getSuggestedFees: this.getSuggestedFees.bind(this), @@ -207,8 +208,8 @@ export class AcrossClient { }); } catch (e) { if ( - !this.tenderlySimOnError || !this.isTenderlyEnabled || + !this.tenderly?.simOnError || !( e instanceof AcrossApiSimulationError || e instanceof ContractFunctionExecutionError @@ -216,6 +217,9 @@ export class AcrossClient { ) { throw e; } + + // The Across API only throws an `AcrossApiSimulationError` when simulating fills + // on the destination chain. const isFillSimulationError = e instanceof AcrossApiSimulationError; const simParams = isFillSimulationError ? { @@ -271,26 +275,25 @@ export class AcrossClient { return fees; } catch (e) { if ( - this.tenderlySimOnError && - this.isTenderlyEnabled && - e instanceof AcrossApiSimulationError + !this.isTenderlyEnabled || + !this.tenderly?.simOnError || + !(e instanceof AcrossApiSimulationError) ) { - const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( - { - networkId: params.destinationChainId.toString(), - to: e.transaction.to, - data: e.transaction.data, - from: e.transaction.from, - value: e.transaction.value ?? "0", - }, - ); - throw new SimulationError({ - simulationId, - simulationUrl, - message: `simulation failed while fetching suggested fees: ${e.message}`, - }); + throw e; } - throw e; + + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly({ + networkId: params.destinationChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching suggested fees: ${e.message}`, + }); } } @@ -304,26 +307,24 @@ export class AcrossClient { return limits; } catch (e) { if ( - this.tenderlySimOnError && - this.isTenderlyEnabled && - e instanceof AcrossApiSimulationError + !this.tenderly?.simOnError || + !(e instanceof AcrossApiSimulationError) ) { - const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( - { - networkId: params.destinationChainId.toString(), - to: e.transaction.to, - data: e.transaction.data, - from: e.transaction.from, - value: e.transaction.value ?? "0", - }, - ); - throw new SimulationError({ - simulationId, - simulationUrl, - message: `simulation failed while fetching limits: ${e.message}`, - }); + throw e; } - throw e; + + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly({ + networkId: params.destinationChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching limits: ${e.message}`, + }); } } @@ -337,26 +338,24 @@ export class AcrossClient { return quote; } catch (e) { if ( - this.tenderlySimOnError && - this.isTenderlyEnabled && - e instanceof AcrossApiSimulationError + !this.tenderly?.simOnError || + !(e instanceof AcrossApiSimulationError) ) { - const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( - { - networkId: params.route.originChainId.toString(), - to: e.transaction.to, - data: e.transaction.data, - from: e.transaction.from, - value: e.transaction.value ?? "0", - }, - ); - throw new SimulationError({ - simulationId, - simulationUrl, - message: `simulation failed while fetching quote: ${e.message}`, - }); + throw e; } - throw e; + + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly({ + networkId: params.route.originChainId.toString(), + to: e.transaction.to, + data: e.transaction.data, + from: e.transaction.from, + value: e.transaction.value ?? "0", + }); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `simulation failed while fetching quote: ${e.message}`, + }); } } @@ -380,32 +379,31 @@ export class AcrossClient { return result; } catch (e) { if ( - this.tenderlySimOnError && - this.isTenderlyEnabled && - e instanceof ContractFunctionExecutionError + !this.tenderly?.simOnError || + !this.isTenderlyEnabled || + !(e instanceof ContractFunctionExecutionError) ) { - const { simulationId, simulationUrl } = await this.simulateTxOnTenderly( - { - networkId: params.deposit.originChainId.toString(), - from: e.sender!, - to: e.contractAddress!, - data: encodeFunctionData({ - abi: e.abi, - functionName: e.functionName, - args: e.args, - }), - value: params.deposit.isNative - ? String(params.deposit.inputAmount) - : "0", - }, - ); - throw new SimulationError({ - simulationId, - simulationUrl, - message: `deposit simulation failed: ${e.shortMessage}`, - }); + throw e; } - throw e; + + const { simulationId, simulationUrl } = await this.simulateTxOnTenderly({ + networkId: params.deposit.originChainId.toString(), + from: e.sender!, + to: e.contractAddress!, + data: encodeFunctionData({ + abi: e.abi, + functionName: e.functionName, + args: e.args, + }), + value: params.deposit.isNative + ? String(params.deposit.inputAmount) + : "0", + }); + throw new SimulationError({ + simulationId, + simulationUrl, + message: `deposit simulation failed: ${e.shortMessage}`, + }); } } @@ -464,21 +462,21 @@ export class AcrossClient { >, ) { if ( - !this.tenderlyAccessKey || - !this.tenderlyAccountSlug || - !this.tenderlyProjectSlug + !this.tenderly?.accessKey || + !this.tenderly?.accountSlug || + !this.tenderly?.projectSlug ) { throw new ConfigError( "Tenderly credentials not set. Client needs to be configured with " + - "'tenderlyAccessKey', 'tenderlyAccountSlug', and 'tenderlyProjectSlug'.", + "'tenderly.accessKey', 'tenderly.accountSlug', and 'tenderly.projectSlug'.", ); } return simulateTxOnTenderly({ ...params, - accessKey: this.tenderlyAccessKey, - accountSlug: this.tenderlyAccountSlug, - projectSlug: this.tenderlyProjectSlug, + accessKey: this.tenderly?.accessKey, + accountSlug: this.tenderly?.accountSlug, + projectSlug: this.tenderly?.projectSlug, enableShare: true, }); } From e8f0c817fc2b1851c9cb0edb994fedf853b6351b Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 11:31:03 +0200 Subject: [PATCH 07/11] fixup --- apps/example/scripts/sdk.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/example/scripts/sdk.ts b/apps/example/scripts/sdk.ts index 1de3862..1a48a51 100644 --- a/apps/example/scripts/sdk.ts +++ b/apps/example/scripts/sdk.ts @@ -49,10 +49,12 @@ async function main() { integratorId: "TEST", logLevel: "DEBUG", walletClient, - tenderlyAccessKey: process.env.TENDERLY_ACCESS_KEY, - tenderlyAccountSlug: process.env.TENDERLY_ACCOUNT_SLUG, - tenderlyProjectSlug: process.env.TENDERLY_PROJECT_SLUG, - tenderlySimOnError: true, + tenderly: { + accessKey: process.env.TENDERLY_ACCESS_KEY, + accountSlug: process.env.TENDERLY_ACCOUNT_SLUG, + projectSlug: process.env.TENDERLY_PROJECT_SLUG, + simOnError: true, + }, }); // do call to find info for displaying input/output tokens and destination chains From cb5ba0ffc9c70f47d5a5cb085876f007abc1e92a Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 11:37:28 +0200 Subject: [PATCH 08/11] fixup --- apps/example/scripts/sdk.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/example/scripts/sdk.ts b/apps/example/scripts/sdk.ts index 1a48a51..fda6758 100644 --- a/apps/example/scripts/sdk.ts +++ b/apps/example/scripts/sdk.ts @@ -50,9 +50,9 @@ async function main() { logLevel: "DEBUG", walletClient, tenderly: { - accessKey: process.env.TENDERLY_ACCESS_KEY, - accountSlug: process.env.TENDERLY_ACCOUNT_SLUG, - projectSlug: process.env.TENDERLY_PROJECT_SLUG, + accessKey: process.env.TENDERLY_ACCESS_KEY!, + accountSlug: process.env.TENDERLY_ACCOUNT_SLUG!, + projectSlug: process.env.TENDERLY_PROJECT_SLUG!, simOnError: true, }, }); From 61e7c37f68c714b7f34d10fbd113c6017b2bf2b4 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 16:25:47 +0200 Subject: [PATCH 09/11] hide `depositMethod` param --- packages/sdk/src/actions/getSuggestedFees.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdk/src/actions/getSuggestedFees.ts b/packages/sdk/src/actions/getSuggestedFees.ts index dc42dcb..8ee480d 100644 --- a/packages/sdk/src/actions/getSuggestedFees.ts +++ b/packages/sdk/src/actions/getSuggestedFees.ts @@ -19,7 +19,6 @@ type SuggestedFeesQueryParams = Partial< relayer?: Address; skipAmountLimit?: boolean; timestamp?: number; - depositMethod?: "depositV3" | "depositExclusive"; }; export type GetSuggestedFeesParams = SuggestedFeesQueryParams & { From 4f8e939c73f2e1478e79d23dc7758fc58f507f14 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 16:26:04 +0200 Subject: [PATCH 10/11] add errors to sdk script --- apps/example/scripts/sdk.ts | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/example/scripts/sdk.ts b/apps/example/scripts/sdk.ts index fda6758..3be1e01 100644 --- a/apps/example/scripts/sdk.ts +++ b/apps/example/scripts/sdk.ts @@ -261,13 +261,33 @@ async function main() { }); console.log(quoteRes); - // simulate deposit tx - should fail - const { request: simulateDepositTxRequest } = - await client.actions.simulateDepositTx({ - walletClient, - deposit: quoteRes.deposit, + try { + await client.actions.getLimits({ + inputToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + outputToken: "0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f", + originChainId: 1, + destinationChainId: 59144, + amount: "1000000000000000", + message: + "0xdbf42570000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000008800000000000000000000000000000000000000000000000000000000066e3f627000000000000000000000000d5e23607d5d73ff2293152f464c3cab005f8769600000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000002470a08231000000000000000000000000d5e23607d5d73ff2293152f464c3cab005f876960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c825e560ae6d6643115096b4b61e8f8d6a1974900000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000024b6b55f2500000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c825e560ae6d6643115096b4b61e8f8d6a1974900000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000002470a08231000000000000000000000000d5e23607d5d73ff2293152f464c3cab005f876960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c825e560ae6d6643115096b4b61e8f8d6a19749000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + recipient: "0xd5E23607D5d73ff2293152f464C3caB005f87696", + relayer: "0x07aE8551Be970cB1cCa11Dd7a11F47Ae82e70E67", }); - console.log("Simulation result:", simulateDepositTxRequest); + } catch (e) { + console.log("Fill simulation error", e); + } + + try { + // simulate deposit tx - should fail + const { request: simulateDepositTxRequest } = + await client.actions.simulateDepositTx({ + walletClient, + deposit: quoteRes.deposit, + }); + console.log("Simulation result:", simulateDepositTxRequest); + } catch (e) { + console.log("Deposit simulation error", e); + } } main(); From 9630603325c9084605b9865ef2553cf6f855b57a Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 25 Sep 2024 17:15:02 +0200 Subject: [PATCH 11/11] enable tenderly simOnError per default --- apps/example/scripts/sdk.ts | 1 - packages/sdk/src/client.ts | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/example/scripts/sdk.ts b/apps/example/scripts/sdk.ts index 3be1e01..8087af5 100644 --- a/apps/example/scripts/sdk.ts +++ b/apps/example/scripts/sdk.ts @@ -53,7 +53,6 @@ async function main() { accessKey: process.env.TENDERLY_ACCESS_KEY!, accountSlug: process.env.TENDERLY_ACCOUNT_SLUG!, projectSlug: process.env.TENDERLY_PROJECT_SLUG!, - simOnError: true, }, }); diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 0fd61e2..6d4b19c 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -132,6 +132,11 @@ export class AcrossClient { args?.logger ?? new DefaultLogger(args?.logLevel ?? CLIENT_DEFAULTS.logLevel); this.tenderly = args.tenderly; + + if (this.tenderly) { + this.tenderly.simOnError = args.tenderly?.simOnError ?? true; + } + // bind methods this.actions = { getSuggestedFees: this.getSuggestedFees.bind(this),