Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add tsdoc annotations for main methods
Browse files Browse the repository at this point in the history
dohaki committed Sep 26, 2024
1 parent a73f39c commit c9c2368
Showing 6 changed files with 512 additions and 56 deletions.
47 changes: 46 additions & 1 deletion packages/sdk/src/actions/executeQuote.ts
Original file line number Diff line number Diff line change
@@ -105,20 +105,65 @@ export type TransactionProgress =

type TxRequest = Awaited<SimulateContractReturnType>["request"];

/**
* Params for {@link executeQuote}.
*/
export type ExecuteQuoteParams = {
logger?: LoggerT;
/**
* An identifier for the integrator.
*/
integratorId: string;
/**
* The deposit to execute. Should be taken from return value of {@link getQuote}.
*/
deposit: Quote["deposit"];
/**
* The wallet client to use for the deposit.
*/
walletClient: ConfiguredWalletClient;
/**
* The public client for the origin chain.
*/
originClient: ConfiguredPublicClient;
/**
* The public client for the destination chain.
*/
destinationClient: ConfiguredPublicClient;
/**
* Whether to use an infinite approval for the SpokePool contract.
*/
infiniteApproval?: boolean;
/**
* Whether to skip the allowance check.
*/
skipAllowanceCheck?: boolean;
/**
* Whether to throw if an error occurs.
*/
throwOnError?: boolean;
/**
* Whether to force the origin chain by switching to it if necessary.
*/
forceOriginChain?: boolean;
/**
* A handler for the execution progress. See {@link ExecutionProgress} for steps.
*/
onProgress?: (progress: ExecutionProgress) => void;
/**
* The logger to use.
*/
logger?: LoggerT;
};

