Skip to content

Commit

Permalink
feat: add initial getQuote utility (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
dohaki authored Sep 4, 2024
1 parent 1fdc3dc commit 8010130
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 38 deletions.
101 changes: 94 additions & 7 deletions apps/example/scripts/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AcrossClient } from "@across-toolkit/sdk";
import { encodeFunctionData, parseAbiItem, Address, Hex } from "viem";

// test using client with node
(async function main() {
Expand All @@ -7,12 +8,98 @@ import { AcrossClient } from "@across-toolkit/sdk";
integratorId: "TEST",
});

const res = await client.actions.getSuggestedFees({
token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
originChainId: 1,
destinationChainId: 10,
amount: "1000000000000000000",
});
// available routes
const routes = await client.actions.getAvailableRoutes({
originChainId: 42161,
destinationChainId: 1,
})!;
const route = routes.find((r) => r.inputTokenSymbol === "DAI")!;
console.log(route);

// quote
const inputAmount = 1000000000000000000000n;
const userAddress = "0x924a9f036260DdD5808007E1AA95f08eD08aA569";
// Aave v2 Lending Pool: https://etherscan.io/address/0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9
const aaveAddress = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9";
// DAI
const depositCurrency = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const aaveReferralCode = 0;

console.log(res);
const quoteRes = await client.actions.getQuote({
...route,
inputAmount,
recipient: "0x924a9f036260DdD5808007E1AA95f08eD08aA569",
crossChainMessage: {
actions: [
{
target: depositCurrency,
callData: generateApproveCallData({
aaveAddress,
depositAmount: inputAmount,
}),
value: 0n,
updateCallData: (outputAmount: bigint) =>
generateApproveCallData({
aaveAddress,
depositAmount: outputAmount,
}),
},
{
target: aaveAddress,
callData: generateDepositCallDataForAave({
userAddress,
depositAmount: inputAmount,
depositCurrency,
aaveReferralCode,
}),
value: 0n,
updateCallData: (outputAmount: bigint) =>
generateDepositCallDataForAave({
userAddress,
depositAmount: outputAmount,
depositCurrency,
aaveReferralCode,
}),
},
],
fallbackRecipient: "0x924a9f036260DdD5808007E1AA95f08eD08aA569",
},
});
console.log(quoteRes);
})();

function generateApproveCallData({
aaveAddress,
depositAmount,
}: {
aaveAddress: Address;
depositAmount: bigint;
}) {
const approveCallData = encodeFunctionData({
abi: [parseAbiItem("function approve(address spender, uint256 value)")],
args: [aaveAddress, depositAmount],
});

return approveCallData;
}

