Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: simulate via tenderly #17

Merged
merged 12 commits into from
Sep 25, 2024
20 changes: 17 additions & 3 deletions apps/example/scripts/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ async function main() {
integratorId: "TEST",
logLevel: "DEBUG",
walletClient,
tenderly: {
accessKey: process.env.TENDERLY_ACCESS_KEY!,
accountSlug: process.env.TENDERLY_ACCOUNT_SLUG!,
projectSlug: process.env.TENDERLY_PROJECT_SLUG!,
pxrl marked this conversation as resolved.
Show resolved Hide resolved
simOnError: true,
},
});

// do call to find info for displaying input/output tokens and destination chains
Expand Down Expand Up @@ -196,16 +202,16 @@ 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(
(route) => route.inputTokenSymbol === "DAI",
)!;

// quote
const inputAmount = parseUnits("10", inputTokenDetails.decimals);
const inputAmount = parseUnits("200", inputTokenDetails.decimals);
pxrl marked this conversation as resolved.
Show resolved Hide resolved
const userAddress = "0x924a9f036260DdD5808007E1AA95f08eD08aA569";
// Aave v2 Lending Pool: https://etherscan.io/address/0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9
const aaveAddress = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9";
Expand Down Expand Up @@ -254,6 +260,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();

Expand Down
22 changes: 7 additions & 15 deletions packages/sdk/src/actions/getAvailableRoutes.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -23,22 +23,14 @@ export async function getAvailableRoutes({
logger,
...params
}: GetAvailableRoutesParams): Promise<AvailableRoutesResponse> {
const searchParams = params
? buildSearchParams<RoutesQueryParams>(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<AvailableRoutesApiResponse>(
`${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,
Expand Down
26 changes: 5 additions & 21 deletions packages/sdk/src/actions/getFillByDepositTx.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildSearchParams, isOk } from "../utils";
import { fetchIndexerApi } from "../utils";
import { DepositStatus } from "./waitForDepositTx";
import {
Hash,
Expand All @@ -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<Quote, "deposit"> & {
depositId: DepositStatus["depositId"];
depositTransactionHash: Hash;
Expand All @@ -39,24 +34,13 @@ export async function getFillByDepositTx(
} = params;

try {
const url = `${indexerUrl}/deposit/status?${buildSearchParams<DepositStatusQueryParams>(
const data = await fetchIndexerApi<IndexerStatusResponse>(
`${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({
Expand Down
29 changes: 13 additions & 16 deletions packages/sdk/src/actions/getLimits.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Address } from "viem";
import { buildSearchParams, isOk, LoggerT } from "../utils";
import { Address, Hex } from "viem";
import { fetchAcrossApi, LoggerT } from "../utils";
import { MAINNET_API_URL } from "../constants";
import { HttpError } from "../errors";
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 & {
Expand All @@ -21,19 +25,12 @@ export async function getLimits({
logger,
...params
}: GetLimitsParams) {
const searchParams = buildSearchParams<LimitsQueryParams>(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<LimitsResponse>(
`${apiUrl}/limits`,
params,
logger,
);
return limits;
}

export type LimitsResponse = {
Expand Down
44 changes: 16 additions & 28 deletions packages/sdk/src/actions/getSuggestedFees.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
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";

type SuggestedFeesQueryParams = Partial<Omit<Route, "originChainId">> &
Pick<Route, "originChainId"> & {
type SuggestedFeesQueryParams = Partial<
Omit<Route, "inputTokenSymbol" | "outputTokenSymbol" | "isNative">
> &
Pick<
Route,
| "originChainId"
| "destinationChainId"
| "inputTokenSymbol"
| "outputTokenSymbol"
> & {
amount: Amount;
recipient?: Address;
message?: string;
relayer?: Address;
skipAmountLimit?: boolean;
timestamp?: number;
depositMethod?: string; // "depositV3" | "depositExclusive"
depositMethod?: "depositV3" | "depositExclusive";
dohaki marked this conversation as resolved.
Show resolved Hide resolved
};

export type GetSuggestedFeesParams = SuggestedFeesQueryParams & {
Expand All @@ -24,35 +32,15 @@ export async function getSuggestedFees({
logger,
...params
}: GetSuggestedFeesParams) {
const searchParams = buildSearchParams<SuggestedFeesQueryParams>({
...params,
depositMethod: "depositExclusive",
});

const url = `${apiUrl}/suggested-fees?${searchParams}`;

logger?.debug(
"Fetching suggested fees for params:",
const data = await fetchAcrossApi<SuggestedFeesResponse>(
`${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
Expand Down
82 changes: 28 additions & 54 deletions packages/sdk/src/actions/simulateDepositTx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
BaseError,
ContractFunctionRevertedError,
PublicClient,
SimulateContractReturnType,
WalletClient,
Expand Down Expand Up @@ -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;
Expand All @@ -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),
pxrl marked this conversation as resolved.
Show resolved Hide resolved
exclusiveRelayer,
quoteTimestamp,
fillDeadline,
exclusivityDeadline,
message,
],
value: isNative ? BigInt(inputAmount) : 0n,
dataSuffix: getIntegratorDataSuffix(integratorId),
});

logger?.debug("Simulation result", result);

return result as unknown as SimulateContractReturnType;
}
Loading