/**
* Executes a quote by:
* 1. Approving the SpokePool contract if necessary
* 2. Depositing the input token on the origin chain
* 3. Waiting for the deposit to be filled on the destination chain
* @param params - See {@link ExecuteQuoteParams}.
* @returns The deposit ID and receipts for the deposit and fill transactions.
* @public
*/
export async function executeQuote(params: ExecuteQuoteParams) {
const {
integratorId,
27 changes: 25 additions & 2 deletions packages/sdk/src/actions/getAvailableRoutes.ts
Original file line number Diff line number Diff line change
@@ -4,25 +4,48 @@ import { Route } from "../types";
import { MAINNET_API_URL } from "../constants";

export type RoutesQueryParams = Partial<{
/**
* The origin token address. If set only routes with this token as origin are returned.
*/
originToken: Address;
/**
* The destination token address. If set only routes with this token as destination
* are returned.
*/
destinationToken: Address;
/**
* The destination chain id. If set only routes with this chain id as destination
* are returned.
*/
destinationChainId: number;
/**
* The origin chain id. If set only routes with this chain id as origin are returned.
*/
originChainId: number;
}>;

/**
* Params for {@link getAvailableRoutes}.
*/
export type GetAvailableRoutesParams = RoutesQueryParams &
Partial<{
apiUrl: string;
logger: LoggerT;
}>;

export type AvailableRoutesResponse = Route[];
export type GetAvailableRoutesReturnType = Route[];

/**
* Get the available routes for a given set of parameters.
* @param params - See {@link GetAvailableRoutesParams}.
* @returns See {@link GetAvailableRoutesReturnType}.
* @public
*/
export async function getAvailableRoutes({
apiUrl = MAINNET_API_URL,
logger,
...params
}: GetAvailableRoutesParams): Promise<AvailableRoutesResponse> {
}: GetAvailableRoutesParams): Promise<GetAvailableRoutesReturnType> {
const routes = await fetchAcrossApi<AvailableRoutesApiResponse>(
`${apiUrl}/available-routes`,
params,
76 changes: 59 additions & 17 deletions packages/sdk/src/actions/getLimits.ts
Original file line number Diff line number Diff line change
@@ -4,39 +4,81 @@ import { MAINNET_API_URL } from "../constants";
import { Amount } from "../types";

type LimitsQueryParams = {
originChainId: number;
destinationChainId: number;
inputToken: Address;
outputToken: Address;
originChainId: number;
/**
* [Optional] The input amount of the deposit. Defaults to a small simulation amount.
* Should in most cases be omitted but is required when using Across+, i.e. when
* a cross-chain message is attached to the deposit.
*/
amount?: Amount;
/**
* [Optional] The cross-chain message of the deposit when using Across+ that should
* be executed on the destination chain. Note that `amount` is required when using
* Across+.
*/
message?: Hex;
/**
* [Optional] The recipient address. Should in most cases be omitted but is required
* when using Across+, i.e. when a cross-chain message is attached to the deposit.
* This needs to be the address of the handler contract on the destination chain.
*/
recipient?: Address;
/**
* [Optional] The relayer address to simulate fill with. Defaults to the Across relayer.
*/
relayer?: Address;
};

/**
* Params for {@link getLimits}.
*/
export type GetLimitsParams = LimitsQueryParams & {
/**
* [Optional] The Across API URL to use. Defaults to the mainnet API URL.
*/
apiUrl?: string;
/**
* [Optional] The logger to use.
*/
logger?: LoggerT;
};

// might not be necessary if this is part of suggested fees response???
export type GetLimitsReturnType = {
/**
* The minimum deposit amount for the route.
*/
minDeposit: bigint;
/**
* The maximum deposit amount for the route.
*/
maxDeposit: bigint;
/**
* The maximum deposit amount for the route that can be executed instantly.
*/
maxDepositInstant: bigint;
};

/**
* Returns the deposit limits for a given route.
* @param params - See {@link GetLimitsParams}.
* @returns See {@link GetLimitsReturnType}.
*/
export async function getLimits({
apiUrl = MAINNET_API_URL,
logger,
...params
}: GetLimitsParams) {
const limits = await fetchAcrossApi<LimitsResponse>(
`${apiUrl}/limits`,
params,
logger,
);
return limits;
}: GetLimitsParams): Promise<GetLimitsReturnType> {
const limits = await fetchAcrossApi<{
minDeposit: string;
maxDeposit: string;
maxDepositInstant: string;
}>(`${apiUrl}/limits`, params, logger);
return {
minDeposit: BigInt(limits.minDeposit),
maxDeposit: BigInt(limits.maxDeposit),
maxDepositInstant: BigInt(limits.maxDepositInstant),
};
}

export type LimitsResponse = {
minDeposit: string;
maxDeposit: string;
maxDepositInstant: string;
maxDepositShortDelay: string;
recommendedDepositInstant: string;
};
102 changes: 94 additions & 8 deletions packages/sdk/src/actions/getQuote.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,58 @@
import { Address, Hex } from "viem";
import { Amount, CrossChainAction, Route } from "../types";
import { Amount, CrossChainAction } from "../types";
import {
getMultiCallHandlerAddress,
buildMulticallHandlerMessage,
LoggerT,
} from "../utils";
import { getSuggestedFees } from "./getSuggestedFees";

/**
* Params for {@link getQuote}.
*/
export type GetQuoteParams = {
route: Route;
route: {
/**
* The origin chain id for deposit route.
*/
originChainId: number;
/**
* The destination chain id for deposit route.
*/
destinationChainId: number;
/**
* The input token for deposit route.
*/
inputToken: Address;
/**
* The output token for deposit route.
*/
outputToken: Address;
/**
* Whether the input token is a native token on the origin chain.
* Defaults to `false`. Should be set to `true` for ETH only if origin chain is not
* Polygon.
*/
isNative?: boolean;
};
/**
* The input amount for deposit route.
*/
inputAmount: Amount;
/**
* [Optional] The logger to use.
*/
logger?: LoggerT;
/**
* [Optional] The Across API URL to use. Defaults to the mainnet API URL.
*/
apiUrl?: string;
outputAmount?: Amount; // @todo add support for outputAmount
/**
* [Optional] The recipient address. Should in most cases be omitted but is required
* when using Across+, i.e. when a cross-chain message is attached to the deposit.
* This needs to be the address of the handler contract on the destination chain.
*/
recipient?: Address;

/**
* A cross-chain message to be executed on the destination chain. Can either
* be a pre-constructed hex string or an object containing the actions to be
@@ -28,9 +66,57 @@ export type GetQuoteParams = {
| Hex;
};

export type Quote = Awaited<ReturnType<typeof getQuote>>;
export type Quote = {
deposit: {
inputAmount: bigint;
outputAmount: bigint;
recipient: Address;
message: Hex;
quoteTimestamp: number;
exclusiveRelayer: Address;
exclusivityDeadline: number;
spokePoolAddress: Address;
destinationSpokePoolAddress: Address;
originChainId: number;
destinationChainId: number;
inputToken: Address;
outputToken: Address;
isNative?: boolean;
};
limits: {
minDeposit: bigint;
maxDeposit: bigint;
maxDepositInstant: bigint;
};
fees: {
lpFee: {
pct: bigint;
total: bigint;
};
relayerGasFee: {
pct: bigint;
total: bigint;
};
relayerCapitalFee: {
pct: bigint;
total: bigint;
};
totalRelayFee: {
pct: bigint;
total: bigint;
};
};
isAmountTooLow: boolean;
estimatedFillTimeSec: number;
};

export async function getQuote(params: GetQuoteParams) {
/**
* Get a quote for a given set of parameters.
* @param params - See {@link GetQuoteParams}.
* @returns See {@link Quote}.
* @public
*/
export async function getQuote(params: GetQuoteParams): Promise<Quote> {
const {
route,
recipient: _recipient,
@@ -110,9 +196,9 @@ export async function getQuote(params: GetQuoteParams) {

return {
deposit: {
inputAmount,
inputAmount: BigInt(inputAmount),
outputAmount,
recipient,
recipient: recipient as Address,
message,
quoteTimestamp: Number(timestamp),
exclusiveRelayer: exclusiveRelayer as Address,
187 changes: 165 additions & 22 deletions packages/sdk/src/actions/getSuggestedFees.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,148 @@
import { Address } from "viem";
import { LoggerT, fetchAcrossApi } from "../utils";
import { Amount, Route } from "../types";
import { Amount } from "../types";
import { MAINNET_API_URL } from "../constants";

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;
};
export type SuggestedFeesQueryParams = {
originChainId: number;
destinationChainId: number;
/**
* The input token address on origin chain.
*/
inputToken: Address;
/**
* The output token address on destination chain.
*/
outputToken: Address;
/**
* The amount of input tokens to deposit.
*/
amount: Amount;
/**
* [Optional] Whether the input token is a native token on the origin chain.
* Defaults to `false`. Should be set to `true` for ETH only if origin chain is not
* Polygon.
*/
isNative?: boolean;
/**
* [Optional] The cross-chain message of the deposit when using Across+ that should
* be executed on the destination chain. Note that `amount` is required when using
* Across+.
*/
message?: string;
/**
* [Optional] The recipient address. Should in most cases be omitted but is required
* when using Across+, i.e. when a cross-chain message is attached to the deposit.
* This needs to be the address of the handler contract on the destination chain.
*/
recipient?: Address;
/**
* [Optional] The relayer address to simulate fill with. Defaults to the Across relayer.
*/
relayer?: Address;
/**
* [Optional] Whether to skip the amount limit check. Defaults to `false`.
*/
skipAmountLimit?: boolean;
};

/**
* Params for {@link getSuggestedFees}.
*/
export type GetSuggestedFeesParams = SuggestedFeesQueryParams & {
/**
* [Optional] The Across API URL to use. Defaults to the mainnet API URL.
*/
apiUrl?: string;
/**
* [Optional] The logger to use.
*/
logger?: LoggerT;
};

export type GetSuggestedFeesReturnType = {
/**
* The estimated fill time in seconds.
*/
estimatedFillTimeSec: number;
/**
* The timestamp of the quote.
*/
timestamp: number;
/**
* Whether the deposit amount is too low.
*/
isAmountTooLow: boolean;
/**
* The quote block.
*/
quoteBlock: number;
/**
* The exclusive relayer address. Will be the zero address if no exclusivity is
* determined.
*/
exclusiveRelayer: string;
/**
* The exclusivity deadline. Will be 0 if no exclusivity is determined.
*/
exclusivityDeadline: number;
/**
* The spoke pool address on the origin chain.
*/
spokePoolAddress: Address;
/**
* The spoke pool address on the destination chain.
*/
destinationSpokePoolAddress: Address;
/**
* The output amount that will be received after deducting the fees.
*/
outputAmount: bigint;
/**
* The total relay fee, i.e. the sum of the relayer capital fee, the relayer gas fee,
* and the lp fee.
*/
totalRelayFee: {
pct: bigint;
total: bigint;
};
/**
* The relayer capital fee.
*/
relayerCapitalFee: {
pct: bigint;
total: bigint;
};
/**
* The relayer gas fee.
*/
relayerGasFee: {
pct: bigint;
total: bigint;
};
/**
* The lp fee.
*/
lpFee: {
pct: bigint;
total: bigint;
};
/**
* The deposit limits.
*/
limits: {
minDeposit: bigint;
maxDeposit: bigint;
maxDepositInstant: bigint;
};
};

/**
* Returns the suggested fees for a given deposit route.
* @param params - See {@link GetSuggestedFeesParams}.
* @returns See {@link GetSuggestedFeesReturnType}.
* @public
*/
export async function getSuggestedFees({
apiUrl = MAINNET_API_URL,
logger,
@@ -40,11 +157,37 @@ export async function getSuggestedFees({
logger,
);

const outputAmount = BigInt(params.amount) - BigInt(data.totalRelayFee.total);
return {
// @todo: more data transformations for easier consumptions
...data,
outputAmount,
estimatedFillTimeSec: data.estimatedFillTimeSec,
outputAmount: BigInt(params.amount) - BigInt(data.totalRelayFee.total),
timestamp: Number(data.timestamp),
isAmountTooLow: data.isAmountTooLow,
quoteBlock: Number(data.quoteBlock),
exclusiveRelayer: data.exclusiveRelayer as Address,
exclusivityDeadline: data.exclusivityDeadline,
spokePoolAddress: data.spokePoolAddress as Address,
destinationSpokePoolAddress: data.destinationSpokePoolAddress as Address,
totalRelayFee: {
pct: BigInt(data.totalRelayFee.pct),
total: BigInt(data.totalRelayFee.total),
},
relayerCapitalFee: {
pct: BigInt(data.relayerCapitalFee.pct),
total: BigInt(data.relayerCapitalFee.total),
},
relayerGasFee: {
pct: BigInt(data.relayerGasFee.pct),
total: BigInt(data.relayerGasFee.total),
},
lpFee: {
pct: BigInt(data.lpFee.pct),
total: BigInt(data.lpFee.total),
},
limits: {
minDeposit: BigInt(data.limits.minDeposit),
maxDeposit: BigInt(data.limits.maxDeposit),
maxDepositInstant: BigInt(data.limits.maxDepositInstant),
},
};
}

129 changes: 123 additions & 6 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
@@ -14,8 +14,11 @@ import {
WaitForDepositTxParams,
GetFillByDepositTxParams,
GetAvailableRoutesParams,
GetAvailableRoutesReturnType,
GetSuggestedFeesParams,
GetSuggestedFeesReturnType,
GetLimitsParams,
GetLimitsReturnType,
simulateDepositTx,
SimulateDepositTxParams,
waitForFillTx,
@@ -57,25 +60,77 @@ const CLIENT_DEFAULTS = {
logLevel: "ERROR",
} as const;

/** Options for {@link AcrossClient.create} */
export type AcrossClientOptions = {
/**
* An identifier representing the integrator.
*/
integratorId: string;
/**
* The chains to use for the Across API. Should be imported from `viem/chains`.
*/
chains: Chain[];
/**
* A wallet client to use for the Across API.
*/
walletClient?: ConfiguredWalletClient;
/**
* The RPC URLs to use for the Across API. Will fall back to the default public RPC URL
* for the chain if not specified.
*/
rpcUrls?: {
[key: number]: string;
[chainId: number]: string;
};
logLevel?: LogLevel; // for default logger
/**
* The log level to use. Defaults to `"ERROR"`.
*/
logLevel?: LogLevel;
/**
* Whether to use the testnet API. Defaults to `false`.
*/
useTestnet?: boolean;
/**
* A custom logger to use for the Across API. Defaults to a console logger.
*/
logger?: LoggerT;
pollingInterval?: number; // milliseconds seconds
/**
* The polling interval in milliseconds to use for the Across API.
* Defaults to `3_000` milliseconds.
*/
pollingInterval?: number;
/**
* Tenderly related options. Can be used for additional debugging support on Tenderly.
* @see https://tenderly.co/transaction-simulator
*/
tenderly?: {
/**
* Whether to automatically simulate transactions on Tenderly when an error occurs.
* Defaults to `true` if credentials `tenderly.accessKey`, `tenderly.accountSlug`,
* and `tenderly.projectSlug` are provided.
*/
simOnError?: boolean;
/**
* The Tenderly API access key.
* @see https://docs.tenderly.co/account/projects/how-to-generate-api-access-token
*/
accessKey: string;
/**
* The Tenderly account slug.
* @see https://docs.tenderly.co/account/projects/account-project-slug
*/
accountSlug: string;
/**
* The Tenderly project slug.
* @see https://docs.tenderly.co/account/projects/account-project-slug
*/
projectSlug: string;
};
};

/**
* Entrypoint for the Across Integrator SDK
* @public
*/
export class AcrossClient {
private static instance: AcrossClient | null = null;

@@ -120,6 +175,9 @@ export class AcrossClient {
simulateTxOnTenderly: AcrossClient["simulateTxOnTenderly"];
};

/**
* @internal
*/
private constructor(args: AcrossClientOptions) {
this.integratorId = args.integratorId;
this.walletClient = args?.walletClient;
@@ -162,13 +220,26 @@ export class AcrossClient {
this.logger.debug("Client created with args: \n", args);
}

/**
* Create a new `AcrossClient` instance as a singleton.
* @param options - See {@link AcrossClientOptions}.
* @returns A new `AcrossClient` instance if it doesn't exist, otherwise the existing
* instance.
* @public
*/
public static create(options: AcrossClientOptions): AcrossClient {
if (this.instance === null) {
this.instance = new AcrossClient(options);
}
return this.instance;
}

/**
* Get the existing `AcrossClient` singleton instance.
* @returns The existing `AcrossClient` instance.
* @throws If the instance is not initialized.
* @public
*/
public static getInstance(): AcrossClient {
if (this.instance === null) {
throw new Error(
@@ -182,6 +253,9 @@ export class AcrossClient {
this.walletClient = params.walletClient;
}

/**
* @internal
*/
getPublicClient(chainId: number): ConfiguredPublicClient {
const client = this.publicClients.get(chainId);
if (!client) {
@@ -191,6 +265,24 @@ export class AcrossClient {
return client;
}

/**
* Execute a quote by:
* 1. Approving the SpokePool contract if necessary
* 2. Depositing the input token on the origin chain
* 3. Waiting for the deposit to be filled on the destination chain
*
* See {@link executeQuote} for more details.
*
* @example
* ```ts
* const quote = await client.getQuote({ route, inputAmount });
* const { depositId } = await client.executeQuote({ deposit: quote.deposit });
* ```
*
* @param params - See {@link ExecuteQuoteParams}.
* @returns The deposit ID and receipts for the deposit and fill transactions.
* @public
*/
async executeQuote(
params: Omit<
ExecuteQuoteParams,
@@ -262,19 +354,31 @@ export class AcrossClient {
}
}

/**
* Get the available routes for a given set of parameters. See {@link getAvailableRoutes}.
* @param params - See {@link GetAvailableRoutesParams}.
* @returns See {@link GetAvailableRoutesReturnType}.
* @public
*/
async getAvailableRoutes(
params: Omit<GetAvailableRoutesParams, "apiUrl" | "logger">,
) {
): Promise<GetAvailableRoutesReturnType> {
return getAvailableRoutes({
...params,
apiUrl: this.apiUrl,
logger: this.logger,
});
}

/**
* Get the suggested fees for a given route. See {@link getSuggestedFees}.
* @param params - See {@link GetSuggestedFeesParams}.
* @returns See {@link GetSuggestedFeesReturnType}.
* @public
*/
async getSuggestedFees(
params: Omit<GetSuggestedFeesParams, "apiUrl" | "logger">,
) {
): Promise<GetSuggestedFeesReturnType> {
try {
const fees = await getSuggestedFees({
...params,
@@ -306,7 +410,14 @@ export class AcrossClient {
}
}

async getLimits(params: Omit<GetLimitsParams, "apiUrl" | "logger">) {
/**
* Get the deposit limits for a given route. See {@link getLimits}.
* @param params - See {@link GetLimitsParams}.
* @returns See {@link GetLimitsReturnType}.
*/
async getLimits(
params: Omit<GetLimitsParams, "apiUrl" | "logger">,
): Promise<GetLimitsReturnType> {
try {
const limits = await getLimits({
...params,
@@ -337,6 +448,12 @@ export class AcrossClient {
}
}

/**
* Get a quote for a given set of parameters. See {@link getQuote}.
* @param params - See {@link GetQuoteParams}.
* @returns See {@link Quote}.
* @public
*/
async getQuote(params: Omit<GetQuoteParams, "logger" | "apiUrl">) {
try {
const quote = await getQuote({

0 comments on commit c9c2368

Please sign in to comment.