function generateDepositCallDataForAave({
userAddress,
depositAmount,
depositCurrency,
aaveReferralCode,
}: {
userAddress: Address;
depositAmount: bigint;
depositCurrency: Address;
aaveReferralCode: number;
}) {
return encodeFunctionData({
abi: [
parseAbiItem(
"function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)"
),
],
args: [depositCurrency, depositAmount, userAddress, aaveReferralCode],
});
}
25 changes: 16 additions & 9 deletions packages/sdk/src/actions/getAvailableRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,21 @@ export type AvailableRoutesResponse = {

export async function getAvailableRoutes(params?: AvailableRoutesParams) {
const client = getClient();
try {
const searchParams = params ? buildSearchParams(params) : "";

const res = await fetchAcross(
`${client.apiUrl}/available-routes?${searchParams}`
);
return (await res.json()) as AvailableRoutesResponse;
} catch (error) {
client.log.error(error);
}
const searchParams = params ? buildSearchParams(params) : "";

const res = await fetchAcross(
`${client.apiUrl}/available-routes?${searchParams}`
);
const data = (await res.json()) as AvailableRoutesResponse;

// Transform to internal type consistency
return data.map((route) => ({
originChainId: route.originChainId,
inputToken: route.originToken as Address,
destinationChainId: route.destinationChainId,
outputToken: route.destinationToken as Address,
inputTokenSymbol: route.originTokenSymbol,
outputTokenSymbol: route.destinationTokenSymbol,
}));
}
117 changes: 107 additions & 10 deletions packages/sdk/src/actions/getQuote.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,121 @@
import { Address, Hex } from "viem";
import { getClient } from "../client";
import { Amount, CrossChainAction } from "../types";
import {
getMultiCallHandlerAddress,
buildMulticallHandlerMessage,
} from "../utils";

export type QuoteParams = {
token: string;
inputToken: Address;
outputToken: Address;
originChainId: number;
destinationChainId: number;
amount: string; // bignumber string
inputAmount: Amount;
outputAmount?: Amount; // @todo add support for outputAmount
recipient?: Address;
crossChainMessage?:
| {
actions: CrossChainAction[];
fallbackRecipient: Address;
}
| Hex;
};

// @todo add support for "message" for Across+ integrations
export async function getQuote(params: QuoteParams) {
const client = getClient();
try {
const suggestedFees = await client.actions.getSuggestedFees(params);
if (!suggestedFees) {
client.log.error("suggested fees failed with params: \n", params);

const {
inputToken,
outputToken,
originChainId,
destinationChainId,
recipient: _recipient,
inputAmount,
crossChainMessage,
} = params;

let message = "0x";
let recipient = _recipient;

if (crossChainMessage && typeof crossChainMessage === "object") {
if (crossChainMessage.actions.length === 0) {
throw new Error("No 'crossChainMessage.actions' provided");
}

message = buildMulticallHandlerMessage({
actions: crossChainMessage.actions,
fallbackRecipient: crossChainMessage.fallbackRecipient,
});
recipient = getMultiCallHandlerAddress(destinationChainId);
}

const { outputAmount, ...fees } = await client.actions.getSuggestedFees({
inputToken,
outputToken,
originChainId,
destinationChainId,
amount: inputAmount,
recipient,
message,
});

// If a given cross-chain message is dependent on the outputAmount, update it
if (crossChainMessage && typeof crossChainMessage === "object") {
for (const action of crossChainMessage.actions) {
if (action.updateCallData) {
action.callData = action.updateCallData(outputAmount);
}
}
return suggestedFees;
} catch (error) {
client.log.error(error);
message = buildMulticallHandlerMessage({
actions: crossChainMessage.actions,
fallbackRecipient: crossChainMessage.fallbackRecipient,
});
}

const {
// partial deposit args
timestamp,
exclusiveRelayer,
exclusivityDeadline,
spokePoolAddress,
// limits
isAmountTooLow,
limits,
// fees
lpFee,
relayerGasFee,
relayerCapitalFee,
totalRelayFee,
// misc
estimatedFillTimeSec,
} = fees;

return {
deposit: {
inputAmount,
outputAmount,
originChainId,
destinationChainId,
recipient,
message,
quoteTimestamp: timestamp,
exclusiveRelayer,
exclusivityDeadline,
spokePoolAddress,
inputToken,
outputToken,
},
limits,
fees: {
lpFee,
relayerGasFee,
relayerCapitalFee,
totalRelayFee,
},
isAmountTooLow,
estimatedFillTimeSec,
};
}

export type QuoteResponse = {};
37 changes: 28 additions & 9 deletions packages/sdk/src/actions/getSuggestedFees.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
import { Address } from "viem";

import { getClient } from "../client";
import { buildSearchParams, fetchAcross } from "../utils";
import { Amount } from "../types";

export type SuggestedFeesParams = {
token: string;
inputToken: Address;
outputToken: Address;
originChainId: number;
destinationChainId: number;
amount: string; // bignumber string
amount: Amount;
recipient?: Address;
message?: string;
};

export async function getSuggestedFees(params: SuggestedFeesParams) {
const client = getClient();
try {
const searchParams = buildSearchParams(params);
const res = await fetchAcross(
`${client.apiUrl}/suggested-fees?${searchParams}`
const searchParams = buildSearchParams({
...params,
depositMethod: "depositExclusive",
});
const res = await fetchAcross(
`${client.apiUrl}/suggested-fees?${searchParams}`
);

if (!res.ok) {
throw new Error(
`Failed to fetch suggested fees: ${res.status}, ${await res.text()}`
);
return (await res.json()) as SuggestedFeesResponse;
} catch (error) {
client.log.error(error);
}

const data = (await res.json()) as SuggestedFeesResponse;

const outputAmount = BigInt(params.amount) - BigInt(data.totalRelayFee.total);
return {
// @todo: more data transformations for easier consumptions
...data,
outputAmount,
};
}

export type SuggestedFeesResponse = {
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./getSuggestedFees";
export * from "./getAvailableRoutes";
export * from "./getOriginChains";
export * from "./getLimits";
export * from "./getQuote";
3 changes: 3 additions & 0 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getSuggestedFees,
getLimits,
getOriginChains,
getQuote,
} from "./actions";
import { MAINNET_API_URL, TESTNET_API_URL } from "./constants";
import { LogLevel, DefaultLogger, LoggerT } from "./utils";
Expand Down Expand Up @@ -31,6 +32,7 @@ export class AcrossClient {
getAvailableRoutes: typeof getAvailableRoutes;
getLimits: typeof getLimits;
getOriginChains: typeof getOriginChains;
getQuote: typeof getQuote;
// ... actions go here
};

Expand All @@ -45,6 +47,7 @@ export class AcrossClient {
getAvailableRoutes: getAvailableRoutes.bind(this),
getLimits: getLimits.bind(this),
getOriginChains: getOriginChains.bind(this),
getQuote: getQuote.bind(this),
};

this.log.debug(
Expand Down
10 changes: 10 additions & 0 deletions packages/sdk/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Address, Hex } from "viem";

export type Amount = string | bigint;

export type CrossChainAction = {
target: Address;
callData: Hex;
value: Amount;
updateCallData?: (outputAmount: bigint) => Hex;
};
2 changes: 1 addition & 1 deletion packages/sdk/src/utils/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const fetchAcross = globalThis.fetch.bind(globalThis);
*/

export function buildSearchParams(
params: Record<string, number | string | Array<number | string>>
params: Record<string, number | bigint | string | Array<number | string>>
): string {
const searchParams = new URLSearchParams();
for (const key in params) {
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./fetch";
export * from "./logger";
export * from "./multicallHandler";
Loading

0 comments on commit 8010130

Please sign in to comment.