diff --git a/docs/docs/03-sdk/04-methods-reference/transfer/sweep.md b/docs/docs/03-sdk/04-methods-reference/transfer/sweep.md new file mode 100644 index 0000000..0dc75cf --- /dev/null +++ b/docs/docs/03-sdk/04-methods-reference/transfer/sweep.md @@ -0,0 +1,141 @@ +--- +id: sweep +title: sweep +sidebar_position: 3 +--- + +# `sweep` + +The `sweep` method generates a quote to transfer the full token balances of source chains to a destination chain. + +## Usage + +```typescript +import { Sprinter, Environment } from "@chainsafe/sprinter-sdk"; + +const sprinter = new Sprinter({ baseUrl: Environment.TESTNET }); + +const settings = { + account: "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519", + destinationChain: 11155111, + token: "USDC", +}; + +sprinter.sweep(settings).then((solution) => { + console.log(solution); +}); +``` + +## Parameters + +- `settings`: _(Required)_ An object containing the following fields: + + - `account`: The user’s address. + - `destinationChain`: The ID of the destination chain. + - `token`: The symbol of the token to be transferred (e.g., `USDC`, `ETH`). + - `recipient?`: _(Optional)_ The address of the recipient of the tokens on the destination chain. + - `sourceChains?`: _(Optional)_ An array of source chain IDs to be considered for the sweep. If omitted, Sprinter will use all available chains for the solution. To limit the solution to a specific chain, provide an array containing only that chain's ID. + +- `fetchOptions?`: _(Optional)_ An object containing `baseUrl` to override the default API endpoint for this request. + +### Example: Using `sourceChains` for sweeping from specific chains + +To get a sweep solution from specific chains, you can set `sourceChains` to an array with chain IDs. + +```typescript +const settings = { + account: "0xYourAddressHere", + destinationChain: 11155111, // Sepolia testnet + token: "USDC", + sourceChains: [84532, 137], +}; + +sprinter.sweep(settings).then((solution) => { + console.log(solution); +}); +``` + +### Example: Using `fetchOptions` + +```typescript +sprinter.sweep(settings, { baseUrl: "https://custom.api.url" }).then((solution) => { + console.log(solution); +}); +``` + +## Response + +Returns a promise that resolves to a `SolutionResponse`. + +```typescript +type SolutionResponse = Array | FailedSolution; + +interface Solution { + destinationChain: number; + destinationTokenAddress: string; + duration: number; // Time estimate in seconds + fee: Amount; + gasCost: Amount; + senderAddress: string; + sourceChain: number; + sourceTokenAddress: string; + tool: Tool; + transaction: Transaction; + approvals?: Array; + amount: Amount; +} + +interface FailedSolution { + error: string; +} +``` + +### Example Response + +import GasTip from "../\_gas-tip.md" + + + +```json +[ + { + "sourceChain": 84532, + "destinationChain": 11155111, + "sourceTokenAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "destinationTokenAddress": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "senderAddress": "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519", + "tool": { + "name": "Across", + "logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/acrossv2.png" + }, + "gasCost": { + "amount": "130680140710000", + "amountUSD": 0 + }, + "fee": { + "amount": "6239846", + "amountUSD": 0 + }, + "amount": "1178950000", + "duration": 120000000000, + "transaction": { + "data": "0x7b9392320000000000000000000000003e101ec02e7a48d16dade204c96bff842e7e25190000000000000000000000003e101ec02e7a48d16dade204c96bff842e7e2519000000000000000000000000036cbd53842c5426634e7929541ec2318f3dcf7e0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000464559700000000000000000000000000000000000000000000000000000000045e6230a0000000000000000000000000000000000000000000000000000000000aa36a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006780030c000000000000000000000000000000000000000000000000000000006780576c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000001dc0de", + "to": "0x82B564983aE7274c86695917BBf8C99ECb6F0F8F", + "from": "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519", + "value": "0x0", + "gasLimit": "0x16378", + "chainId": 84532 + }, + "approvals": [ + { + "data": "0x095ea7b300000000000000000000000082b564983ae7274c86695917bbf8c99ecb6f0f8f0000000000000000000000000000000000000000000000000000000046455970", + "to": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "from": "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519", + "value": "0x0", + "gasLimit": "0xe484", + "chainId": 84532 + } + ] + } +] +``` diff --git a/docs/docs/04-react-sdk/03-using-hooks.md b/docs/docs/04-react-sdk/03-using-hooks.md index 58c9aa3..a3f41ba 100644 --- a/docs/docs/04-react-sdk/03-using-hooks.md +++ b/docs/docs/04-react-sdk/03-using-hooks.md @@ -28,6 +28,7 @@ function YourComponent() { getUserBalances, getAvailableTokens, getAvailableChains, + getSweep, getTransfer, getTransferWithHook, getPoolAssetOnDestination, diff --git a/docs/docs/04-react-sdk/04-methods-reference/useSprinterTransfers.md b/docs/docs/04-react-sdk/04-methods-reference/useSprinterTransfers.md index d80437c..3c97c56 100644 --- a/docs/docs/04-react-sdk/04-methods-reference/useSprinterTransfers.md +++ b/docs/docs/04-react-sdk/04-methods-reference/useSprinterTransfers.md @@ -12,6 +12,7 @@ The `useSprinterTransfers` hook provides a simplified interface for generating c - `getTransferWithHook`: Single-hop token transfers combined with a contract call on the destination chain. - `getPoolAssetOnDestination`: Aggregates token balances across multiple chains into a single destination. - `getPoolAssetOnDestinationWithHook`: Aggregates token balances and performs a contract call on the destination chain. +- `getSweep`: A function that generates a quote to transfer the full token balances of source chains to a destination chain You can trigger any of these methods via the hook to fetch a cross-chain solution. @@ -59,6 +60,7 @@ The `useSprinterTransfers` hook returns an object with the following properties: - **`getTransferWithHook`**: A function that generates a single-hop token transfer combined with a contract call. - **`getPoolAssetOnDestination`**: A function that generates a solution to aggregate balances from multiple chains into a single destination. - **`getPoolAssetOnDestinationWithHook`**: A function that generates a solution to aggregate balances and execute a contract call on the destination chain. +- **`getSweep`**: A function that generates a quote to transfer the full token balances of source chains to a destination chain ## Example Response @@ -99,5 +101,6 @@ Each method accepts a `settings` parameter, which varies depending on the operat - **`getTransferWithHook`**: See [SDK Transfer with Contract Call Method Reference](../../sdk/methods-reference/transfer/transfer-with-hook). - **`getPoolAssetOnDestination`**: See [SDK Pool Asset Method Reference](../../sdk/methods-reference/pool-asset-on-destination/pool-asset-on-destination). - **`getPoolAssetOnDestinationWithHook`**: See [SDK Pool Asset with Contract Call Method Reference](../../sdk/methods-reference/pool-asset-on-destination/pool-asset-on-destination-with-hook). +- **`getSweep`**: See [Sweep Method Reference](../../sdk/methods-reference/transfer/sweep). Each method calls the Sprinter SDK's corresponding function and returns the intent-based solution for cross-chain transfers or contract calls. diff --git a/packages/react/lib/context.tsx b/packages/react/lib/context.tsx index cc3a345..0e1eeef 100644 --- a/packages/react/lib/context.tsx +++ b/packages/react/lib/context.tsx @@ -63,6 +63,7 @@ export function SprinterContext({ children, baseUrl }: SprinterContextProps) { getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook, + getSweep, } = useTransfers(sprinter); /** Initialization */ @@ -85,6 +86,7 @@ export function SprinterContext({ children, baseUrl }: SprinterContextProps) { getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook, + getSweep, }} > {children} diff --git a/packages/react/lib/hooks.ts b/packages/react/lib/hooks.ts index 36c0f1f..6839de5 100644 --- a/packages/react/lib/hooks.ts +++ b/packages/react/lib/hooks.ts @@ -83,7 +83,7 @@ export function useSprinterBalances(account: Address) { const balances: BalancesEntry = _balances[account] || balancesEmptyState; const getUserBalances = useCallback( () => _getUserBalances(account), - [_getUserBalances, account] + [_getUserBalances, account], ); return { balances, getUserBalances }; @@ -227,6 +227,7 @@ export function useSprinterChains() { * - `getTransferWithHook`: A function to get a transfer solution that includes an additional contract call on the destination chain. * - `getPoolAssetOnDestination`: A function to get a solution for pooling assets from multiple chains and transferring them to the destination chain. * - `getPoolAssetOnDestinationWithHook`: A function to get a solution for pooling assets and performing a contract call on the destination chain. + * - `getSweep`: A function to get a solution for sweeping assets from source chains to a destination chain. * * @example * ```ts @@ -269,6 +270,7 @@ export function useSprinterTransfers() { getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook, + getSweep, } = useSprinter(); return { solution, @@ -276,5 +278,6 @@ export function useSprinterTransfers() { getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook, + getSweep, }; } diff --git a/packages/react/lib/internal/useTransfers.ts b/packages/react/lib/internal/useTransfers.ts index 9781d4b..d380080 100644 --- a/packages/react/lib/internal/useTransfers.ts +++ b/packages/react/lib/internal/useTransfers.ts @@ -1,34 +1,59 @@ -import { - type SolutionResponse, - Sprinter, -} from "@chainsafe/sprinter-sdk"; -import {useCallback} from "react"; -import {useAsyncRequest} from "./useAsyncRequest"; -import type {Infer} from "superstruct"; +import { type SolutionResponse, Sprinter } from "@chainsafe/sprinter-sdk"; +import { useCallback } from "react"; +import { useAsyncRequest } from "./useAsyncRequest"; +import type { Infer } from "superstruct"; import { MultiHopSchema, MultiHopWithContractSchema, - SingleHopSchema, SingleHopWithContractSchema + SingleHopSchema, + SingleHopWithContractSchema, + SweepSchema, } from "@chainsafe/sprinter-sdk/dist/internal/validators"; export function useTransfers(sprinter: Sprinter) { const { state: solution, makeRequest } = useAsyncRequest(); - const getPoolAssetOnDestination = useCallback((settings: Infer) => { - makeRequest(sprinter.poolAssetOnDestination(settings)); - }, [sprinter, makeRequest]); + const getPoolAssetOnDestination = useCallback( + (settings: Infer) => { + makeRequest(sprinter.poolAssetOnDestination(settings)); + }, + [sprinter, makeRequest], + ); + + const getPoolAssetOnDestinationWithHook = useCallback( + (settings: Infer) => { + makeRequest(sprinter.poolAssetOnDestinationWithHook(settings)); + }, + [sprinter, makeRequest], + ); - const getPoolAssetOnDestinationWithHook = useCallback((settings: Infer) => { - makeRequest(sprinter.poolAssetOnDestinationWithHook(settings)); - }, [sprinter, makeRequest]); + const getTransfer = useCallback( + (settings: Infer) => { + makeRequest(sprinter.transfer(settings)); + }, + [sprinter, makeRequest], + ); - const getTransfer = useCallback((settings: Infer) => { - makeRequest(sprinter.transfer(settings)); - }, [sprinter, makeRequest]); + const getTransferWithHook = useCallback( + (settings: Infer) => { + makeRequest(sprinter.transferWithHook(settings)); + }, + [sprinter, makeRequest], + ); - const getTransferWithHook = useCallback((settings: Infer) => { - makeRequest(sprinter.transferWithHook(settings)); - }, [sprinter, makeRequest]); + const getSweep = useCallback( + (settings: Infer) => { + makeRequest(sprinter.sweep(settings)); + }, + [sprinter, makeRequest], + ); - return { solution, getTransfer, getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook }; + return { + solution, + getTransfer, + getTransferWithHook, + getPoolAssetOnDestination, + getPoolAssetOnDestinationWithHook, + getSweep, + }; } diff --git a/packages/react/src/App.tsx b/packages/react/src/App.tsx index c5c4fd4..5907cc4 100644 --- a/packages/react/src/App.tsx +++ b/packages/react/src/App.tsx @@ -1,14 +1,15 @@ -import { useState } from 'react' -import './App.css' -import {SprinterContext} from "../lib/main.ts"; -import {Component} from "./Component.tsx"; -import {Action} from "./Action.tsx"; +import { useState } from "react"; +import "./App.css"; +import { SprinterContext } from "../lib/main.ts"; +import { Component } from "./Component.tsx"; +import { Action } from "./Action.tsx"; +import { Environment } from "@chainsafe/sprinter-sdk"; function App() { - const [count, setCount] = useState(0) + const [count, setCount] = useState(0); return ( - +
@@ -26,7 +27,7 @@ function App() { Click on the Vite and React logos to learn more

- ) + ); } -export default App +export default App; diff --git a/packages/react/src/Component.tsx b/packages/react/src/Component.tsx index e738773..62bd8ac 100644 --- a/packages/react/src/Component.tsx +++ b/packages/react/src/Component.tsx @@ -1,12 +1,21 @@ import { useEffect } from "react"; -import { useSprinterTokens } from "../lib/hooks.ts"; +import { useSprinterTokens, useSprinterTransfers } from "../lib/hooks.ts"; export function Component() { - const hook = useSprinterTokens(); + const { getAvailableTokens } = useSprinterTokens(); + const { getSweep, solution } = useSprinterTransfers(); useEffect(() => { - hook.getAvailableTokens(); - }, [hook]); + getAvailableTokens(); + }, [getAvailableTokens]); - return
A component
; + useEffect(() => { + getSweep({ + account: "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519", + destinationChain: 11155111, + token: "USDC", + }); + }, [getSweep]); + + return
{JSON.stringify(solution, null, 2)}
; } diff --git a/packages/sdk/src/api.ts b/packages/sdk/src/api.ts index ad4ab41..1c07a8a 100644 --- a/packages/sdk/src/api.ts +++ b/packages/sdk/src/api.ts @@ -12,6 +12,7 @@ import type { Solution, SolutionOptions, SolutionResponse, + SweepSolutionOptions, TokenSymbol, } from "./types"; import { getEnv } from "./utils"; @@ -123,8 +124,8 @@ export async function getSolution( url.searchParams.set("token", token); url.searchParams.set("amount", String(amount)); // - if (threshold) url.searchParams.set("threshold", String(threshold)); - if (whitelistedSourceChains?.length) + threshold && url.searchParams.set("threshold", String(threshold)); + whitelistedSourceChains?.length && url.searchParams.set( "whitelistedSourceChains", whitelistedSourceChains.join(","), @@ -214,3 +215,31 @@ export async function getContractCallSolution( if ("error" in response) return response; return response.data; } + +export async function getSweepSolution( + { + account, + destinationChain, + recipient, + token, + sourceChains, + }: SweepSolutionOptions, + { baseUrl, signal }: FetchOptions = {}, +): Promise { + const url = new URL("/solutions/balance-sweep", baseUrl || BASE_URL); + + url.searchParams.set("account", account); + url.searchParams.set("destination", String(destinationChain)); + url.searchParams.set("token", token); + recipient && url.searchParams.set("recipient", recipient); + sourceChains?.length && + url.searchParams.set("whitelistedSourceChains", sourceChains.join(",")); + + const response = await fetch(url, { signal }).then( + (response) => + response.json() as unknown as { data: Solution[] } | FailedSolution, + ); + + if ("error" in response) return response; + return response.data; +} diff --git a/packages/sdk/src/internal/validators.ts b/packages/sdk/src/internal/validators.ts index cf11ec2..5d53d4d 100644 --- a/packages/sdk/src/internal/validators.ts +++ b/packages/sdk/src/internal/validators.ts @@ -79,3 +79,11 @@ export const MultiHopWithContractSchema = assign( BridgeCoreSchema, ContractCallSchema, ); + +export const SweepSchema = object({ + account: hexString(), + destinationChain: number(), + recipient: optional(hexString()), + token: string(), + sourceChains: optional(array(number())), +}); diff --git a/packages/sdk/src/sprinter.ts b/packages/sdk/src/sprinter.ts index fedb7e4..6e7cf50 100644 --- a/packages/sdk/src/sprinter.ts +++ b/packages/sdk/src/sprinter.ts @@ -7,6 +7,7 @@ import { getFungibleTokens, getSolution, getSupportedChains, + getSweepSolution, } from "./api"; import { formatBalances, getUserBalances } from "./internal/userBalances"; import { @@ -14,6 +15,7 @@ import { MultiHopWithContractSchema, SingleHopSchema, SingleHopWithContractSchema, + SweepSchema, } from "./internal/validators"; import type { Address, @@ -24,6 +26,7 @@ import type { FungibleToken, SolutionOptions, SolutionResponse, + SweepSolutionOptions, } from "./types"; export class Sprinter { @@ -476,6 +479,53 @@ export class Sprinter { ); } + /** + * Fetches and returns a quote to transfer full token balances of source chains to a destination chain. + * + * @param {Infer} settings - The settings object for defining the sweep parameters: + * - `account` {string}: The user's wallet address for the transaction. + * - `destinationChain` {number}: The ID of the destination blockchain. + * - `token` {string}: The token symbol (e.g., "ETH", "USDC") to be transferred. + * - `sourceChains` {Array} (optional): An array of source chain IDs to be considered for the transfer. + * - `recipient` {string} (optional): The address of the recipient of the tokens on the destination chain. + * + * @param {FetchOptions} [options] - Optional configuration for the fetch request, such as custom headers or query parameters. + * + * @returns {Promise} A promise that resolves to the solution object containing the optimal sweep strategy and contract call, or a `FailedSolution` in case of an error. + * + * @example + * ```ts + * import { Sprinter } from '@chainsafe/sprinter-sdk'; + * + * const sprinter = new Sprinter(); + * + * const settings = { + * account: "0x3e101ec02e7a48d16dade204c96bff842e7e2519", + * destinationChain: 11155111, + * sourceChains: [84532, 137], + * token: "USDC", + * recipient: "0xRecipientAddress", + * }; + * + * sprinter.sweep(settings).then(solution => { + * console.log(solution); + * }).catch(error => { + * console.error(error); + * }); + * ``` + */ + public async sweep( + settings: Infer, + options?: FetchOptions, + ): Promise { + assert(settings, SweepSchema); + + return await getSweepSolution( + settings as SweepSolutionOptions, + this.makeFetchOptions(options), + ); + } + private deferredRequest( name: string, request: () => Promise, diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index a44e149..36961c9 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -66,6 +66,14 @@ export interface ContractSolutionOptions extends SolutionOptions { contractCall: ContractCallSolutionOptions; } +export interface SweepSolutionOptions { + account: Address; + destinationChain: ChainID; + token: TokenSymbol; + sourceChains?: ChainID[]; + recipient?: Address; +} + interface Amount { amount: string; amountUSD: number;