# Get a set of routes for a request that describes a transfer of tokens
Source: https://docs.li.fi/api-reference/advanced/get-a-set-of-routes-for-a-request-that-describes-a-transfer-of-tokens
post /v1/advanced/routes
In order to execute any transfer, you must first request possible `Routes`. From the result set a `Route` can be selected and executed by retrieving the transaction for every included `Step` using the `/steps/transaction` endpoint.
**Attention**: This request is more complex and intended to be used via our [JavaScript SDK](https://docs.li.fi/integrate-li.fi-js-sdk/install-li.fi-sdk).
Need opinionated routing defaults? Include `options.preset` in the request body and review the
[API presets overview](/api-reference/presets/overview) for the supported configurations.
# Populate a step with transaction data
Source: https://docs.li.fi/api-reference/advanced/populate-a-step-with-transaction-data
post /v1/advanced/stepTransaction
This endpoint expects a full `Step` object which usually is retrieved by calling the `/advanced/routes` endpoint and selecting the most suitable `Route`. Afterwards the transaction for every required `Step` can be retrieved using this endpoint.
**Attention**: This request is more complex and intended to be used via our [JavaScript SDK](https://docs.li.fi/integrate-li.fi-js-sdk/install-li.fi-sdk).
# Check the status of a cross chain transfer
Source: https://docs.li.fi/api-reference/check-the-status-of-a-cross-chain-transfer
get /v1/status
Cross chain transfers might take a while to complete. Waiting on the transaction on the sending chain doesn't help here. For this reason we build a simple endpoint that let's you check the status of your transfer.
Important: The endpoint returns a `200` successful response even if the transaction can not be found. This behavior accounts for the case that the transaction hash is valid but the transaction has not been mined yet.
While none of the parameters `fromChain`, `toChain` and `bridge` are required, passing the `fromChain` parameter will speed up the request and is therefore encouraged.
If you want to learn more about how to use this endpoint please have a look at our [guide](/introduction/user-flows-and-examples/status-tracking).
# Error Codes
Source: https://docs.li.fi/api-reference/error-codes
Exhaustive list of possible error codes
## API status codes
API status code is the code returned by the server like 200, 404, 429, 500, 502.
## API error codes
API returns the following set of error codes:
* DefaultError = 1000,
* FailedToBuildTransactionError = 1001,
* NoQuoteError = 1002,
* NotFoundError = 1003,
* NotProcessableError = 1004,
* RateLimitError = 1005,
* ServerError = 1006,
* SlippageError = 1007,
* ThirdPartyError = 1008,
* TimeoutError = 1009,
* UnauthorizedError = 1010,
* ValidationError = 1011,
* RpcFailure = 1012,
* MalformedSchema = 1013,
## Tool errors
In addition to returning status and error codes, API may return error messages for underlying tools, describing an issue with specific tools. This can be caused by many reasons, from the tool simply not supporting the requested token pair or insufficient liquidity.
To better explain failure cases, we try to return errors in a predictable format. The `ToolError` interface looks like the following:
```TypeScript theme={"system"}
type ToolErrorType = 'NO_QUOTE'
interface ToolError {
errorType: ToolErrorType
code: string
action: Action
tool: string
message: string
}
```
### Possible codes:
`NO_POSSIBLE_ROUTE`: No route was found for this action.
`INSUFFICIENT_LIQUIDITY`: The tool's liquidity is insufficient.
`TOOL_TIMEOUT`: The third-party tool timed out.
`UNKNOWN_ERROR`: An unknown error occurred.
`RPC_ERROR`: There was a problem getting on-chain data. Please try again later.
`AMOUNT_TOO_LOW`:The initial amount is too low to transfer using this tool.
`AMOUNT_TOO_HIGH`: The initial amount is too high to transfer using this tool.
`FEES_HIGHER_THAN_AMOUNT`: The fees are higher than the initial amount — this would result in a negative resulting token.
`DIFFERENT_RECIPIENT_NOT_SUPPORTED`: This tool does not support different recipient addresses.
`TOOL_SPECIFIC_ERROR`: The third-party tool returned an error.
`CANNOT_GUARANTEE_MIN_AMOUNT`: The tool cannot guarantee that the minimum amount will be met.
### Example response:
```TypeScript theme={"system"}
{
"message": "Unable to find a quote for the requested transfer.",
"errors": [
{
"errorType": "NO_QUOTE",
"code": "INSUFFICIENT_LIQUIDITY",
"action": {
"fromChainId": 100,
"toChainId": 100,
"fromToken": {
"address": "0x4ecaba5870353805a9f068101a40e0f32ed605c6",
"decimals": 6,
"symbol": "USDT",
"chainId": 100,
"coinKey": "USDT",
"name": "USDT",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"priceUSD": "0.99872"
},
"toToken": {
"address": "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83",
"decimals": 6,
"symbol": "USDC",
"chainId": 100,
"coinKey": "USDC",
"name": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
},
"fromAmount": "1",
"slippage": 0.03,
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
},
"tool": "1inch",
"message": "The tool's liquidity is insufficient."
},
{
"errorType": "NO_QUOTE",
"code": "TOOL_TIMEOUT",
"action": {
"fromChainId": 100,
"toChainId": 100,
"fromToken": {
"address": "0x4ecaba5870353805a9f068101a40e0f32ed605c6",
"decimals": 6,
"symbol": "USDT",
"chainId": 100,
"coinKey": "USDT",
"name": "USDT",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"priceUSD": "0.99872"
},
"toToken": {
"address": "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83",
"decimals": 6,
"symbol": "USDC",
"chainId": 100,
"coinKey": "USDC",
"name": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
},
"fromAmount": "1",
"slippage": 0.03,
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
},
"tool": "openocean",
"message": "The third party tool timed out."
},
{
"errorType": "NO_QUOTE",
"code": "NO_POSSIBLE_ROUTE",
"action": {
"fromChainId": 100,
"toChainId": 100,
"fromToken": {
"address": "0x4ecaba5870353805a9f068101a40e0f32ed605c6",
"decimals": 6,
"symbol": "USDT",
"chainId": 100,
"coinKey": "USDT",
"name": "USDT",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"priceUSD": "0.99872"
},
"toToken": {
"address": "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83",
"decimals": 6,
"symbol": "USDC",
"chainId": 100,
"coinKey": "USDC",
"name": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
},
"fromAmount": "1",
"slippage": 0.03,
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
},
"tool": "superfluid",
"message": "No route was found for this action."
}
]
}
```
# Fetch all known tokens
Source: https://docs.li.fi/api-reference/fetch-all-known-tokens
get /v1/tokens
Retrieve LI.FI’s catalog of supported tokens, optionally filtered by chain or tag.
Use this endpoint to list tokens that LI.FI currently supports. Combine filters to narrow the response to specific chains, token tags, or both.
## Filter tokens
### Chain filter
Set the `chains` query parameter to a comma-separated list of chain identifiers (for example, `ETH,ARB,OP`). Only tokens live on those networks are returned.
### Tag filter
Use the `tags` query parameter to restrict results to tokens with specific LI.FI tags. Currently `stablecoin` is the only public tag.
Tags and chains can be combined. The response includes only tokens that satisfy all provided filters.
## Examples
### Fetch the complete token catalog
```bash theme={"system"}
curl "https://li.quest/v1/tokens"
```
### Fetch all stablecoins
```bash theme={"system"}
curl "https://li.quest/v1/tokens?tags=stablecoin"
```
### Fetch Arbitrum stablecoins
```bash theme={"system"}
curl "https://li.quest/v1/tokens?chains=ARB&tags=stablecoin"
```
More tags may be introduced over time as we add support for more token types.
# Fetch information about a Token
Source: https://docs.li.fi/api-reference/fetch-information-about-a-token
get /v1/token
This endpoint can be used to get more information about a token by its address or symbol and its chain.
If you want to learn more about how to use this endpoint please have a look at our [guide](/api-reference/fetch-information-about-a-token).
# Get gas price for the specified chainId
Source: https://docs.li.fi/api-reference/gas/get-gas-price-for-the-specified-chainid
get /v1/gas/prices/{chainId}
This endpoint can be used to get the most recent gas prices for the supplied chainId.
# Get gas prices for enabled chains
Source: https://docs.li.fi/api-reference/gas/get-gas-prices-for-enabled-chains
get /v1/gas/prices
This endpoint can be used to get the most recent gas prices for the enabled chains in the server.
# Get a gas suggestion for the specified chain
Source: https://docs.li.fi/api-reference/get-a-gas-suggestion-for-the-specified-chain
get /v1/gas/suggestion/{chain}
Endpoint to retrieve a suggestion on how much gas is needed on the requested chain. The suggestion is based on the average price of 10 approvals and 10 uniswap based swaps via LI.FI on the specified chain.
If `fromChain` and `fromToken` are specified, the result will contain information about how much `fromToken` amount the user has to send to receive the suggested gas amount on the requested chain.
# Get a list of filtered transfers
Source: https://docs.li.fi/api-reference/get-a-list-of-filtered-transfers
get /v1/analytics/transfers
This endpoint can be used to retrieve a list of transfers filtered by certain properties. Returns a maximum of 1000 transfers.
# Get a paginated list of filtered transfers
Source: https://docs.li.fi/api-reference/get-a-paginated-list-of-filtered-transfers
get /v2/analytics/transfers
A paginated version of the `GET /v1/analytics/transfers endpoint`. This endpoint can be used to retrieve a list of transfers filtered by certain properties.
# Get a quote for a token transfer
Source: https://docs.li.fi/api-reference/get-a-quote-for-a-token-transfer
get /v1/quote
This endpoint can be used to request a quote for a transfer of one token to another, cross chain or not.
The endpoint returns a `Step` object which contains information about the estimated result as well as a `transactionRequest` which can directly be sent to your wallet.
The estimated result can be found inside the `estimate`, containing the estimated `toAmount` of the requested `Token` and the `toAmountMin`, which is the guaranteed minimum value that the transfer will yield including slippage.
If you want to learn more about how to use this endpoint please have a look at our [guide](/introduction/user-flows-and-examples/requesting-route-fetching-quote).
For preset options, use the `preset` query parameter. See the [API presets overview](/api-reference/presets/overview). The [stablecoin preset](/api-reference/presets/stablecoin) is ideal for stablecoin transfers and can be customized. To see supported stablecoins, use `/v1/tokens?tags=stablecoin`.
# Get a quote for a token transfer
Source: https://docs.li.fi/api-reference/get-a-quote-for-a-token-transfer-1
get /v1/quote/toAmount
This endpoint is an alternative to the `v1/quote` endpoint, taking a `toAmount` value rather than `fromAmount`. This endpoint will calculate an appropriate `fromAmount` based on the specified `toAmount`, and use this value to generate the quote data.
This endpoint can be used to request a quote for a transfer of one token to another, cross chain or not.
The endpoint returns a `Step` object which contains information about the estimated result as well as a `transactionRequest` which can directly be sent to your wallet.
The estimated result can be found inside the `estimate`, containing the estimated required `fromAmount` of the sending `Token` to meet the `toAmountMin` of the receiving token, which is the guaranteed minimum value that the transfer will yield including slippage.
If you want to learn more about how to use this endpoint please have a look at our [guide](/introduction/user-flows-and-examples/requesting-route-fetching-quote).
# Get available bridges and exchanges
Source: https://docs.li.fi/api-reference/get-available-bridges-and-exchanges
get /v1/tools
This endpoint can be used to get information about the bridges and exchanges available trough our service
# Get information about all currently supported chains
Source: https://docs.li.fi/api-reference/get-information-about-all-currently-supported-chains
openapi.yaml GET /v1/chains
If you want to learn more about how to use this endpoint please have a look at our [guide](/sdk/chains-tools).
# Get integrator's collected fees data for all supported chains
Source: https://docs.li.fi/api-reference/get-integrators-collected-fees-data-for-all-supported-chains
get /v1/integrators/{integratorId}
This endpoint can be used to request all integrator's collected fees data by tokens for all supported chains.
The endpoint returns an `Integrator` object which contains the integrator id and an array of fee balances for all supported chains.
# Get status information about a lifuel transaction
Source: https://docs.li.fi/api-reference/get-status-information-about-a-lifuel-transaction
get /v1/gas/status
# Get the total amount of a token received on a specific chain, for cross-chain transfers.
Source: https://docs.li.fi/api-reference/get-the-total-amount-of-a-token-received-on-a-specific-chain-for-cross-chain-transfers
get /v1/analytics/transfers/summary
Calculates and returns the total received token amount per wallet address, per sending chain, within a specified time range, for a given receiving chain and receiving token. Only aggregates cross-chain transfers, meaning transfers with distinct sending and receiving chains.
# Get transaction request for withdrawing collected integrator's fees by chain
Source: https://docs.li.fi/api-reference/get-transaction-request-for-withdrawing-collected-integrators-fees-by-chain
get /v1/integrators/{integratorId}/withdraw/{chainId}
This endpoint can be used to get transaction request for withdrawing integrator's collected fees the specified chain. If a list of token addresses is provided, the generated transaction will only withdraw the specified funds.
If there is no collected fees for the provided token's addresses, the `400` error will be thrown.
The endpoint returns a `IntegratorWithdrawalTransactionResponse` object which contains the transaction request.
# In case a transaction was missed by a relayer, this endpoint can be used to force a tx to be re-fetched.
Source: https://docs.li.fi/api-reference/in-case-a-transaction-was-missed-by-a-relayer-this-endpoint-can-be-used-to-force-a-tx-to-be-re-fetched
get /v1/gas/refetch
# Overview
Source: https://docs.li.fi/api-reference/introduction
Fundamentals of LI.FI`s API.
**Building an AI agent?** Start with our [Agent Integration Guide](/agents/overview) for a streamlined overview of the essential endpoints, or use [llms.txt](/llms.txt) for machine-readable documentation.
## Base URL
LI.FI’s API is built on REST principles and is served over HTTPS.
The Base URL for all API endpoints is:
```javascript theme={"system"}
https://li.quest/v1
```
## Authentication
All LI.FI APIs do not require API key. API key is only needed for higher rate limits
Authentication to LI.FI's API is performed via the custom HTTP header `x-lifi-api-key` with an API key. If you are using the Client SDK, you will set the API when constructing a client, and then the SDK will send the header on your behalf with every request. If integrating directly with the API, you’ll need to send this header yourself like so:
```curl theme={"system"}
curl --location 'https://li.quest/v1/quote?fromChain=100&fromAmount=1000000&fromToken=0x4ecaba5870353805a9f068101a40e0f32ed605c6&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&toChain=137&toToken=0x2791bca1f2de4661ed88a30c99a7a9449aa84174&slippage=0.03' \
--header 'x-lifi-api-key: YOUR_CUSTOM_KEY'
```
API key can be tested using the following endpoint:
```javascript theme={"system"}
curl --location 'https://li.quest/v1/keys/test'
--header 'x-lifi-api-key: YOUR_CUSTOM_KEY'
```
Never expose your `x-lifi-api-key` in client-side environments such as browser-based JavaScript or direct Widget integrations. Using the API key on the client side can lead to unauthorized usage or abuse of your key, as it becomes publicly accessible in the browser's developer tools or network tab.
If you're using the LI.FI Widget, you **do not need to pass an API key**. The Widget operates securely without requiring a key in the frontend. For server-side integrations (e.g. SDK or API requests from your backend), always keep your key secret and secure.
## Rate Limit
Rate limit is counted per IP without API key and per API Key with authenticated requests.
Please refer to [Rate limits and API authentication](/api-reference/rate-limits) page.
## Error Message
Errors consist of three parts:
1. HTTP error code
2. LI.FI error code
3. Error message
Specific error codes and messages are defined on [Error Codes](/api-reference/error-codes) page
**Looking for one-click DeFi operations?** Use [Composer](/composer/overview) to deposit into vaults, stake, and lend — all through the same `/quote` endpoint. Set `toToken` to a vault token address and Composer handles the rest. See [Composer API Parameters](/composer/reference/api-parameters).
# OpenAPI Specification
Source: https://docs.li.fi/api-reference/openapi-spec
Download or reference the LI.FI API OpenAPI specification for integration with AI agents, SDKs, and tools
## LI.FI OpenAPI Specification
The LI.FI API follows the OpenAPI 3.0 specification. You can use this spec to:
* Generate client SDKs in any language
* Import into API testing tools (Postman, Insomnia, etc.)
* Integrate with AI agents and LLM tools
* Build automated documentation
## Access the spec
The full specification is available at the URLs below. Use whichever format suits your workflow.
Raw YAML hosted on this site
Raw YAML from the GitHub repository
| Format | URL |
| ------------- | --------------------------------------------------------------------------- |
| YAML (docs) | `https://docs.li.fi/openapi.yaml` |
| YAML (GitHub) | `https://raw.githubusercontent.com/lifinance/public-docs/main/openapi.yaml` |
## Specification details
* **OpenAPI Version**: 3.0.2
* **API Version**: 1.0.0
* **Endpoints**: 28+ paths covering quotes, routes, tokens, chains, and more
## API base URLs
| Environment | Base URL |
| ----------- | -------------------------- |
| Production | `https://li.quest` |
| Staging | `https://staging.li.quest` |
## For AI agents
AI agents can discover and consume this API through multiple formats:
| Resource | URL | Description |
| ------------ | ----------------------------------------------- | --------------------------------- |
| OpenAPI Spec | `https://docs.li.fi/openapi.yaml` | Full OpenAPI 3.0.2 specification |
| AI Plugin | `https://docs.li.fi/.well-known/ai-plugin.json` | Standard discovery format |
| llms.txt | `https://docs.li.fi/llms.txt` | Structured documentation for LLMs |
| MCP Server | `https://mcp.li.quest/mcp` | Model Context Protocol server |
| Agent Guide | [/agents/overview](/agents/overview) | Integration guide for AI agents |
## Quick links
* [API Introduction](/api-reference/introduction) - Get started with the LI.FI API
* [Rate Limits](/api-reference/rate-limits) - API keys and rate limits
* [Get a Quote](/api-reference/get-a-quote-for-a-token-transfer) - Request a cross-chain swap quote
# Parse transaction call data (BETA)
Source: https://docs.li.fi/api-reference/parse-transaction-call-data-beta
get /v1/calldata/parse
This endpoint allows to pass transaction call data. It will then parse the call data based on known and on-chain ABIs to provide a JSON overview of the internal transaction information.
# Perform multiple contract calls across blockchains (BETA)
Source: https://docs.li.fi/api-reference/perform-multiple-contract-calls-across-blockchains-beta
post /v1/quote/contractCalls
This endpoint can be used to bridge tokens, swap them and perform a number or arbitrary contract calls on the destination chain. You can find an example of it [here](https://github.com/lifinance/sdk/tree/main/examples).
This functionality is currently in beta. While we've worked hard to ensure its stability and functionality, there might still be some rough edges.
# API Presets
Source: https://docs.li.fi/api-reference/presets/overview
Preconfigured routing defaults for LI.FI quote and advanced route endpoints.
API presets provide tuned routing defaults for the LI.FI API. When you pass a preset, LI.FI loads the preset’s defaults and merges any request-specific options on top. Your explicit values always win, so you can start with a preset and selectively override the pieces you care about.
## Supported endpoints
* [`POST /v1/advanced/routes`](/api-reference/advanced/get-a-set-of-routes-for-a-request-that-describes-a-transfer-of-tokens)
* [`GET /v1/quote`](/api-reference/get-a-quote-for-a-token-transfer)
## How presets are applied
1. LI.FI loads the preset defaults.
2. LI.FI merges your request options on top of those defaults.
3. Any conflicting keys fall back to the values you provided in the request body or query string.
This keeps requests concise while preserving full control.
## Processing flow
1. Validate that the requested preset name is present and active.
2. Load the preset configuration (defaults, tool preferences, etc) or use the built-in default if it isn’t available.
3. Merge preset defaults into the request payload or query parameters.
4. Apply user-provided overrides so explicit values win.
5. Log the resolved preset for observability and troubleshooting.
If validation fails, for example because the preset name does not match the required pattern, the API returns HTTP `400`.
## Preset names
Presets can be referenced by:
* **Generic names** such as `stablecoin`
* **Specific names** such as `stablecoin-cheapest`
See all [available presets](/api-reference/presets/overview#available-presets).
A generic name can map to a specific preset. For example, `stablecoin` resolves to `stablecoin-cheapest` by default. LI.FI may promote other presets behind the same generic name when new configurations such as `stablecoin-fastest` or partner-specific variants `stablecoin-partnerX` become available.
## Using the preset parameter
### Advanced Routes
Provide the preset inside the `options` object.
```json theme={"system"}
{
"fromChainId": 1,
"toChainId": 137,
"fromAmount": "1000000000000000000",
"fromTokenAddress": "0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C",
"toTokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"options": {
"preset": "stablecoin"
}
}
```
### Quote
Send the preset as a query parameter.
```
GET /v1/quote?fromChain=1&toChain=137&fromToken=0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C&toToken=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174&fromAddress=0x123...&fromAmount=1000000000000000000&preset=stablecoin
```
## Available presets
* [Stablecoin preset](/api-reference/presets/stablecoin)
* Commerce preset (coming soon)
More presets will be documented as they are released.
# Stablecoin preset
Source: https://docs.li.fi/api-reference/presets/stablecoin
Stablecoin-focused routing defaults for LI.FI quotes and advanced routes.
Use the stablecoin preset when you need LI.FI to favor tight slippage and reliable routing for stablecoin moves. It is built for experiences such as:
* Treasury and treasury-rebalance flows.
* Larger cross-chain stablecoin transfers that demand predictable execution.
* Integrations that require a safe default but still override specific fields when needed.
## Quick start
1. Add `preset=stablecoin` to your quote or advanced routes request.
2. Send the request as usual. LI.FI applies the preset defaults first, then merges any overrides you supply.
### Quote request (GET)
```bash theme={"system"}
curl "https://li.quest/v1/quote?fromChain=1&toChain=137&fromToken=0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C&toToken=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174&fromAddress=0x123...&fromAmount=1000000000000000000&preset=stablecoin"
```
Review the full query parameter schema in the [`GET /v1/quote` reference](/api-reference/get-a-quote-for-a-token-transfer).
### Advanced routes (POST)
```bash theme={"system"}
curl -X POST "https://li.quest/v1/advanced/routes" \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 1,
"toChainId": 137,
"fromAmount": "1000000000000000000",
"fromTokenAddress": "0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C",
"toTokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"options": {
"preset": "stablecoin"
}
}'
```
See the complete request body structure in the [`POST /v1/advanced/routes` reference](/api-reference/advanced/get-a-set-of-routes-for-a-request-that-describes-a-transfer-of-tokens).
### JavaScript/TypeScript
```ts theme={"system"}
const quoteRequest = {
fromChain: 1,
toChain: 137,
fromToken: "0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C",
toToken: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
fromAddress: "0x123...",
fromAmount: "1000000000000000000",
preset: "stablecoin"
};
```
## Preset defaults
The stablecoin preset configures these defaults to protect execution quality:
* order: `CHEAPEST`
* slippage: `0.001` (0.1%)
* Price impact capped at 2%
* Shortlist of bridges that are prime for stablecoin transfers
* All exchanges enabled by default
If your request includes conflicting values, your request wins. For instance, when you supply `denyBridges` or similar list overrides, the preset’s defaults are replaced by the values you send. Include all bridges you want denied (existing and new) in the request payload.
This means you can override the preset defaults with your own values as shown below.
## Overrides example
```ts theme={"system"}
const routesRequestWithOverrides = {
fromChainId: 1,
toChainId: 137,
fromAmount: "1000000000000000000",
fromTokenAddress: "0xA0b86a33E6441b8C4C8C0C4C0C4C0C4C0C4C0C4C",
toTokenAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
options: {
preset: "stablecoin",
slippage: 0.005, // loosen to 0.5% instead of the default 0.1%
order: "FASTEST" // override the default CHEAPEST order
}
};
```
Integrators are advised to use the stablecoin preset as is and only override its values if absolutely necessary, since the default options are carefully optimized for stablecoin transfers.
To see all available options you can override, check the [options schema](https://github.com/lifinance/types/blob/main/src/api.ts#L116).
## Tool preferences
For bridging, the preset prioritizes stablecoin-friendly paths such as:
* Mint-and-burn flows like Glacis, Mayan Swift, Mayan MCTP, and Celer
* Intent and solver-based options like Eco, Relay, Across, and Gaszip
* Additional tools as they are vetted and added over time
## Check supported stablecoins
Stablecoin support is driven by LI.FI’s token tagging. Use [`/v1/tokens`](/api-reference/fetch-all-known-tokens) with the `tags` filter to confirm which assets qualify.
### All stablecoins
```bash theme={"system"}
curl "https://li.quest/v1/tokens?tags=stablecoin"
```
### Stablecoins on a specific chain
```bash theme={"system"}
curl "https://li.quest/v1/tokens?chains=ARB&tags=stablecoin"
```
Combine the `tags` filter with `chains` filters to match your integration. Need the full parameter list? Check the [`GET /v1/tokens` reference](/api-reference/fetch-all-known-tokens).
## Related docs
* [API presets overview](/api-reference/presets/overview)
* [`GET /v1/quote`](/api-reference/get-a-quote-for-a-token-transfer)
* [`POST /v1/advanced/routes`](/api-reference/advanced/get-a-set-of-routes-for-a-request-that-describes-a-transfer-of-tokens)
# Rate Limits and API Authentication
Source: https://docs.li.fi/api-reference/rate-limits
To mitigate misuse and manage capacity on our API, we have implemented limits on LI.FI API usage.
Rate limits apply to requests made using your `x-lifi-api-key` and are calculated per API key across all endpoints. These limits help prevent abuse and ensure a smooth experience for everyone.
# Current Rate Limits
The default rate limits for production usage are as follows:
### Unauthenticated
| Endpoint | Rate Limit |
| --------------------------- | ------------------------- |
| `/quote` | 75 requests per two hours |
| `/advanced/routes` | 75 requests per two hours |
| `/advanced/stepTransaction` | 50 requests per two hours |
| Other public endpoints | 100 requests per minute |
### Authenticated
API keys created via the [LI.FI Partner Portal](https://li.fi/plans/) currently default to **100 requests per minute**.
Rate limits for quote-related endpoints (`/quote`, `/advanced/routes`, `/advanced/stepTransaction`) are enforced on a **two-hour rolling window**. For example, if your API key limit is 100 RPM, you can make up to 12,000 requests within any two-hour window for those endpoints.
> 🔒 Higher limits may be available for enterprise clients. Please see our [Plans page](https://li.fi/plans/) for more details.
# Handling Rate Limits
Every response includes your current rate limit in the headers. Keep in mind that limits can differ depending on the endpoint.
In the Partner Portal, you’ll see your requests-per-minute (RPM) limit. To give you flexibility during spikes, we don’t enforce it minute by minute. Instead, we multiply your RPM by 120 and apply it as a two-hour rolling window.
👉 Example: If your limit is 100 RPM, that means you can make up to 12,000 requests within any two-hour window — either all at once or spread out however you like.
### Rate Limit Information In Request Response
`ratelimit-reset`: in how many seconds will the rate limit reset (2 hours equal 7200)
`ratelimit-limit`: the total limit for the period of 2 hours
`ratelimit-remaining`: how much of the limit is still left until the reset
Here's how you can calculate your average RPM:
`(ratelimit-limit - ratelimit-remaining) / ((7200 - ratelimit-reset) / 60)`
If you exceed your limits, you'll receive a `429 Too Many Requests` response with error code `1005` (`RateLimitError`). When this occurs:
* The response will include details on when the rate limit resets
* Consider requesting a higher rate limit via the [Partner Portal](https://li.fi/plans/)
# Best Practices
To avoid hitting rate limits:
* Cache results from `GET /tokens`, `GET /chains`, and static endpoints
* Avoid polling frequently for the same data
* Batch or debounce user input that triggers API calls
# Abuse Prevention
To prevent abuse, LI.FI may temporarily block keys that:
* Consistently exceed rate limits
* Attempt to bypass limits through multiple keys or IPs
* Cause performance degradation to the service
# Using the API key
All LI.FI APIs do not require API key. API key is only needed for higher rate limits
Authentication to LI.FI's API is performed via the custom HTTP header `x-lifi-api-key` with an API key. If you are using the Client SDK, you will set the API when [creating a config](/sdk/configure-sdk), and then the SDK will send the header on your behalf with every request. If integrating directly with the API, you’ll need to send this header yourself like so:
```curl theme={"system"}
curl --location 'https://li.quest/v1/quote?fromChain=100&fromAmount=1000000&fromToken=0x4ecaba5870353805a9f068101a40e0f32ed605c6&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&toChain=137&toToken=0x2791bca1f2de4661ed88a30c99a7a9449aa84174&slippage=0.03' \
--header 'x-lifi-api-key: YOUR_CUSTOM_KEY'
```
API key can be tested using the following endpoint:
```javascript theme={"system"}
curl --location 'https://li.quest/v1/keys/test'
--header 'x-lifi-api-key: YOUR_CUSTOM_KEY'
```
Never expose your `x-lifi-api-key` in client-side environments such as browser-based JavaScript or direct Widget integrations. Using the API key on the client side can lead to unauthorized usage or abuse of your key, as it becomes publicly accessible in the browser's developer tools or network tab.
If you're using the LI.FI Widget, you **do not need to pass an API key**. The Widget operates securely without requiring a key in the frontend. For server-side integrations (e.g. SDK or API requests from your backend), always keep your key secret and secure.
# Need Higher Limits?
If you're building a high-volume integration or a production-grade product, we’re happy to support your scaling needs.
Please see our [Plans page](https://li.fi/plans/) for more details.
# Returns all possible connections between two chains.
Source: https://docs.li.fi/api-reference/returns-all-possible-connections-based-on-a-from-or-tochain
get /v1/connections
This endpoint gives information about all possible transfers between chains.
`fromChain` and `toChain` are required. Additional filters such as token, bridge, and exchange can be used to narrow the result further.
Information about which chains and tokens are supported can be taken from the response of the /v1/chains endpoint.
Information about which bridges and exchanges are supported can be taken from the response of the `/v1/tools` endpoint.
# Core
Source: https://docs.li.fi/changelog/backend
Additions and updates to the core LI.FI service
## New Chains
* **TRON** — full support for TRON transactions and routing
* **Arbitrum Nova** added
* **Base Sepolia** and **Arbitrum Sepolia** testnets added
## New Bridges
* **Mayan v2** integration shipped
* **NEAR Intents** expanded to include Tron
## New DEXs
* **Bitget** DEX added
* **OKX** new contracts deployed
* **Paraswap** API updated
* Removed **Odos** chains to keep routing clean
## Features
* **Hyperliquid builder codes** support
* **Custom Solana priority fees** for faster execution
* **Bitcoin simple transactions** — new flow for straightforward BTC transfers
* **Polymer Standard** limit increased to 10M
* **Transak session** request endpoint added
* Improved precision of integrator fees between quote and transaction generation
* Optional **Composer API key** support
## Earn API
### Breaking Changes
* **API paths simplified** - all Earn endpoints have dropped the `/earn` path segment. Update your base paths:
* `/v1/earn/chains` is now `/v1/chains`
* `/v1/earn/vaults` is now `/v1/vaults`
* `/v1/earn/vaults/{chainId}/{address}` is now `/v1/vaults/{chainId}/{address}`
* `/v1/earn/protocols` is now `/v1/protocols`
* `/v1/earn/portfolio/{userAddress}/positions` is now `/v1/portfolio/{userAddress}/positions`
* **`provider` field removed** from vault objects
* **Portfolio positions** - `protocolName` and `balanceUsd` are now nullable; handle `null` values in your integration
### New Features
* **Three new filters** on `GET /v1/vaults`: `isTransactional`, `isRedeemable`, and `isComposerSupported` let you narrow results to vaults that support specific capabilities
* **`address` field** added to portfolio position objects, returning the vault contract address for each position
* **Structured error responses** for `400` and `404` - validation errors now return a detailed `errors` array with per-field codes and paths
## Reliability
* Fixed status API not working for funds moved in the same block
* Improved Hyperliquid and Relay status parsing
* Improved Solana status error parsing
* `/quote/toAmount` now returns `1011` for same-token requests and caps the adjustment factor to prevent over-quoting
* Normalised x100 percentage values in status API responses
* Fixed inconsistent FeeForwarder address mapping in status step lookup
* Fixed Stargate V2 and Polymer status validation
* Filter token tax updates by GoPlus-supported chains
## Fee Infrastructure
* Upgraded fee handling on supported EVM chains from **FeeCollector** to **FeeForwarder** — fees are now forwarded directly to recipient wallets at execution time, with no manual withdrawal required. Chains without FeeForwarder fall back to the legacy contract automatically. No partner action needed. See [FeeForwarder](/introduction/integrating-lifi/fee-forwarder) for details.
## New Chains
* Added **Fogo (SVM)**, **Tempo**, and **Morph** support
* Enabled **Arc Testnet**, **OP Sepolia**, **Arbitrum Sepolia**, and **Base Sepolia** across Backend, Solver, Wallet, and Order services
## New Bridges
* **Hypercore:** Native Deposits, Native Withdrawals, and Spot Swapping now supported
* **Eco Bridge** enabled on HyperEVM and BSC, with updated Solana program ID
* **Symbiosis** expanded to BSC, SEI, ARB, and Morph
* **Polymer** added fee buffer and simultaneous mainnet/testnet support
* New **Across** swap facet and improved receiver address status parsing
* **Chainflip** updated to SDK v2.1.1
* **Titan** API endpoint updated
* New **LI.FI Intents DEX** implementation
## New DEXs
* **OKX** liquidity source filtering (`excludeDexIds`) and exposed liquidity list endpoint
* **Relay** token whitelisting
* **Houdini Swap** integration (including Solana)
* **OogaBooga** disabled
## Features
* Improved `/tools` endpoint performance
* Strengthened wallet compliance screening with the Hypernative integration
## Reliability
* Improved **Solana RPC** latency and stability
* Corrected **Hyperliquid** transaction status parsing
* Fixed impossible price impact (>100%) when input minus fees is lower than output
* Fixed **LI.FI Intents** status parsing and **LIFI Transfer** token casing inconsistencies
* **Across** temporarily disabled and re-enabled after fixing pending transaction parsing
* **Chainflip** temporarily disabled and fixed via SDK v2.1.1
## New Chains
* Added **Telos** and **Zcash** support
## New Bridges
* Expanded **MegaETH** bridge coverage (GasZip, Garden, native bridge)
* New two-step bridge flow to **Hyperliquid spot**
* Re-enabled the **LI.FI Intents bridge**
## New DEXs
* Added **Fly** on Solana
* New routing path on **Symbiosis**
* Added **Kelp**, **Morpho vaults v2**, and **Neutrl** in Composer
## Features
* Dedicated Hyperliquid endpoints and improved cross-chain status tracking
* Launched For Agents documentation and LI.FI MCP server docs
* Strengthened wallet compliance screening with lower latency
## Reliability
* Improved token pricing accuracy and wallet balance display
* Better fee calculations across stable chains and bridge routes
* Improved gas recommendations for MegaETH
## New Chains
* Added **Viction** support
* Removed inactive chains to keep routing clean
## New Bridges
* **Allbridge** now available on Unichain and Linea
* **Glacis** expanded to Flow
* Continued **MegaETH** rollout with relay and native bridge
## New DEXs
* Added **Nordstern Finance**, **Cetus** on Sui, and **Eisen** on Monad
## Features
* **Smart slippage selection** for major and stable assets
* Revamped **dynamic stablecoin route fees**
* New **tool control options** for integrators to manage protocols and exchanges via API
* Improved **token search** performance
## Intents Stack
* **Wallet Service** and **Order Service** reached v1.0.0
* **Solver v2.1.0** with reliability and rebalancing upgrades
## Composer
* **Composer v0.3.0** with new protocol modules (Ether.fi, HypurrFi, Fluid, Spark, Royco, Cap)
## Reliability
* Improved transaction indexing and RPC reliability
* Better Bitcoin address support (xpub, UTXO handling)
* Fee accuracy and data consistency improvements
## New Chains
* Day-one support for **Stable**
* Added **MegaETH** and **Plume**
## New Bridges
* Added **Garden**, **CelerCircle**, and **CelerCircleFast** bridges
* Updated **Chainflip** integration
* Enabled **Stargate** on Plasma
## New DEXs
* Added **OKX aggregator** on Solana
* Enabled **SushiSwap** on Monad
* Expanded Monad DEX coverage with Fly
## Features
* **API Config Presets** for optimized stablecoin routes
* **Token tagging** for stablecoins to improve routing
* Improved quote accuracy with better caching and validation
## Reliability
* Improved platform health monitoring
* Better token pricing accuracy and update frequency
* General infrastructure stability improvements
## New Chains
* Day-one support for **Monad**
## New Bridges
* Enabled **Across** and **GasZip** on Monad
* Upgraded **Glacis** contracts
* Added **Unit** withdrawals
## New DEXs
* Enabled **Magpie Fly**, **Kuru**, and **Monorail** aggregators
## Features
* Faster route sorting and display
* **OpenOcean** expanded to ETH, BSC, Scroll, and Monad
## Reliability
* API performance improvements across analytics endpoints
* Improved token data quality on Sui and Solana
* General platform stability improvements
## Milestones
* Reached **\$50B lifetime volume**
* Expanded to **24/7 technical support** coverage
## Ecosystem
* **Chains:** Flow, Hemi added; Hypercore available via API
* **Bridges:** Across destination swaps on HyperEVM; Solana support for Across; Hypercore to HyperEVM transfers; Pioneer Bridge; Eco Bridge USDT on Celo
* **DEX Aggregators:** GlueX, Magpie, Hyperflow Corewriter; Titan and OKX on Solana; Momentum on Sui; Plasma chain DEXs
* **Protocols:** Perena support added
## Features
* Mayan bridge split into three providers: `mayan`, `mayanWH`, `mayanMCTP`
* Route quality improvements and positive slippage collection
* Allow `/status` to work with `transactionId`
## Bug Fixes
* Fixed multiple Solana transaction errors (insufficient lamports, insufficient funds, transaction size limits)
* Fixed wSOL display in status responses
* Fixed gas amount USD values in contract calls
* Improved gas estimation and status parsing accuracy
# Monthly Updates
Source: https://docs.li.fi/changelog/monthly-updates
Monthly changelog of new chains, bridges, DEXs, features, and improvements shipped across the LI.FI platform.
## April 2026
### New Chains
* **TRON:** Full support shipped and live on the LI.FI API, including on Jumper.
* **Arbitrum Nova:** Now supported.
* **Base Sepolia** and **Arbitrum Sepolia:** Testnets enabled.
### New Bridges
* **Mayan v2:** New bridge version integrated.
* **NEAR Intents:** Expanded to include Tron routing.
### New DEXs and DEX Aggregators
* **Bitget:** New DEX added.
* **OKX:** New contracts deployed.
* **Paraswap:** API updated.
* **Odos:** Chains removed to keep routing clean.
### Earn API
* **Breaking:** All Earn endpoints dropped the `/earn` path segment. For example, `/v1/earn/vaults` is now `/v1/vaults`. Update base paths in your integration.
* **Breaking:** Removed the `provider` field from vault objects.
* **Breaking:** `protocolName` and `balanceUsd` on portfolio positions are now nullable — handle `null` values.
* Added three new filters on `GET /v1/vaults`: `isTransactional`, `isRedeemable`, and `isComposerSupported`.
* Added `address` field to portfolio position objects, returning the vault contract address for each position.
* Structured error responses for `400` and `404` — validation errors now return a detailed `errors` array with per-field codes and paths.
### Features and Improvements
* **Widget and SDK v4:** New major versions released.
* **Hyperliquid builder codes** support added.
* **Custom Solana priority fees** for faster execution.
* **Bitcoin simple transactions:** New flow for straightforward BTC transfers.
* **Polymer Standard** limit increased to 10M.
* **Transak** session request endpoint added.
* **Partner Portal:** Shipped a banner and modal notifying users of the FeeCollector → FeeForwarder transition.
* **Solver:** Ethereum routes enabled for USDC, WETH, and ETH.
* **Jumper:** Learn page search, reading progress bar, and table of contents; multi-address portfolio queries; Solana positions; improved wSOL support; TRON is live on Jumper.
* **Composer:** Optional API key support added.
### Reliability
* Fixed status API not working for funds moved in the same block.
* Improved Hyperliquid, Relay, and Solana status parsing.
* `/quote/toAmount` now returns `1011` for same-token requests and caps the adjustment factor to prevent over-quoting.
* Normalised percentage values in status API responses.
* Fixed Stargate V2 and Polymer status validation.
* Improved integrator fee precision between quote and transaction generation.
***
## March 2026
### Fee Infrastructure
* **FeeForwarder:** LI.FI has upgraded fee handling on supported EVM chains. Fees are now forwarded directly to recipient wallets at execution time, with no manual withdrawal required. Chains without FeeForwarder deployed continue to use the legacy FeeCollector contract. No changes to API responses or partner integrations.
### New Chains
* **Fogo (SVM):** New Solana-based chain added.
* **Tempo:** Added as a supported chain.
* **Morph:** Enabled with FlyTrade and Symbiosis routing.
* **Arc Testnet, OP Sepolia, Arbitrum Sepolia, Base Sepolia:** Testnet support added across Backend, Solver, Wallet, and Order services, with Polymer enabled as the initial bridge for Arc Testnet ↔ OP Sepolia.
### New Bridges
* **Hypercore:** Native Deposits, Native Withdrawals, and Spot Swapping now supported.
* **Eco Bridge:** Enabled on HyperEVM and BSC, with updated Solana program ID.
* **Symbiosis:** Expanded to BSC, SEI, and ARB, plus Morph support.
* **Polymer:** Added fee buffer and simultaneous mainnet/testnet support.
* **Across:** New swap facet implementation and improved receiver address status parsing.
* **Chainflip:** Updated to SDK v2.1.1 with pending transaction fix.
* **Titan:** API endpoint updated with new simulation argument.
* **LI.FI Intents:** New DEX implementation for intent-based routing.
### New DEXs and DEX Aggregators
* **OKX:** Added liquidity source filtering (`excludeDexIds`) and exposed a liquidity list endpoint.
* **Relay:** Token whitelisting added.
* **Houdini Swap:** New integration on Jumper, including Solana support.
* **OogaBooga:** Disabled.
### Features and Improvements
* **Composer:** New protocols added including Seamless staking, Avant zaps, Auto Finance (Tokemak), YO Protocol, Yearn, and Upshift. Added referrer support for Ether.fi, Veda, and Spark, plus Linea slot-finder support.
* **Intents Stack:** Reliable solver registration, origin/destination chain filters on `GET /orders`, strict solver-address enforcement, and configurable `maxPriceImpact` via environment for the Ledger service.
* **Wallet Service:** Added RPC failover and fallback across all RPC calls for more reliable transaction submission.
* **Partner Portal:** Token-level fee withdrawal (PAR-300), company integration limit overrides, and multicompany invite improvements.
* **Partner Service:** New fees resolve endpoint and company-level integration limit overrides.
* **Gasless Service:** Migrated to the new Gelato SDK.
* **Jumper:** Sitemap indexing improvements, theme flickering fixes, SVM + EVM wallet support on Earn, and Houdini Swap integration (including Solana).
* Improved `/tools` endpoint performance and Codex pricing.
### Reliability
* Improved Solana RPC call latency and stability.
* Hyperliquid transaction status parsing corrected; completed txs no longer marked as pending.
* Fixed impossible price impact (>100%) when input minus fees is lower than output.
* Fixed LI.FI Intents status parsing and LIFI Transfer token casing inconsistencies.
* Across temporarily disabled and re-enabled after fixing pending transaction parsing.
* Chainflip temporarily disabled and fixed via SDK v2.1.1.
***
## February 2026
### New Chains
* **Telos:** Now supported, with routing and liquidity rolling out across products.
* **Zcash:** Now supported with reliable token pricing.
### New Bridges
* **MegaETH:** Expanded bridge coverage with GasZip, Garden, and native bridge support.
* **Hyperliquid spot:** New two-step bridge flow for cleaner execution and tracking.
* **Intents bridge:** Re-enabled the LI.FI Intents bridge.
### New DEXs and DEX Aggregators
* **Fly on Solana:** More Solana liquidity now routed through LI.FI.
* **Symbiosis:** New routing path enabled for improved options.
* **Composer:** Added Kelp, Morpho vaults v2, and Neutrl protocol support.
### Features and Improvements
* Dedicated Hyperliquid endpoints and improved cross-chain status tracking.
* Launched the **For Agents** documentation tab for AI agents and crawlers.
* Added LI.FI MCP server docs, OpenAPI surfacing through llms.txt, and AI-friendly redirects.
* New guides for debugging, API presets, intermediate tokens, smart slippage, and Bitcoin formats.
* Strengthened wallet compliance screening with lower latency.
* Partner Portal and Partner Service updates with improved analytics and configurable rate limits.
### Reliability
* Improved token pricing accuracy and wallet balance display.
* Better fee calculations across stable chains and bridge routes.
* Improved gas recommendations for MegaETH.
* General performance and stability improvements across the platform.
***
## January 2026
### New Chains
* **Viction:** Now supported.
* Removed inactive chains to keep routing clean.
### New Bridges
* **Allbridge:** Now available on Unichain and Linea.
* **Glacis:** Expanded to support Flow.
* **MegaETH:** Continued rollout with relay and native bridge support.
### New DEXs and DEX Aggregators
* **Nordstern Finance** DEX added.
* **Cetus** DEX added on Sui.
* **Eisen** added on Monad.
### Features
* **Smart slippage selection** for major and stable assets.
* **Dynamic stablecoin route fees** revamped for better pricing.
* New **tool control options** for integrators to manage allowed protocols and exchanges via the API.
* Improved **token search** performance.
### Intents Stack
* **Wallet Service** and **Order Service** reached their v1.0.0 milestones with improved performance and reliability.
* **Solver v2.1.0** shipped with reliability and rebalancing upgrades.
### Composer
* **Composer v0.3.0:** Expanded protocol support with new modules including Ether.fi staking, HypurrFi, Fluid, Spark, Royco, and Cap.
### Reliability
* Improved transaction indexing resilience and RPC reliability across chains.
* Better Bitcoin address support including xpub and improved UTXO handling.
* General fee accuracy and data consistency improvements.
***
## December 2025
### New Chains
* **Stable:** Day-one support shipped and enabled on production.
* **MegaETH:** Now supported.
* **Plume:** Now supported.
### New Bridges
* **Garden bridge** added.
* **CelerCircle** and **CelerCircleFast** bridges added.
* **Chainflip** integration updated.
* **Stargate** enabled on Plasma.
### New DEXs and DEX Aggregators
* **OKX aggregator** added on Solana.
* **SushiSwap** enabled on Monad.
* Expanded Monad DEX coverage with Fly support.
### Features
* **API Config Presets** for optimized stablecoin routes and quotes.
* **Token tagging** for stablecoins to improve routing accuracy.
* Improved quote accuracy with better caching and validation.
### Reliability
* Improved platform health monitoring and alerting.
* Better token pricing accuracy and update frequency.
* General infrastructure stability improvements.
***
## November 2025
### New Chains
* **Monad:** Day-one support shipped with rapid follow-up improvements after launch.
### New Bridges
* **Across on Monad** enabled with reliability improvements.
* **GasZip on Monad** enabled including inbound support.
* **Glacis** contracts upgraded and re-enabled.
* **Unit** withdrawals added.
### New DEXs and DEX Aggregators
* **Magpie Fly** enabled.
* **Kuru** aggregator added.
* **Monorail** aggregator added.
### Features
* Faster route sorting and display for quicker results.
* **OpenOcean** expanded to ETH, BSC, Scroll, and Monad.
* SDK improvements: Permit2 refactor and fixes for multi-step estimation, slippage handling, and approvals.
### Reliability
* API performance improvements across analytics and transfer endpoints.
* Improved token data quality on Sui and Solana.
* General platform stability improvements.
***
## October 2025
### Milestones
* Reached **\$50B lifetime volume**.
* Technical support expanded to **24/7 coverage**.
### New Chains
* **Flow** and **Hemi** added.
* **Hypercore** infrastructure now available via API.
### New Bridges
* Across destination swaps on HyperEVM.
* Solana support for Across.
* Hypercore to HyperEVM transfers.
* Pioneer Bridge enabled.
* Eco Bridge USDT on Celo.
### New DEXs and DEX Aggregators
* GlueX, Magpie, Hyperflow Corewriter on EVM.
* Titan, OKX on Solana.
* Momentum on Sui.
* Plasma chain DEXs enabled.
### Features
* **Perena** protocol support added.
* Significant **Widget bundle size reduction** and new "All Networks" tab.
* SDK now supports **all Bitcoin address types** including nested SegWit.
* Route quality improvements and positive slippage collection.
* Mayan bridge split into three distinct providers for clearer routing: `mayan`, `mayanWH`, `mayanMCTP`.
* Partner Portal now supports **multi-user accounts**.
### Bug Fixes
* Fixed multiple Solana transaction errors (insufficient lamports, insufficient funds, transaction size limits).
* Fixed wSOL display in status responses.
* Fixed gas amount USD values in contract calls.
* Improved gas estimation and status parsing accuracy.
# Partner Portal
Source: https://docs.li.fi/changelog/partner-portal
Additions and updates to the LI.FI Partner Portal
## Partner Portal
* **FeeForwarder migration notice:** Added an in-portal banner and modal notifying users of the transition from FeeCollector to FeeForwarder for fee handling
* Security dependency updates
## Partner Portal
* **Token-level fee withdrawal** (PAR-300) — withdraw fees per token rather than per wallet
* **Company integration limit overrides** — support for per-company integration caps
* Multicompany invite flow improvements
* Fixed add-wallet dialog crash when adding the last default wallet
* Fixed error page content overflow and non-verified JWT decoding in analytics
## Partner Service
* New **fees resolve endpoint** with enhanced response schema
* **OpenAPI documentation** published for the Partner Service
* **Company update endpoint** for max integration limit override
* Configurable default API key rate limit
## Improvements
* Updated analytics and improved Partner Portal experience
* Configurable API key rate limits for integrators
## Improvements
* Partner Portal onboarding improvements and performance fixes
## Improvements
* Partner Portal quality-of-life improvements
## Improvements
* Improved Partner Portal onboarding experience
## New Features
* **Multi-user accounts** in Partner Portal
# SDK
Source: https://docs.li.fi/changelog/sdk
Additions and updates to the LI.FI SDK
## Major Release
* **v4 released** — new major version of the SDK, now adopted by Jumper
## Improvements
* Improved error handling and reliability in the Intents stack
* Better fee handling and fallback logic for solver-based routes
## Features
* **Smart slippage selection** for major and stable assets
* Improved Bitcoin address support including xpub and better UTXO handling
## Changes
* Updated Chainflip integration
## Features
* **Permit2 refactor** for improved token approval flows
* Fixes for multi-step route estimation, slippage handling, and approvals
## Features
* Support for **all Bitcoin address types** including nested SegWit
* Updated **OKX v6** integration
# Widget
Source: https://docs.li.fi/changelog/widget
Additions and updates to the LI.FI widget
## Major Release
* **v4 released** — new major version of the Widget, now adopted by Jumper
## Improvements
* Improved slippage calculation and reporting accuracy
## New Features
* **180 kB bundle size reduction** for faster load times
* **"All Networks" tab** is now live
* **Multi-step route badges** for improved route visibility
* Support for **Porto wallet**
# API Integration
Source: https://docs.li.fi/composer/guides/api-integration
Integrate LI.FI Composer via the REST API with same-chain deposits, cross-chain flows, and error handling.
This guide walks you through integrating Composer directly via the LI.FI REST API. This approach gives you full control over the request/response flow and is ideal for backend services, custom frontends, or any environment where you want to manage transactions yourself.
**Already using the LI.FI SDK or Widget?** Composer works automatically. See the [SDK guide](/composer/guides/sdk-integration) or [Widget guide](/composer/guides/widget-integration) instead.
***
## Authentication
The LI.FI API is open and requires no API key. You can start making requests immediately.
* **`integrator`** (optional query parameter): A string that identifies your application in analytics and on-chain events. Defaults to `"lifi-api"` when omitted. Must be alphanumeric with hyphens, underscores, or dots, max 23 characters.
* **`x-lifi-api-key`** (optional header): An API key is generated automatically when you create an integration in the [LI.FI partner portal](https://portal.li.fi/). Attach it for higher rate limits. Without a key, unauthenticated limits apply: 75 requests per two hours for `/quote` and `/advanced/routes`, 50 requests per two hours for `/advanced/stepTransaction`, and 100 requests per minute for other endpoints. With an API key, the default is 100 requests per minute across all endpoints.
For full details, see [Authentication](/api-reference/authentication).
***
## Overview
Composer does not require a dedicated endpoint. Set `toToken` to a supported protocol token address and LI.FI returns a Composer route from the standard endpoints.
The integration flow:
1. **Request a quote** via `GET /v1/quote` or `POST /v1/advanced/routes`
2. **Set token allowance** by approving the LI.FI Diamond contract to spend your tokens
3. **Send the transaction** using the `transactionRequest` from the quote response
4. **Track status** by polling `GET /v1/status` for cross-chain transfers
***
## Request a Composer Quote
### Using `GET /quote` (Recommended)
The simplest way to get a Composer transaction. Returns a single best route with transaction data included.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000'
```
```ts TypeScript theme={"system"}
import axios from 'axios';
const getQuote = async (params: {
fromChain: number;
toChain: number;
fromToken: string;
toToken: string;
fromAmount: string;
fromAddress: string;
slippage?: number;
integrator?: string;
}) => {
const result = await axios.get('https://li.quest/v1/quote', {
params: {
...params,
toAddress: params.fromAddress,
},
});
return result.data;
};
const quote = await getQuote({
fromChain: 8453, // Base
toChain: 8453, // Base (same-chain)
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token
fromAmount: '1000000', // 1 USDC (6 decimals)
fromAddress: '0xYOUR_WALLET_ADDRESS',
slippage: 0.005, // 0.5% slippage tolerance
integrator: 'your-app-name',
});
```
```python Python theme={"system"}
import requests
response = requests.get('https://li.quest/v1/quote', params={
'fromChain': 8453,
'toChain': 8453,
'fromToken': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
'toToken': '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
'fromAmount': '1000000',
'fromAddress': '0xYOUR_WALLET_ADDRESS',
'toAddress': '0xYOUR_WALLET_ADDRESS',
'slippage': 0.005,
'integrator': 'your-app-name',
})
quote = response.json()
```
### Slippage
The `slippage` parameter is a decimal value representing the maximum acceptable price difference. For example, `0.005` means 0.5%. If omitted, the API defaults to `0.005`. Cross-chain routes involve more steps, so consider `0.01` (1%) or higher for cross-chain flows.
| Value | Meaning |
| ------- | -------------- |
| `0.005` | 0.5% (default) |
| `0.01` | 1% |
| `0.03` | 3% |
### Using `POST /advanced/routes`
Returns multiple route options. Useful when you want to present choices to the user or need more control over route selection.
```ts TypeScript theme={"system"}
const getRoutes = async (params: {
fromChainId: number;
toChainId: number;
fromTokenAddress: string;
toTokenAddress: string;
fromAmount: string;
fromAddress: string;
}) => {
const result = await axios.post('https://li.quest/v1/advanced/routes', {
...params,
toAddress: params.fromAddress,
});
return result.data;
};
const routesResponse = await getRoutes({
fromChainId: 8453,
toChainId: 8453,
fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
toTokenAddress: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
fromAmount: '1000000',
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
// Select the best route
const route = routesResponse.routes[0];
```
When using `/advanced/routes`, transaction data is **not** included in the response. You must call `POST /v1/advanced/stepTransaction` to get the `transactionRequest` for each step. With `/quote`, transaction data is included directly.
#### Getting transaction data for a route step
```ts TypeScript theme={"system"}
const getStepTransaction = async (step: any) => {
const result = await axios.post('https://li.quest/v1/advanced/stepTransaction', step);
return result.data;
};
const route = routesResponse.routes[0];
const stepWithTx = await getStepTransaction(route.steps[0]);
// stepWithTx.transactionRequest now contains the ready-to-sign transaction
```
For a detailed comparison, see [Difference between Quote and Route](/introduction/user-flows-and-examples/difference-between-quote-and-route).
***
## Quote Response Structure
A Composer quote response contains the route details, estimated output, and a ready-to-sign transaction. Here are the key fields:
```json theme={"system"}
{
"id": "0x...",
"type": "lifi",
"tool": "composer",
"toolDetails": {
"key": "composer",
"name": "Composer",
"logoURI": "https://..."
},
"action": {
"fromToken": {
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6,
"chainId": 8453
},
"toToken": {
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"symbol": "sparkUSDC",
"decimals": 18,
"chainId": 8453
},
"fromAmount": "1000000",
"fromChainId": 8453,
"toChainId": 8453,
"slippage": 0.005
},
"estimate": {
"toAmount": "946832715862427",
"toAmountMin": "942098552283115",
"approvalAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"executionDuration": 0,
"feeCosts": [...],
"gasCosts": [...]
},
"integrator": "lifi-api",
"transactionRequest": {
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x...",
"value": "0x0",
"gasLimit": "0x5f45bc",
"gasPrice": "0x1b0875",
"chainId": 8453,
"from": "0xYOUR_WALLET_ADDRESS"
},
"includedSteps": [
{
"type": "protocol",
"tool": "feeCollection",
"toolDetails": { "key": "feeCollection", "name": "Integrator Fee" },
"action": {...},
"estimate": {...}
},
{
"type": "protocol",
"tool": "composer",
"toolDetails": { "key": "composer", "name": "Composer" },
"action": {...},
"estimate": {...}
}
]
}
```
### Key fields
| Field | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `tool` | `"composer"` for same-chain Composer routes. For cross-chain routes, this is the bridge name (e.g., `"stargateV2"`). Check `includedSteps` for `tool: "composer"` to confirm Composer is in the route. |
| `action.fromToken` / `action.toToken` | Token objects with `address`, `symbol`, `decimals`, and `chainId`. |
| `action.slippage` | The slippage tolerance applied to this quote. |
| `estimate.toAmount` | Estimated output in the `toToken`'s smallest unit. |
| `estimate.toAmountMin` | Minimum output accounting for slippage. |
| `estimate.approvalAddress` | The contract address to approve for token spending (see below). |
| `estimate.executionDuration` | Estimated execution time in seconds. |
| `transactionRequest` | Ready-to-sign EVM transaction. Fields (`value`, `gasLimit`, `gasPrice`) are **hex-encoded strings**. |
| `estimate.feeCosts` | Array of fee entries. Every Composer route includes a `feeCollection` step that takes a small percentage fee. Check this array to surface fee details to users. |
| `includedSteps` | Ordered list of steps the route will execute. Typically includes a `feeCollection` step followed by the `composer` step. |
**Hex-encoded values:** The `transactionRequest` fields `value`, `gasLimit`, and `gasPrice` are hex strings (e.g., `"0x5f45bc"`). The `chainId` field is a plain number. When building transactions manually (e.g., in Python), parse hex fields with `int(value, 16)`.
### Contract addresses
The `estimate.approvalAddress` and `transactionRequest.to` both point to the **LI.FI Diamond contract**, a verified, audited smart contract deployed on all supported chains.
| Chain | LI.FI Diamond Address |
| -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| All EVM chains | [`0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE`](https://etherscan.io/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE) |
The Composer onchain VM (which executes the compiled bytecode) is a separate contract called internally by the Diamond. For audit reports and contract verification, see [Security and Audits](/introduction/learn-more/security-and-audits).
Quotes reflect current market conditions and may become stale. If the user reviews a quote for more than 30 seconds, re-fetch before signing to get up-to-date pricing and simulation results.
### Morpho vault naming
Morpho vaults are named after their **curator**, not after Morpho itself. For example, the address `0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A` returns `symbol: "sparkUSDC"` because it is a Spark-curated Morpho vault. This is expected: Morpho provides the vault infrastructure, and curators like Spark create strategies on top.
***
## Set Token Allowance
Before executing, the LI.FI Diamond contract needs approval to spend your tokens. The approval address is returned in the quote response at `estimate.approvalAddress`.
Skip this step if `fromToken` is a native token (e.g., ETH). Native tokens don't require approval.
```ts TypeScript theme={"system"}
import { ethers } from 'ethers';
const ERC20_ABI = [
'function approve(address spender, uint256 amount) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
];
const ensureAllowance = async (
signer: ethers.Signer,
tokenAddress: string,
approvalAddress: string,
amount: string
) => {
if (tokenAddress === ethers.ZeroAddress) return; // Native token
const erc20 = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const owner = await signer.getAddress();
const currentAllowance = await erc20.allowance(owner, approvalAddress);
if (currentAllowance < BigInt(amount)) {
const tx = await erc20.approve(approvalAddress, amount);
await tx.wait();
}
};
await ensureAllowance(
signer,
quote.action.fromToken.address,
quote.estimate.approvalAddress,
quote.action.fromAmount
);
```
```python Python theme={"system"}
from web3 import Web3
ERC20_ABI = [
{
"name": "approve",
"type": "function",
"inputs": [
{"name": "spender", "type": "address"},
{"name": "amount", "type": "uint256"}
],
"outputs": [{"name": "", "type": "bool"}]
},
{
"name": "allowance",
"type": "function",
"inputs": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"}
],
"outputs": [{"name": "", "type": "uint256"}]
}
]
def ensure_allowance(w3, account, token_address, approval_address, amount):
token = w3.eth.contract(address=token_address, abi=ERC20_ABI)
current = token.functions.allowance(account.address, approval_address).call()
if current < int(amount):
tx = token.functions.approve(approval_address, int(amount)).build_transaction({
'from': account.address,
'nonce': w3.eth.get_transaction_count(account.address),
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
ensure_allowance(
w3,
account,
quote['action']['fromToken']['address'],
quote['estimate']['approvalAddress'],
quote['action']['fromAmount']
)
```
If an approval transaction was sent, re-fetch the quote before executing. The `transactionRequest` contains gas estimates that can become stale by the time the approval confirms. Call `GET /v1/quote` again with the same parameters and use the fresh `transactionRequest`.
***
## Send the Transaction
Submit the `transactionRequest` from the quote response. This is a standard EVM transaction.
```ts TypeScript theme={"system"}
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Transaction hash:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
```
```python Python theme={"system"}
tx = {
'to': quote['transactionRequest']['to'],
'data': quote['transactionRequest']['data'],
'value': int(quote['transactionRequest']['value'], 16),
'gas': int(quote['transactionRequest']['gasLimit'], 16),
'gasPrice': int(quote['transactionRequest']['gasPrice'], 16),
'nonce': w3.eth.get_transaction_count(account.address),
'chainId': int(quote['transactionRequest']['chainId']),
}
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Confirmed: {receipt['transactionHash'].to_0x_hex()}")
```
***
## Track Status
For **same-chain** Composer transactions, the operation is complete once the transaction is confirmed.
For **cross-chain** Composer flows, poll the `/status` endpoint until the transfer completes:
```ts TypeScript theme={"system"}
const getStatus = async (txHash: string, fromChain: number, toChain: number) => {
const result = await axios.get('https://li.quest/v1/status', {
params: { txHash, fromChain, toChain },
});
return result.data;
};
const pollStatus = async (txHash: string, fromChain: number, toChain: number) => {
let status;
do {
status = await getStatus(txHash, fromChain, toChain);
console.log(`Status: ${status.status} (${status.substatus || ''})`);
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 5000));
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
return status;
};
// Only needed for cross-chain
if (quote.action.fromChainId !== quote.action.toChainId) {
const finalStatus = await pollStatus(
tx.hash,
quote.action.fromChainId,
quote.action.toChainId
);
console.log('Final:', finalStatus.status);
}
```
For the full status reference including substatus values, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).
***
## Error Handling
Common errors when working with Composer:
| Error | Cause | Resolution |
| ------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `No routes found` | Vault token not supported or insufficient liquidity | Verify the vault token address is correct and the protocol is [supported](/composer/reference/supported-protocols) |
| `Simulation failed` | The Composer execution would fail onchain | Check token balances, allowances, and that the vault is accepting deposits |
| `Insufficient allowance` | Token approval not set or too low | Call `approve()` with the correct `approvalAddress` and amount |
For the full error reference, see [Error Codes](/api-reference/error-codes).
***
## Next Steps
Advanced cross-chain Composer flows and patterns
Implement same-chain and cross-chain withdrawals
Copy-paste recipes for Morpho, Aave, Euler, and more
Managed execution with hooks, events, and automatic retries
Full list of supported protocols and capabilities
# Cross-Chain Composer
Source: https://docs.li.fi/composer/guides/cross-chain-compose
Deposit into any protocol on any EVM chain, starting from wherever your assets are. LI.FI handles the bridge, swaps, and final deposit in one flow.
Composer supports cross-chain deposits across EVM chains. A user on Ethereum can deposit into a Morpho vault on Base, or deposit USDC from Arbitrum into any supported protocol on Base. LI.FI handles bridge selection, intermediate swaps, and the final deposit automatically.
From a developer's perspective, the integration is identical to same-chain: just set `fromChain` and `toChain` to different values.
Cross-chain Composer works across EVM chains today. Non-EVM chains (Solana, etc.) are not yet supported.
***
## How It Works
Cross-chain Composer combines two LI.FI capabilities:
1. **Bridge routing:** LI.FI selects the optimal bridge to move assets from the source chain to the destination chain.
2. **Composer execution:** On the destination chain, Composer deposits into the target protocol atomically.
From the user's perspective, this is a single flow with one signature on the source chain.
***
## Cross-Chain vs. Same-Chain
| | Same-chain | Cross-chain |
| --------------- | ----------------------- | ----------------------------------------------- |
| Transactions | 1 | 2 (source chain + destination chain) |
| Atomicity | Fully atomic | Per-chain atomic; eventually consistent overall |
| Status tracking | Not needed | Poll `/status` until complete |
| Integration | `fromChain === toChain` | `fromChain !== toChain` |
***
## Example: ETH (Ethereum) → Morpho Vault (Base)
Deposit ETH from Ethereum into a Spark-curated Morpho USDC vault on Base. LI.FI bridges ETH, swaps to USDC on Base, and deposits into the vault.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=8453&\
fromToken=0x0000000000000000000000000000000000000000&\
toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=100000000000000000&\
slippage=0.01'
```
```ts TypeScript theme={"system"}
import axios from 'axios';
const { data: quote } = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: 1, // Ethereum
toChain: 8453, // Base
fromToken: '0x0000000000000000000000000000000000000000', // ETH (native)
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: '100000000000000000', // 0.1 ETH
slippage: 0.01, // 1% for cross-chain
},
});
// No approval needed for native ETH; submit directly
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
console.log('Source chain tx confirmed:', tx.hash);
// Poll status until the cross-chain transfer completes
let status;
do {
const { data } = await axios.get('https://li.quest/v1/status', {
params: {
txHash: tx.hash,
fromChain: quote.action.fromChainId,
toChain: quote.action.toChainId,
},
});
status = data;
console.log(`Status: ${status.status} ${status.substatus || ''}`);
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 5000));
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
console.log('Final status:', status.status);
// User now holds Morpho vault tokens on Base
```
***
## Example: USDC (Arbitrum) → Morpho Vault (Base)
Bridge USDC from Arbitrum and deposit into the Spark-curated Morpho vault on Base in a single flow.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=42161&toChain=8453&fromToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=10000000'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: 42161, // Arbitrum
toChain: 8453, // Base
fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: '1000000000', // 1000 USDC (6 decimals)
slippage: 0.01,
},
});
// Approve USDC, send tx, and poll status (same pattern as above)
```
**What may happen under the hood:**
1. Bridge USDC from Arbitrum to Base
2. Deposit USDC into Morpho vault on Base
3. User receives vault tokens
The exact steps depend on the route returned by the API. The routing engine may choose a different bridge or insert intermediate swaps if that produces a better outcome.
### Swap + Bridge + Deposit
User has a different token on a different chain.
**Example:** ETH on Ethereum → Aave USDC lending on Optimism
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=10&fromToken=0x0000000000000000000000000000000000000000&toToken=AAVE_AUSDC_TOKEN_ADDRESS_ON_OPTIMISM&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=100000000000000000'
```
```ts TypeScript theme={"system"}
const quote = await getQuote({
fromChain: 1, // Ethereum
toChain: 10, // Optimism
fromToken: '0x0000000000000000000000000000000000000000', // ETH (native)
toToken: 'AAVE_AUSDC_TOKEN_ADDRESS_ON_OPTIMISM', // Aave aUSDC on Optimism
fromAmount: '100000000000000000', // 0.1 ETH
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
```
**What may happen under the hood:**
1. Swap ETH → USDC on Ethereum, then bridge USDC to Optimism, or bridge ETH directly and swap on the destination chain
2. Deposit USDC into Aave on Optimism
3. User receives aUSDC tokens
The routing engine decides the optimal sequence. You will see the chosen path in the route response.
### Bridge + Swap + Stake
User wants to stake on a different chain.
**Example:** USDC on Arbitrum → wstETH (Lido) on Ethereum
```ts TypeScript theme={"system"}
const quote = await getQuote({
fromChain: 42161, // Arbitrum
toChain: 1, // Ethereum
fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toToken: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', // wstETH on Ethereum
fromAmount: '1000000000', // 1000 USDC
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
```
**What may happen under the hood:**
1. Bridge USDC from Arbitrum to Ethereum
2. Swap USDC → ETH on Ethereum
3. Stake ETH via Lido, wrap to wstETH
4. User receives wstETH
## The routing engine may choose a different sequence (e.g., swap on Arbitrum first, then bridge ETH) if it finds a better path.
## Status Tracking
Cross-chain transfers require status polling. After the source chain transaction confirms, poll `GET /v1/status` until the transfer reaches `DONE` or `FAILED`.
```ts TypeScript theme={"system"}
const pollStatus = async (txHash: string, fromChain: number, toChain: number) => {
let status;
do {
const { data } = await axios.get('https://li.quest/v1/status', {
params: { txHash, fromChain, toChain },
});
status = data;
console.log(`Status: ${status.status} (${status.substatus || ''})`);
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 5000));
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
return status;
};
```
For the full status and substatus reference, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).
***
## Partial Failure Handling
Cross-chain flows are eventually consistent. In rare cases, the bridge step succeeds but the destination deposit fails. When this happens:
* The user receives the bridged tokens on the destination chain rather than vault tokens
* Source chain tokens are not at risk
* Gas fees are consumed
Build your UI to handle the `FAILED` status and prompt users to check their destination chain balance if a cross-chain transfer does not complete.
***
## Next Steps
Full API integration guide with same-chain and cross-chain examples
Managed cross-chain execution with the LI.FI TypeScript SDK
Copy-paste recipes for common protocols and chains
Protocols available for cross-chain deposits
# SDK Integration
Source: https://docs.li.fi/composer/guides/sdk-integration
Use the LI.FI TypeScript SDK for managed Composer execution with automatic allowance handling, status tracking, hooks, and events.
The LI.FI SDK handles the full Composer lifecycle (allowance checks, chain switching, transaction submission, and status tracking) so you can focus on your application logic. This guide shows how to use Composer through the SDK.
**Prerequisites:** You should have the LI.FI SDK installed and configured. If not, see [Installing the SDK](/sdk/installing-the-sdk) and [Configure SDK](/sdk/configure-sdk).
***
## Quick Example
Deposit USDC into a Morpho vault on Base using the SDK:
```ts theme={"system"}
import { createConfig, getQuote, convertQuoteToRoute, executeRoute } from '@lifi/sdk';
// 1. Configure the SDK (once, at app startup)
createConfig({
integrator: 'YourAppName',
});
// 2. Get a Composer quote
const quote = await getQuote({
fromChain: 8453, // Base
toChain: 8453, // Base
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token
fromAmount: '1000000', // 1 USDC
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
// 3. Convert quote to route and execute - SDK handles allowance, submission, and tracking
const route = convertQuoteToRoute(quote);
const executedRoute = await executeRoute(route, {
updateRouteHook(updatedRoute) {
console.log('Route updated:', updatedRoute);
},
});
console.log('Done!', executedRoute);
```
That's it. The SDK internally manages:
* **Allowance checks and approvals**
* **Transaction data retrieval** (if using `/advanced/routes`)
* **Transaction submission**
* **Status tracking and polling**
* **Chain switching** (for cross-chain flows)
***
## Step-by-Step Guide
### 1. Configure the SDK
Set up the SDK once at application startup. You must configure [EVM providers](/sdk/configure-sdk-providers#setup-evm-provider) for the chains you want to use.
```ts theme={"system"}
import { createConfig } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
createConfig({
integrator: 'YourAppName',
providers: [
// Configure your EVM provider - see SDK docs for full setup
],
});
```
For full provider configuration, see [Configure SDK Providers](/sdk/configure-sdk-providers).
### 2. Request a Composer Quote
Use `getQuote` for a single best route (includes transaction data) or `getRoutes` for multiple options.
#### Using `getQuote`
```ts theme={"system"}
import { getQuote } from '@lifi/sdk';
const quote = await getQuote({
fromChain: 8453,
toChain: 8453,
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
fromAmount: '1000000',
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
```
#### Using `getRoutes`
```ts theme={"system"}
import { getRoutes } from '@lifi/sdk';
const result = await getRoutes({
fromChainId: 8453,
toChainId: 8453,
fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
toTokenAddress: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
fromAmount: '1000000',
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
const route = result.routes[0]; // Select the best route
```
The `toToken` / `toTokenAddress` is always the **vault token address** of the target protocol. This is what triggers Composer. Find vault token addresses on the protocol's own app or documentation.
### 3. Execute the Route
The `executeRoute` function handles the entire execution lifecycle:
```ts theme={"system"}
import { executeRoute } from '@lifi/sdk';
const executedRoute = await executeRoute(route, {
// Called whenever the route object is updated during execution
updateRouteHook(updatedRoute) {
const step = updatedRoute.steps[0];
const process = step?.execution?.process;
const lastProcess = process?.[process.length - 1];
console.log(`Step: ${step?.tool}`);
console.log(`Status: ${lastProcess?.status}`);
console.log(`Tx: ${lastProcess?.txHash || 'pending'}`);
},
});
```
### 4. Monitor Execution
The `updateRouteHook` callback fires on every state change. Use it to update your UI:
```ts theme={"system"}
const executedRoute = await executeRoute(route, {
updateRouteHook(updatedRoute) {
for (const step of updatedRoute.steps) {
if (!step.execution) continue;
for (const process of step.execution.process) {
switch (process.status) {
case 'STARTED':
console.log(`${process.type} started`);
break;
case 'PENDING':
console.log(`${process.type} pending - tx: ${process.txHash}`);
break;
case 'DONE':
console.log(`${process.type} complete`);
break;
case 'FAILED':
console.error(`${process.type} failed: ${process.error?.message}`);
break;
}
}
}
},
});
```
***
## Cross-Chain Composer via SDK
Cross-chain Composer works across EVM chains. The integration is identical to same-chain: just use different `fromChain` and `toChain` values. The SDK handles bridge routing, chain switching, and status tracking automatically.
### ETH (Ethereum) → Morpho vault (Base)
```ts theme={"system"}
const crossChainQuote = await getQuote({
fromChain: 1, // Ethereum
toChain: 8453, // Base
fromToken: '0x0000000000000000000000000000000000000000', // ETH (native)
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAmount: '100000000000000000', // 0.1 ETH
fromAddress: '0xYOUR_WALLET_ADDRESS',
});
const route = convertQuoteToRoute(crossChainQuote);
const executedRoute = await executeRoute(route, {
updateRouteHook(updatedRoute) {
console.log('Route updated:', updatedRoute.steps.map(s => s.tool));
},
});
```
### USDC (Ethereum) → Morpho Vault (Base)
```ts theme={"system"}
const crossChainQuote = await getQuote({
fromChain: 1, // Ethereum
toChain: 8453, // Base
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAmount: '1000000000', // 1000 USDC
fromAddress: '0xYOUR_WALLET_ADDRESS',
slippage: 0.01,
});
const route = convertQuoteToRoute(crossChainQuote);
const executedRoute = await executeRoute(route, {
updateRouteHook(updatedRoute) {
console.log('Route updated:', updatedRoute.steps.map(s => s.tool));
},
});
```
The SDK automatically handles:
* Bridge selection and execution
* Waiting for bridge completion
* Chain switching to the destination chain
* Executing the Composer deposit on the destination chain
* Status tracking throughout
***
## Execution Options
All execution options are optional but can be useful for advanced use cases:
| Option | Type | Description |
| ------------------------------ | ------------------------------ | ------------------------------------------------------------------------------ |
| `updateRouteHook` | `(route) => void` | Called on every route state change. Use for UI updates. |
| `updateTransactionRequestHook` | `(txRequest) => Promise` | Modify transaction requests before submission (e.g., custom gas). |
| `acceptExchangeRateUpdateHook` | `(params) => Promise` | Called if the exchange rate changes during execution. Return `true` to accept. |
| `infiniteApproval` | `boolean` | If `true`, approves max uint256 instead of the exact amount. |
| `executeInBackground` | `boolean` | If `true`, execution continues even if the user navigates away. |
For the full execution options reference, see [Execute Routes/Quotes](/sdk/execute-routes).
***
## Error Handling
The SDK throws errors that you can catch and handle:
```ts theme={"system"}
try {
const executedRoute = await executeRoute(route, {
updateRouteHook(updatedRoute) {
// Track progress
},
});
} catch (error) {
console.error('Execution failed:', error.message);
// Check the route's step execution for detailed failure info
for (const step of route.steps) {
if (step.execution) {
for (const process of step.execution.process) {
if (process.status === 'FAILED') {
console.error(`Step ${step.tool} failed:`, process.error?.message);
}
}
}
}
}
```
***
## Next Steps
Advanced cross-chain Composer flows
Zero-code Composer via the drop-in Widget
Full SDK configuration reference
Copy-paste recipes for common protocols
# Widget Integration
Source: https://docs.li.fi/composer/guides/widget-integration
Composer works automatically in the LI.FI Widget. Learn how to configure, customize, and listen for Composer-specific events.
The LI.FI Widget is a drop-in React component that provides a complete swap, bridge, and Composer UI. **Composer works out-of-the-box.** When a user selects a vault token as the destination, the Widget automatically uses Composer to execute the deposit.
***
## Zero-Code Composer
If you already have the LI.FI Widget integrated, Composer is already available to your users. No additional configuration is needed.
When a user:
1. Selects a vault token (e.g., a Morpho vault) as the destination token
2. The Widget automatically detects this is a Composer route
3. Displays the estimated vault tokens the user will receive
4. Executes the full Composer flow (swap + deposit) in a single transaction
***
## Basic Setup
If you haven't integrated the Widget yet, here's a minimal setup:
```tsx theme={"system"}
import { LiFiWidget } from '@lifi/widget';
import type { WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
variant: 'default',
};
export function ComposerWidget() {
return ;
}
```
For full Widget setup instructions, see [Install Widget](/widget/install-widget).
***
## Configuring Composer in the Widget
### Pre-select a Vault Token
You can pre-configure the Widget to target a specific vault token, creating a focused deposit experience:
```tsx theme={"system"}
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
toChain: 8453, // Base
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token
};
```
This pre-fills the destination with the Morpho vault, so users only need to select their source token and amount.
### Lock the Destination
To create a dedicated deposit widget (e.g., "Deposit into Morpho"), lock the destination so users can't change it:
```tsx theme={"system"}
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
toChain: 8453,
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
disabledUI: ['toToken', 'toAddress'],
};
```
### Filter Source Chains and Tokens
Restrict which source chains or tokens are available:
```tsx theme={"system"}
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
toChain: 8453,
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
chains: {
allow: [1, 10, 42161, 8453], // Only Ethereum, Optimism, Arbitrum, Base
},
};
```
***
## Listening for Composer Events
The Widget emits events during execution. Use these to track Composer transactions in your application:
```tsx theme={"system"}
import { LiFiWidget } from '@lifi/widget';
import type { Route } from '@lifi/sdk';
function ComposerWidget() {
return (
{
console.log('Composer execution started:', route);
}}
onRouteExecutionUpdated={(route: Route) => {
console.log('Composer execution updated:', route);
}}
onRouteExecutionCompleted={(route: Route) => {
console.log('Composer execution completed:', route);
// e.g., update user's vault position in your UI
}}
onRouteExecutionFailed={(route: Route) => {
console.error('Composer execution failed:', route);
}}
/>
);
}
```
For the full events reference, see [Widget Events](/widget/widget-events).
***
## Widget Variants for Composer
The Widget supports different visual variants. For Composer use cases, consider:
| Variant | Best For |
| --------- | ------------------------------------------- |
| `default` | General-purpose swap/bridge/deposit widget |
| `compact` | Embedded deposit widgets with limited space |
| `drawer` | Slide-out panel for deposit flows |
```tsx theme={"system"}
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
variant: 'compact',
toChain: 8453,
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
};
```
For all variants, see [Select Widget Variants](/widget/select-widget-variants).
***
## Customizing Appearance
Match the Widget to your app's design system:
```tsx theme={"system"}
const widgetConfig: WidgetConfig = {
integrator: 'YourAppName',
appearance: 'dark',
theme: {
palette: {
primary: { main: '#5C67FF' },
secondary: { main: '#fab6f4' },
},
shape: {
borderRadius: 12,
borderRadiusSecondary: 8,
},
},
};
```
For full theming options, see [Customize Widget](/widget/customize-widget).
***
## Wallet Management
The Widget can manage wallet connections internally or integrate with your existing wallet provider. See [Wallet Management](/widget/wallet-management) for details on connecting external wallet providers.
***
## Try It Live
Test Composer in the Widget Playground:
Interactive playground to test Composer flows with the LI.FI Widget
***
## Next Steps
Full Widget configuration reference
All available Widget event callbacks
Direct API integration for full control
Managed execution with the TypeScript SDK
# Withdrawals
Source: https://docs.li.fi/composer/guides/withdrawals
Withdraw from Composer-supported protocol positions via the LI.FI API with same-chain and cross-chain patterns.
Withdrawals follow the same integration pattern as deposits. Use `GET /quote` or `POST /advanced/routes` with the user's position token as `fromToken` and the desired output token as `toToken`.
Withdrawals are available for protocols that support the **Withdraw** action. Check the [Supported Protocols](/composer/reference/supported-protocols) reference for per-protocol capabilities.
***
## How Withdrawals Work
| Parameter | Role in a withdrawal |
| ----------------------- | ----------------------------------------------------------------------- |
| `fromToken` | Protocol position token the user holds (vault token, aToken, LST, etc.) |
| `toToken` | Token the user wants to receive |
| `fromChain` / `toChain` | Same chain for local withdrawals; different chains for cross-chain |
If the protocol and market support withdraw via Composer, LI.FI returns a Composer-capable route.
***
## Same-Chain Withdrawal
Withdraw from a protocol position into an underlying token on the same chain.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=CHAIN_ID&\
toChain=CHAIN_ID&\
fromToken=PROTOCOL_POSITION_TOKEN_ADDRESS&\
toToken=DESIRED_OUTPUT_TOKEN_ADDRESS&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=POSITION_AMOUNT_IN_SMALLEST_UNIT'
```
```ts TypeScript theme={"system"}
const quote = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: CHAIN_ID,
toChain: CHAIN_ID,
fromToken: 'PROTOCOL_POSITION_TOKEN_ADDRESS',
toToken: 'DESIRED_OUTPUT_TOKEN_ADDRESS',
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: 'POSITION_AMOUNT_IN_SMALLEST_UNIT',
},
});
```
***
## Cross-Chain Withdrawal
Withdraw on one chain and receive the output token on another chain in a single flow.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=SOURCE_CHAIN_ID&\
toChain=DESTINATION_CHAIN_ID&\
fromToken=PROTOCOL_POSITION_TOKEN_ADDRESS&\
toToken=DESTINATION_OUTPUT_TOKEN_ADDRESS&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=POSITION_AMOUNT_IN_SMALLEST_UNIT'
```
Cross-chain withdrawals require status polling via `GET /status` after the source-chain transaction confirms. See [Cross-Chain Composer Patterns](/composer/guides/cross-chain-compose).
***
## Execution Flow
The execution steps are identical to Composer deposits.
1. Request a quote via `/quote` or `/advanced/routes`
2. Set token allowance if `approvalAddress` is present and `fromToken` is an ERC-20
3. Submit the `transactionRequest`
4. Poll `/status` for cross-chain transfers
Full walkthrough of the quote, allowance, execute, and status polling flow.
***
## Protocols That Support Withdraw
The following protocols support withdrawals via Composer. Deposit-only protocols (Ethena, Kinetiq, Maple, Royco, USDai) must be withdrawn through their own interfaces.
| Protocol | Type | Withdraw via Composer |
| ------------- | ---------------- | --------------------- |
| Aave V3 | Lending | Yes |
| Avant | Yield | Yes |
| Avon | Lending | Yes |
| Cap | Yield | Yes |
| Euler | Lending | Yes |
| Felix Vanilla | Vaults | Yes |
| Fluid | Lending | Yes |
| HyperLend | Lending | Yes |
| HypurrFi | Lending | Yes |
| Kelp | Liquid Staking | Yes |
| Morpho V1/V2 | Lending / Vaults | Yes |
| Neutrl | Yield | Yes |
| Neverland | Vaults | Yes |
| Spark | Lending | Yes |
| Tokemak | Yield | Yes |
Full protocol reference including deposit-only vs deposit + withdraw support.
***
## Error Handling
Common issues specific to withdrawals:
| Issue | Resolution |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| No route returned | Confirm the protocol supports withdraw via Composer and the user holds the position token |
| Insufficient balance | Verify the user's `fromToken` balance matches the requested `fromAmount` |
| Allowance error | Set approval for the position token to the `approvalAddress` returned in the quote |
| Cross-chain partial failure | Bridged tokens arrive on the destination chain but the final swap may need manual completion |
For full status and substatus values, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).
***
## Next Steps
Bridge-inclusive execution patterns and status handling
End-to-end deposit recipes for multiple protocols
How Composer routes are activated and returned
# How Composer Works
Source: https://docs.li.fi/composer/how-it-works
Deep dive into Composer's architecture: the Onchain VM, eDSL compiler, dynamic calldata injection, and the full transaction lifecycle.
This page explains the internal architecture of LI.FI Composer: how multi-step DeFi operations get compiled, simulated, and executed as a single transaction.
***
## Architecture Overview
Composer transforms a user's intent (e.g., "deposit USDC into Morpho on Base") into an optimised, executable onchain transaction. The system has three layers:
***
## Core Components
### 1. Onchain VM (Execution Engine)
The Onchain VM is a smart contract deployed on supported EVM chains. It acts as a general-purpose execution engine that can:
* Call any other onchain protocol or series of protocols
* Pass outputs from one step as inputs to the next
* Handle token approvals, transfers, and balance checks between steps
* Execute the entire sequence atomically (same-chain)
The VM receives compiled bytecode and executes it in a single transaction, abstracting away the complexity of multi-protocol interactions.
**Contract address (Ethereum):** [`0xD214A2eB3799076791A4Cde4F0AB184A49dB832d`](https://etherscan.io/address/0xD214A2eB3799076791A4Cde4F0AB184A49dB832d)
### 2. eDSL and Compiler
The embedded Domain-Specific Language (eDSL) is a purpose-built language for expressing contract interactions in TypeScript. When a Composer route is requested:
1. The routing engine identifies which protocols and actions are needed
2. The eDSL expresses these interactions as a typed program
3. The compiler transforms this program into bytecode the Onchain VM can execute
This approach provides type safety, composability, and the ability to optimise execution paths at compile time.
### 3. Dynamic Calldata Injection
Many DeFi operations require data from a previous step. For example, depositing into a vault requires knowing the exact token amount received from a preceding swap. Composer handles this through **dynamic calldata injection**:
* The compiler identifies dependencies between steps
* At execution time, the VM automatically intercepts outputs from completed steps
* These outputs are injected as inputs into subsequent steps
* This happens entirely onchain, within the same transaction
***
## Transaction Lifecycle
Here is the full lifecycle of a Composer transaction, from user request to onchain execution:
### Step-by-Step Breakdown
| Step | What Happens | Where |
| ------------------------------- | ----------------------------------------------------------------------------------- | -------------- |
| **1. Quote request** | Developer requests a quote with a vault token as `toToken` | Client → API |
| **2. Route optimisation** | LI.FI's routing engine finds the optimal path across DEXs, bridges, and protocols | API backend |
| **3. eDSL compilation** | The Composer compiler generates bytecode for the Onchain VM | API backend |
| **4. Pre-execution simulation** | The full execution path is simulated to verify it will succeed and estimate outputs | API backend |
| **5. Quote response** | API returns `transactionRequest` (ready-to-sign tx) plus estimated amounts | API → Client |
| **6. Transaction submission** | User signs and submits the transaction to the blockchain | Client → Chain |
| **7. Onchain execution** | The Onchain VM executes the compiled bytecode, performing all steps atomically | Onchain |
| **8. Confirmation** | Transaction is confirmed; vault tokens are in the user's wallet | Chain → Client |
| **9. Status check** | Optionally poll `/status` for cross-chain transfers | Client → API |
***
## Same-Chain vs. Cross-Chain
Composer behaves differently depending on whether the source and destination are on the same chain.
### Same-Chain Composition
* **Atomic execution:** all steps succeed or none do
* **Single gas payment:** one transaction fee
* **Instant completion:** confirmed in one block
* **Simulation guarantee:** if simulation passes, execution will succeed (barring extreme edge cases like mempool front-running)
### Cross-Chain Composition
* **Two-phase execution:** source chain transaction triggers a bridge, then destination chain actions execute
* **Per-phase atomicity:** each phase is atomic within its chain, but the overall flow is eventually consistent
* **Bridge latency:** total time depends on the bridge used (seconds to minutes)
* **Status tracking required:** use `/status` endpoint to monitor cross-chain progress
Cross-chain Composer combines LI.FI's bridge routing with Composer's onchain execution on the destination chain. The API call is identical to same-chain: just use different `fromChain` and `toChain` values. Cross-chain deposits are supported across EVM chains. Non-EVM chains are not yet supported.
***
## Pre-Execution Simulation
One of Composer's key safety features is **pre-execution simulation**. Before returning a quote, the backend:
1. **Simulates the full execution path** using a fork of the current chain state
2. **Verifies all steps succeed:** token swaps, approvals, deposits
3. **Calculates exact output amounts:** the estimated vault tokens the user will receive
4. **Detects potential failures:** insufficient liquidity, incompatible tokens, protocol-specific issues
If simulation fails, the API returns an error rather than a transaction that would revert onchain. This protects users from wasting gas on failed transactions.
***
## Composer as a LI.FI Tool
In the LI.FI architecture, Composer is classified as a **tool**, similar to how bridges and DEX aggregators are tools. This means:
* Composer routes appear alongside bridge and swap routes in API responses
* The routing engine automatically selects Composer when `toToken` matches a supported protocol token
* For same-chain routes, Composer is identified by `tool: "composer"` in the quote response. For cross-chain routes, the top-level `tool` is the bridge used (e.g., `"stargateV2"`), and Composer appears as the final step in the route's step list.
***
## Next Steps
Step-by-step guide to integrating Composer via the REST API
Learn how to compose cross-chain DeFi operations
Understand failure modes and how to handle them
Full list of supported protocols and capabilities
# What is Composer?
Source: https://docs.li.fi/composer/overview
One-click DeFi deposits, staking, and lending across any EVM chain. Bundle swaps, bridges, and protocol interactions into a single flow.
LI.FI Composer bundles protocol interactions (deposits, staking, lending) into a **single flow across any EVM chain**. Instead of navigating multiple protocols and signing multiple transactions, your users complete DeFi workflows in one click.
**No custom integration required.** If you already use the LI.FI API, SDK, or Widget, you already have Composer. Set `toToken` to a [supported protocol's](/composer/reference/supported-protocols) token address and Composer activates automatically.
***
## Who Is Composer For?
Offer users sophisticated onchain actions directly in your app. Composer reduces user friction while expanding the breadth of DeFi operations your platform supports. Create campaigns for user engagement, such as "deposit into new protocol" promotions.
Enable 1-click deposits into your protocol. Users can deposit from any integrated wallet or dApp without leaving their current app, including from any other EVM chain.
Get your protocol integrated as a Composer target to expand user accessibility and adoption. Partner protocols can be invited to the Composer backend codebase. See [For Protocol Teams](/composer/for-protocols/integration-guide).
***
## Why Use Composer?
### For your business
* **Reduce integration costs.** Support deposits into 20+ DeFi protocols through a single API integration instead of building custom integrations for each protocol individually.
* **Expand your product offering.** Add an earn, yield, or staking section to your wallet or dApp. Composer gives your users access to lending, liquid staking, and vault strategies without building protocol-specific logic.
* **Increase user engagement.** One-click deposits reduce friction and drop-off. Users who would otherwise leave your app to access another protocol can now complete the full flow in your product.
* **Simple integration.** If you already use the LI.FI API, SDK, or Widget, Composer is already available. Set `toToken` to a supported protocol's token address and it activates automatically.
### Technical advantages
* **Single transaction execution.** Multiple actions completed in one transaction. Users sign once, pay gas once.
* **Lower gas costs.** Batching actions eliminates per-step transaction fees.
* **Simulated before execution.** The entire execution path is simulated before submitting onchain, preventing failed transactions and giving users certainty about outcomes.
* **Atomic execution.** Compositions execute atomically. All steps succeed or none do.
* **Works everywhere.** Available through the API, SDK, and Widget with zero additional integration.
***
## How It Works
Composer uses two core components:
1. **Onchain VM (Execution Engine).** A smart contract that can call any other onchain protocol or series of them, handling all execution without requiring users to understand blockchain interactions.
2. **eDSL and Compiler.** A purpose-built domain-specific language to express contract interactions in TypeScript. This gets compiled to bytecode for the VM to execute onchain.
When you request a quote with a vault token as the `toToken`, LI.FI's routing engine automatically identifies the optimal path (swap, bridge, or both), compiles the Composer instructions, simulates the full execution path, and returns a ready-to-sign transaction. No dedicated endpoint or extra parameters are needed — the standard `GET /quote` and `POST /advanced/routes` endpoints work as-is. This applies equally to same-chain and cross-chain flows: to deposit across chains, just set `fromChain` and `toChain` to different values.
Learn about the Onchain VM, eDSL compiler, dynamic calldata injection, and the full transaction lifecycle.
***
## Supported Protocols & Chains
Composer supports 20+ protocols across 20+ EVM chains. The team is open to integration requests based on technical feasibility and community feedback.
Live reference: protocols, supported actions, and supported chains. Always up to date.
***
## Try It Out
Deposit 1 USDC into a Morpho vault on Base with a single API call:
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=YOUR_WALLET_ADDRESS&toAddress=YOUR_WALLET_ADDRESS&fromAmount=1000000'
```
```ts TypeScript theme={"system"}
import axios from 'axios';
const quote = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: 8453, // Base
toChain: 8453, // Base (same-chain)
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token
fromAddress: 'YOUR_WALLET_ADDRESS',
toAddress: 'YOUR_WALLET_ADDRESS',
fromAmount: '1000000', // 1 USDC (6 decimals)
},
});
console.log(quote.data);
```
Follow the step-by-step quickstart to execute your first Composer transaction in under 5 minutes.
***
## Choose Your Integration Path
Direct REST API integration for full control over the Composer flow.
Use the LI.FI TypeScript SDK for managed execution, status tracking, and error handling.
Drop-in UI component. Composer works automatically with zero additional code.
***
## Current Limitations
* **EVM chains only.** Cross-chain deposits are supported across EVM chains. Solana and other non-EVM chains are not yet supported.
* **Tokenised positions only.** Composer targets must return a token (e.g., vault tokens, LP tokens, LSTs).
***
## Security
All LI.FI smart contracts, including the Composer onchain VM, undergo independent security audits before production deployment. LI.FI maintains a \$1,000,000 USD bug bounty program and real-time contract monitoring.
Audit reports, bug bounty program, and monitoring details.
***
## Next Steps
Execute your first Composer transaction in under 5 minutes
Step-by-step REST API integration walkthrough
Onchain VM, eDSL compiler, and transaction lifecycle
Copy-paste recipes for vault, staking, and lending deposits
Bridge + deposit patterns across EVM chains
Withdraw from protocol positions via Composer
# Quickstart
Source: https://docs.li.fi/composer/quickstart
Execute your first Composer transaction in under 5 minutes. Deposit USDC into a Morpho vault on Base with a single API call.
This quickstart walks you through a same-chain Composer transaction, depositing USDC into a Morpho vault on Base. By the end, you'll understand the full flow of requesting a quote, approving tokens, executing the transaction, and tracking status.
Composer uses standard LI.FI endpoints. Set `toToken` to a supported protocol token address and the routing engine returns a Composer route automatically.
This quickstart covers a same-chain deposit. Composer also supports cross-chain deposits across EVM chains. See [Cross-Chain Composer Patterns](/composer/guides/cross-chain-compose) for those flows.
## Prerequisites
* A wallet address with USDC on Base (even a small amount like 1 USDC works)
* Node.js 18+ (for the TypeScript examples) or `curl`
***
Request a quote with `toToken` set to the vault token address:
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000'
```
```ts TypeScript theme={"system"}
import axios from 'axios';
const API_URL = 'https://li.quest/v1';
const getQuote = async (
fromChain: number,
toChain: number,
fromToken: string,
toToken: string,
fromAmount: string,
fromAddress: string
) => {
const result = await axios.get(`${API_URL}/quote`, {
params: {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
toAddress: fromAddress,
},
});
return result.data;
};
const quote = await getQuote(
8453, // Base
8453, // Base (same-chain)
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
'0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token
'1000000', // 1 USDC (6 decimals)
'0xYOUR_WALLET_ADDRESS'
);
console.log(quote);
```
**Key parameters:**
| Parameter | Value | Description |
| ------------ | --------------- | -------------------------- |
| `fromChain` | `8453` | Base chain ID |
| `toChain` | `8453` | Base (same-chain deposit) |
| `fromToken` | `0x8335...2913` | USDC on Base |
| `toToken` | `0x7BfA...34A` | Morpho vault token address |
| `fromAmount` | `1000000` | 1 USDC (6 decimals) |
The `toToken` is always the **vault token address** of the target protocol. You can find vault token addresses on the protocol's own app or documentation.
The response includes `transactionRequest`, a ready-to-sign EVM transaction, along with estimated output amounts and the tools used. The `tool` field will be `"composer"` for Composer routes.
Quotes reflect current market conditions. If the user reviews a quote for more than 30 seconds, re-fetch before signing to get up-to-date pricing and simulation results.
Before executing, ensure the LI.FI Diamond contract is approved to spend your tokens. The approval address is returned in the quote response at `quote.estimate.approvalAddress`. This points to the [LI.FI Diamond](https://etherscan.io/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE), an audited contract deployed on all supported chains.
If you're sending a native token (e.g., ETH), skip this step. Native tokens don't require approval.
```ts TypeScript theme={"system"}
import { ethers } from 'ethers';
const ERC20_ABI = [
'function approve(address spender, uint256 amount) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
];
const checkAndSetAllowance = async (
signer: ethers.Signer,
tokenAddress: string,
approvalAddress: string,
amount: string
) => {
const erc20 = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const signerAddress = await signer.getAddress();
const allowance = await erc20.allowance(signerAddress, approvalAddress);
if (allowance < BigInt(amount)) {
const tx = await erc20.approve(approvalAddress, amount);
await tx.wait();
console.log('Approval set.');
} else {
console.log('Allowance already sufficient.');
}
};
await checkAndSetAllowance(
signer,
quote.action.fromToken.address,
quote.estimate.approvalAddress,
quote.action.fromAmount
);
```
If an approval transaction was sent, re-fetch the quote before executing. The `transactionRequest` returned in the original quote contains gas estimates that can become stale by the time the approval confirms. Call `GET /v1/quote` again with the same parameters to get a fresh transaction.
Send the transaction using the `transactionRequest` object from the quote response.
```ts TypeScript theme={"system"}
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Transaction sent:', tx.hash);
const receipt = await tx.wait();
console.log('Transaction confirmed:', receipt.hash);
```
That's it. Composer handles the swap and deposit in a single atomic transaction.
For same-chain transactions, the transaction is complete once confirmed. For cross-chain transfers, poll the `/status` endpoint until the transfer reaches `DONE` or `FAILED`:
```ts TypeScript theme={"system"}
const getStatus = async (txHash: string, fromChain: number, toChain: number) => {
const result = await axios.get(`${API_URL}/status`, {
params: { txHash, fromChain, toChain },
});
return result.data;
};
// For cross-chain transfers, poll until complete
if (quote.action.fromChainId !== quote.action.toChainId) {
let status;
do {
status = await getStatus(tx.hash, quote.action.fromChainId, quote.action.toChainId);
console.log('Status:', status.status, status.substatus);
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5s
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
console.log('Final status:', status.status);
}
```
| Status | Meaning |
| ----------- | ----------------------------------------- |
| `NOT_FOUND` | Transaction submitted but not yet indexed |
| `INVALID` | Hash is not tied to the requested tool |
| `PENDING` | Transaction is in progress |
| `DONE` | Completed successfully |
| `FAILED` | Transaction failed |
For the full status reference, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).
***
## Full Working Example
Copy-paste this complete example to run your first Composer transaction:
```ts TypeScript theme={"system"}
import { ethers } from 'ethers';
import axios from 'axios';
const API_URL = 'https://li.quest/v1';
// --- Configuration ---
const PRIVATE_KEY = 'YOUR_PRIVATE_KEY';
const RPC_URL = 'https://mainnet.base.org';
const FROM_CHAIN = 8453; // Base
const FROM_TOKEN = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
const TO_TOKEN = '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A'; // Morpho vault token
const FROM_AMOUNT = '1000000'; // 1 USDC
// --- Setup ---
const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
// --- Helpers ---
const getQuote = async (fromAddress: string) => {
const result = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: FROM_CHAIN,
toChain: FROM_CHAIN,
fromToken: FROM_TOKEN,
toToken: TO_TOKEN,
fromAmount: FROM_AMOUNT,
fromAddress,
toAddress: fromAddress,
},
});
return result.data;
};
const ERC20_ABI = [
'function approve(address spender, uint256 amount) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
];
const ensureAllowance = async (
tokenAddress: string,
approvalAddress: string,
amount: string
) => {
const erc20 = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const address = await signer.getAddress();
const allowance = await erc20.allowance(address, approvalAddress);
if (allowance < BigInt(amount)) {
console.log('Setting allowance...');
const tx = await erc20.approve(approvalAddress, amount);
await tx.wait();
console.log('Allowance set.');
}
};
// --- Main ---
const run = async () => {
const address = await signer.getAddress();
console.log('Wallet:', address);
// 1. Get quote
console.log('Requesting Composer quote...');
const quote = await getQuote(address);
console.log('Quote received. Tool:', quote.tool);
console.log('Estimated output:', quote.estimate.toAmount);
// 2. Approve
await ensureAllowance(
quote.action.fromToken.address,
quote.estimate.approvalAddress,
quote.action.fromAmount
);
// 3. Execute
console.log('Sending transaction...');
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Tx hash:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
console.log('Done! USDC deposited into Morpho vault.');
};
run().catch(console.error);
```
***
## What Just Happened?
Behind the scenes, Composer:
1. **Identified the optimal path.** LI.FI's routing engine determined the best way to convert USDC into the Morpho vault token.
2. **Activated Composer from `toToken`.** The vault token destination signaled a Composer-capable route.
3. **Compiled eDSL instructions.** The Composer compiler generated bytecode for the onchain VM.
4. **Simulated the execution.** The full path was simulated before returning the quote, ensuring it will succeed.
5. **Executed atomically.** Your single transaction swapped USDC and deposited into Morpho in one atomic operation.
***
## Next Steps
Bridge + deposit and swap + bridge + deposit across EVM chains
Add the other half of the user journey with withdraw flows
Use the LI.FI SDK for managed execution with hooks and events
Copy-paste recipes for Morpho, Aave, Euler, and more
# Deposit Examples
Source: https://docs.li.fi/composer/recipes/vault-deposits
End-to-end deposit recipes for Composer-supported protocols. Same-chain and cross-chain, from quote to confirmed transaction.
Every Composer deposit, whether it targets a vault, staking protocol, or lending market, uses the same API call. Set `toToken` to the protocol's token address and LI.FI handles swaps, bridging, and the final deposit.
Each recipe below is end-to-end: request a quote, approve tokens, sign the transaction, and confirm the deposit.
All examples use `GET /quote`. The same `toToken` addresses work with `POST /advanced/routes` and the LI.FI SDK. See [API Integration](/composer/guides/api-integration) or [SDK Integration](/composer/guides/sdk-integration) for full integration guides.
***
## Same-Chain: USDC → Morpho Vault (Base)
Deposit 1 USDC into a Spark-curated Morpho USDC vault on Base.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000'
```
```ts TypeScript theme={"system"}
import { ethers } from 'ethers';
import axios from 'axios';
const API_URL = 'https://li.quest/v1';
// 1. Get a Composer quote
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 8453,
toChain: 8453,
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault token (sparkUSDC)
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000', // 1 USDC (6 decimals)
slippage: 0.005,
},
});
console.log('Tool:', quote.tool); // "composer"
console.log('Estimated output:', quote.estimate.toAmount);
console.log('Approval target:', quote.estimate.approvalAddress);
// 2. Approve the LI.FI Diamond to spend your tokens
const ERC20_ABI = [
'function approve(address spender, uint256 amount) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
];
const erc20 = new ethers.Contract(quote.action.fromToken.address, ERC20_ABI, signer);
const allowance = await erc20.allowance(await signer.getAddress(), quote.estimate.approvalAddress);
if (allowance < BigInt(quote.action.fromAmount)) {
const approveTx = await erc20.approve(quote.estimate.approvalAddress, quote.action.fromAmount);
await approveTx.wait();
console.log('Approval confirmed.');
}
// 3. Sign and send the transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Tx hash:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
```
To target a different protocol, swap the `toToken` for any address listed in the [Supported Protocols](/composer/reference/supported-protocols) reference. Everything else stays the same.
***
## Same-Chain: USDC → Aave (Ethereum)
Deposit 1000 USDC into Aave V3 on Ethereum, receiving aUSDC in return.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1, // Ethereum
toChain: 1, // Ethereum
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
toToken: '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c', // aEthUSDC (Aave V3)
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000000', // 1000 USDC (6 decimals)
slippage: 0.005,
},
});
// Approve + send (same pattern as the Morpho example above)
const erc20 = new ethers.Contract(quote.action.fromToken.address, ERC20_ABI, signer);
const allowance = await erc20.allowance(await signer.getAddress(), quote.estimate.approvalAddress);
if (allowance < BigInt(quote.action.fromAmount)) {
const approveTx = await erc20.approve(quote.estimate.approvalAddress, quote.action.fromAmount);
await approveTx.wait();
}
const tx = await signer.sendTransaction(quote.transactionRequest);
const receipt = await tx.wait();
console.log('aEthUSDC received. Tx:', receipt.hash);
```
***
## Same-Chain: USDe → sUSDe on Ethena (Ethereum)
Deposit USDe into Ethena's staked USDe (sUSDe) for yield.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0x4c9EDD5852cd905f086C759E8383e09bff1E68B3&\
toToken=0x9D39A5DE30e57443BfF2A8307A4256c8797A3497&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000000000000000&\
slippage=0.005'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1,
toChain: 1,
fromToken: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3', // USDe
toToken: '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497', // sUSDe (Ethena)
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000000000000000000', // 1000 USDe (18 decimals)
slippage: 0.005,
},
});
// Approve + send (same pattern as the Morpho example above)
```
***
## Same-Chain: USDC → Euler eUSDC-2 (Ethereum)
Deposit USDC into Euler V2's USDC lending vault on Ethereum, receiving eUSDC-2 in return.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1, // Ethereum
toChain: 1, // Ethereum
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
toToken: '0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9', // eUSDC-2 (Euler V2)
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000000', // 1000 USDC (6 decimals)
slippage: 0.005,
},
});
// Approve + send (same pattern as the Morpho example above)
```
***
## Same-Chain: USDC → syrupUSDC on Maple (Ethereum)
Deposit USDC into Maple Finance's Syrup USDC pool on Ethereum, receiving syrupUSDC.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1, // Ethereum
toChain: 1, // Ethereum
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
toToken: '0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b', // syrupUSDC (Maple)
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000000', // 1000 USDC (6 decimals)
slippage: 0.005,
},
});
// Approve + send (same pattern as the Morpho example above)
```
***
## Cross-Chain: ETH (Ethereum) → Morpho Vault (Base)
Deposit ETH from Ethereum into the Morpho vault on Base. LI.FI handles the bridge, intermediate swaps, and deposit.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=8453&fromToken=0x0000000000000000000000000000000000000000&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=100000000000000000'
```
```ts TypeScript theme={"system"}
// 1. Get cross-chain quote
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1, // Ethereum
toChain: 8453, // Base
fromToken: '0x0000000000000000000000000000000000000000', // ETH
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '100000000000000000', // 0.1 ETH
slippage: 0.01, // 1% for cross-chain
},
});
// 2. No approval needed for native ETH
// 3. Send the source chain transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
console.log('Source tx confirmed:', tx.hash);
// 4. Poll status until the cross-chain transfer completes
let status;
do {
const { data } = await axios.get(`${API_URL}/status`, {
params: {
txHash: tx.hash,
fromChain: quote.action.fromChainId,
toChain: quote.action.toChainId,
},
});
status = data;
console.log(`Status: ${status.status} ${status.substatus || ''}`);
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 5000));
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
console.log('Final:', status.status);
```
Cross-chain transfers require status polling via `GET /status`. See [Cross-Chain Composer Patterns](/composer/guides/cross-chain-compose) for the full execution flow and partial failure handling.
***
## Cross-Chain: USDC (Ethereum) → Morpho Vault (Base)
Bridge USDC from Ethereum and deposit into the Spark-curated Morpho vault on Base. LI.FI handles the bridge and final deposit in one flow.
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=8453&fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000000'
```
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 1, // Ethereum
toChain: 8453, // Base
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A', // Morpho vault on Base
fromAddress: await signer.getAddress(),
toAddress: await signer.getAddress(),
fromAmount: '1000000000', // 1000 USDC (6 decimals)
slippage: 0.01,
},
});
// Approve USDC on Ethereum
const erc20 = new ethers.Contract(quote.action.fromToken.address, ERC20_ABI, signer);
const allowance = await erc20.allowance(await signer.getAddress(), quote.estimate.approvalAddress);
if (allowance < BigInt(quote.action.fromAmount)) {
const approveTx = await erc20.approve(quote.estimate.approvalAddress, quote.action.fromAmount);
await approveTx.wait();
}
// Send source chain transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
console.log('Source tx confirmed:', tx.hash);
// Poll until cross-chain transfer completes
let status;
do {
const { data } = await axios.get(`${API_URL}/status`, {
params: { txHash: tx.hash, fromChain: 1, toChain: 8453 },
});
status = data;
if (status.status !== 'DONE' && status.status !== 'FAILED') {
await new Promise((r) => setTimeout(r, 5000));
}
} while (status.status !== 'DONE' && status.status !== 'FAILED');
console.log('Final status:', status.status);
```
***
## Felix Vanilla Vaults
[Felix Vanilla](https://www.usefelix.xyz/) vaults are supported for deposit and withdraw.
### Same-Chain: Deposit into Felix Vanilla Vault
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=FELIX_VAULT_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
```
```ts TypeScript theme={"system"}
const quote = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: CHAIN_ID,
toChain: CHAIN_ID,
fromToken: 'SOURCE_TOKEN_ADDRESS',
toToken: 'FELIX_VAULT_TOKEN_ADDRESS', // Felix Vanilla vault token
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: 'AMOUNT_IN_SMALLEST_UNIT',
},
});
```
***
## Neverland Vaults
[Neverland](https://neverland.money/) vaults are supported for deposit and withdraw.
### Same-Chain: Deposit into Neverland Vault
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=NEVERLAND_VAULT_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
```
```ts TypeScript theme={"system"}
const quote = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: CHAIN_ID,
toChain: CHAIN_ID,
fromToken: 'SOURCE_TOKEN_ADDRESS',
toToken: 'NEVERLAND_VAULT_TOKEN_ADDRESS',
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: 'AMOUNT_IN_SMALLEST_UNIT',
},
});
```
***
## Depositing on Behalf of Another Address
Some protocols (such as Aave with `onBehalfOf`) support depositing on behalf of a different wallet. To use this pattern, set `toAddress` to the recipient's address:
```ts TypeScript theme={"system"}
const { data: quote } = await axios.get(`${API_URL}/quote`, {
params: {
fromChain: 8453,
toChain: 8453,
fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
fromAddress: '0xSENDER_ADDRESS',
toAddress: '0xRECIPIENT_ADDRESS', // Vault tokens go to this address
fromAmount: '1000000',
slippage: 0.005,
},
});
```
Not all protocols support deposit-on-behalf. If the target protocol does not support it, the API will return an error or ignore the `toAddress` distinction. Test with your target protocol before relying on this pattern.
***
## Pendle Yield Tokens
[Pendle](https://www.pendle.finance/) enables yield tokenisation. Composer supports deposit and withdraw.
### Same-Chain: Deposit into Pendle
```bash curl theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=PENDLE_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
```
```ts TypeScript theme={"system"}
const quote = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain: CHAIN_ID,
toChain: CHAIN_ID,
fromToken: 'SOURCE_TOKEN_ADDRESS',
toToken: 'PENDLE_TOKEN_ADDRESS', // Pendle yield token
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: 'AMOUNT_IN_SMALLEST_UNIT',
},
});
```
***
## Deposit-Only Protocols
The following protocols support **deposit only** (no withdraw via Composer):
* **[Maple](https://maple.finance/)** — Lending protocol
* **[Ethena](https://ethena.fi/)** — USDe to sUSDe, ENA to sENA conversions
* **[Kinetiq](https://kinetiq.xyz/)** — Staking (see [Staking Recipes](/composer/recipes/staking))
For these protocols, use the same `GET /quote` pattern with the protocol's vault/staking token as `toToken`.
***
## General Pattern
Every vault deposit recipe follows the same pattern:
```
GET /quote
fromChain = source chain ID
toChain = destination chain ID (same or different)
fromToken = token you're starting with
toToken = VAULT TOKEN ADDRESS (this triggers Composer)
fromAmount = amount in smallest unit
fromAddress = your wallet
toAddress = your wallet (receives vault tokens)
```
The **only Composer-specific detail** is that `toToken` must be a vault token address from a [supported protocol](/composer/reference/supported-protocols). Everything else is identical to a standard LI.FI swap or bridge request.
***
## Next Steps
Withdraw from protocol positions via Composer
Bridge + deposit patterns, status polling, and partial failure handling
Full protocol list with example token addresses
Step-by-step REST API integration walkthrough
# API latency and optimization
Source: https://docs.li.fi/guides/latency
API latency and optimization guide
## Where Latency Comes From and How LI.FI Routing Works
LI.FI's routing engine aggregates quotes from multiple bridges and decentralized exchanges (DEXes). This process involves real-time requests to external protocols and tools, including:
* Bridge and DEX aggregators
* On-chain simulations for security and execution checks
* Off-chain services that may introduce their own delays
Latency can vary depending on the number of providers queried, the responsiveness of those providers, and whether on-chain simulations are enabled.
The LI.FI routing flow includes two main components:
1. **Swap Step Resolution** – Fetches quotes from DEXes.
2. **Route Composition** – Combines swaps and bridges to construct optimal cross-chain paths.
Both steps involve third-party systems and introduce potential delays. By default, LI.FI waits a short time for responses before returning the best available result. However, integrators can configure timing strategies to better control this behavior.
***
## Optimizing Response Timing
You can optimize how quickly you receive quotes by customizing:
### Choosing Between `/quote` and `/advanced/routes`
* Use `/quote` for **faster responses**. It returns a single best route. It combines route finding and transaction generation into a single call which cuts down on client to server latency.
* Use `/advanced/routes` to **retrieve multiple route options**. Those calls are quite fast to show results to the user quickly. In order to execute one of the routes a call to `/stepTransaction` is needed to generate the transaction data.
### Disabling Simulation
* By default, responses with transaction data include on-chain simulation checks.
* To improve speed, set the simulation option to `false` . You pass the `skipSimulation` flag as a query parameter to the `/quote` or `/stepTransaction` endpoint:
```json theme={"system"}
/v1/advanced/stepTransaction?skipSimulation=true
/v1/quote?skipSimulation=true&...
```
* **Note**: Disabling simulation reduces verification but improves response time. It is especially recommended when you simulate/gasEstimate the transaction either way in your system.
### Selecting Timing Strategies
LI.FI allows you to control how long it waits for results using timing strategies. Instead of only specify a timeout they are allow more advanced configuration to ensure that results of multiple tools get considered.
Timing strategies are applied in two ways when generating routes:
* `swapStepTimingStrategies`: applied when requesting same chain exchanges
* `routeTimingStrategies`: applied on the full route that can consist of multiple tools (e.g. swap+bridge)
#### Timing Strategy Format
A timing strategies consists of the following properties:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 600,
"startingExpectedResults": 4,
"reduceEveryMs": 300
}
```
* **strategy:** Currently only `minWaitTime` exists
* **minWaitTimeMs:** Minimum time to wait for responses (e.g. 600ms)
* **startingExpectedResults:** Number of expected quotes (e.g. 4)
* **reduceEveryMs:** Frequency of reducing expectations (e.g. every 300ms)
> When this strategy is applied, we give all tool 600ms (minWaitTimeMs) to return a result. If we received 4 or more (startingExpectedResults) results during this time we return those and don't wait for other tools.\
> If less than 4 results are present we wait another 300ms and check if now at least 3 results are present.
#### Passing Strategies in API calls
In `POST /v1/advanced/routes` requests:
```json theme={"system"}
{
...
"options": {
"timing": {
"swapStepTimingStrategies": [
{
"strategy": "minWaitTime",
"minWaitTimeMs": 600,
"startingExpectedResults": 4,
"reduceEveryMs": 300
}
],
"routeTimingStrategies": [
{
"strategy": "minWaitTime",
"minWaitTimeMs": 1500,
"startingExpectedResults": 6,
"reduceEveryMs": 500
}
]
}
}
}
```
In `GET /v1/quote` requests:
```
/v1/quote?...
&swapStepTimingStrategies=minWaitTime-600-4-300
&routeTimingStrategies=minWaitTime-1500-6-500
```
The passed strategies in those examples are the default strategies we apply.
#### Timing Strategy Examples
**Maximize Results**\
Returns the best routes even if it takes longer:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 5,
"reduceEveryMs": 300
}
```
**Balanced Approach**\
Waits a moderate amount of time to return a mix of speed and completeness:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 1,
"reduceEveryMs": 300
}
```
**Fastest Possible Response**\
Returns first available result with no delay:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 0,
"startingExpectedResults": 1,
"reduceEveryMs": 300
}
```
**Time-Limited Return (timeout)**\
Returns any result received within a fixed time limit:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 0,
"reduceEveryMs": 0
}
```
The timing strategies are only used for how long LI.FI will wait for third party providers to respond, the total response time from LI.FI API will be the sum of roundTripTime+parsing+strategies+simulation.
# Chain Overview
Source: https://docs.li.fi/introduction/chains
A list of supported chains
LI.FI offers bridging and swaps between most EVM chains, native Bitcoin, Solana and SUI.
The list of supported chains can also be found on our [API](/api-reference/get-information-about-all-currently-supported-chains).
# Monetizing the integration
Source: https://docs.li.fi/introduction/integrating-lifi/monetizing-integration
As an integrator, you can monetize LI.FI and collect fees from our Widget/SDK/API integration.
Any dApp that integrates LI.FI's Widget, SDK, or our API can now take fees from the volume they put through LI.FI.
## How it works
When using LI.FI Widget or calling our SDK/API to request quotes for a transaction, you can pass a fee parameter specifying the percentage fee you'd be taking from the requested transaction.
### EVM
Fees on EVM chains are forwarded directly to your configured fee wallet at transaction execution time. Fees arrive in your wallet as soon as the user's transaction is confirmed.
See [FeeForwarder](/introduction/integrating-lifi/fee-forwarder) for full details on how this works, including how to withdraw any legacy fees that were collected before this upgrade.
Only the designated fee-collection wallet receives forwarded fees. If the fee-collection wallet is updated, only fees from transactions after the update will be sent to the new wallet.
### Sui
The fees are sent to the fee wallet directly and do not need to be claimed.
### Solana
The fees are sent to the fee wallet directly and do not need to be claimed.
### Bitcoin
The fees are sent to the fee wallet directly and do not need to be claimed.
When collecting fees on Bitcoin, the transaction data must remain unaltered.
This data contains critical transfer and user refund instructions that are
specific to each bridge integration. Modifying the transaction data may lead
to irreversible loss of funds.
## How to set-up fee wallets
To set up your account and to start collecting fees, please set up your integration on [https://portal.li.fi/](https://portal.li.fi/).
Fees are collected on every chain and for every token individually. On all supported chains, fees are sent directly to your configured fee wallet at execution time. You can view your fee activity and balances in the [Partner Portal](https://portal.li.fi/).
## How to set up fee collection
The fee parameter is the percent of the integrator's fee, that is taken from every transaction. The parameter expects a float number e.g 0.02 refers to 2% of the transaction volume. The maximum fee amount should be less than 100%. Also, you should pass your custom integrator string to ensure the fees are collected to the right account. LI.FI will receive a percentage share of the collected fees depending on the use case and volume.
See examples of how to set up fee collection for different environments:
Detailed examples of how to configure fees in the LI.FI Widget, including
simple and advanced configurations.
Learn about how to configure fees and monetize your LI.FI SDK integration.
# Partnership Requests
Source: https://docs.li.fi/introduction/integrating-lifi/partnership-opportunities
Explore partnership opportunities.
## Explore Integration Opportunities
Fill out our [integration form](https://li.fi/contact-us/) to discover how you can partner with us and integrate LI.FI into your platform.
## Official Business Partnership Contacts
For any business partnership discussions, please ensure you are only communicating with the following team members from LI.FI:
**Head of DeFi Sales**
: [@Cerberus0x](https://t.me/andreilifi)
: [andrei@li.finance](mailto:andrei@li.finance)
: @0xCerberus
**Head of Integrations**
: [@jemorla](https://t.me/jemorla)
: [julian@li.finance](mailto:julian@li.finance)
# Universal Market Access for Digital Assets
Source: https://docs.li.fi/introduction/introduction
One Integration. Every Chain. Every Asset. Every Liquidity Source.
LI.FI is the routing and execution layer that connects any application to all on-chain liquidity across chains, bridges, DEXs, solvers, and yield protocols through a single integration.
## Why LI.FI
Blockchain infrastructure is fragmented across:
* Dozens of chains and rollups
* Multiple bridge protocols
* DEX aggregators per ecosystem
* Different token standards (USDC, USDC.e, wrapped assets, native gas tokens)
* Emerging intent and solver networks
* RWAs and hundreds of stablecoins
* Yield opportunities and money markets
* Perps DEXs & Orderbooks
Integrating these systems individually is expensive, brittle, and difficult to maintain. Data management and customer support tooling is painful on top.
**LI.FI abstracts this complexity behind a single integration.**
***
## What LI.FI Provides
LI.FI is the routing and orchestration layer that enables:
* Same-chain swaps
* Cross-chain swaps & bridging
* Cross-chain contract calls
* Multi-step transaction flows (bridge → swap → zap → deposit)
* Finding and buying perps
* Finding and zapping into yield opportunities
All through one unified API and SDK.
***
## How It Works
LI.FI sits between your application and the fragmented liquidity landscape.
Bridges, DEXs, Solvers, Perp Orderbooks, and Yield protocols into a single interface.
Token standards and chain differences so your app never has to handle them directly.
Optimal routes based on price, speed, gas cost, and execution reliability.
Transactions with built-in monitoring and fallback logic to maximise success.
You integrate once. LI.FI handles the routing logic and infrastructure maintenance.
***
## Core Capabilities
Access liquidity across major chains and ecosystems without maintaining individual integrations.
Routes are optimised for best price, gas efficiency, execution success rate, and speed.
If a provider fails or a route becomes invalid, LI.FI automatically re-routes to maximise transaction success.
Canonical token mapping prevents failures from wrapped assets and bridged variants (e.g. USDC vs USDC.e).
Build programmable multi-step flows: swap + bridge, bridge + deposit, cross-chain zaps, and full DeFi workflows.
***
## When to Use LI.FI
LI.FI is built for any application that requires reliable on-chain execution:
* Wallets & Wallet-as-a-Service
* Exchanges & trading desks
* Fintechs, neobanks & Banking-as-a-Service
* DeFi applications
* On-ramp / off-ramp providers
* AI agents executing on-chain
If your users need to move assets across chains or interact with fragmented liquidity, LI.FI removes the infrastructure burden.
***
## Without LI.FI vs. With LI.FI
| | Without LI.FI | With LI.FI |
| ----------------------- | ----------------------------------- | ------------------------------- |
| **Bridges** | Integrate one per chain pair | Covered by a single integration |
| **DEX aggregators** | Integrate one per ecosystem | Included out of the box |
| **Token mapping** | Build and maintain manually | Handled automatically |
| **Failed transactions** | Monitor and recover yourself | Built-in fallback routing |
| **New chain support** | Re-integrate each time | Continuous expansion by LI.FI |
| **Data feeds** | Aggregate and map dozens of sources | Unified data layer |
| **Maintenance** | Ongoing, high overhead | Managed by LI.FI |
| **Time to market** | Weeks to months per integration | Days |
***
## Next Steps
Integrate LI.FI into your application using the JavaScript/TypeScript SDK for full control over routes, execution, and token management.
Explore the LI.FI REST API to fetch quotes, execute cross-chain transfers, and track transaction status directly via HTTP.
Drop in the ready-made swap and bridge widget for an instant, customizable cross-chain UI in your app.
# For Bridges
Source: https://docs.li.fi/introduction/learn-more/for-bridges
Bridge integration requirements
## Overview
Integrating a new bridge involves significant effort, including backend and smart contract implementation, comprehensive testing, and understanding of specific edge cases. Given the high volume of traffic on our platform, we must also perform stress testing and optimize each integration, such as identifying data that can be cached.
Our prioritization for bridge integrations is strictly guided by the needs of our enterprise customers. Despite having over 60 employees, meeting these demands remains a challenge. As such, our capacity to accommodate additional requests is limited by these priorities.
## Strategies to Improve Your Chances of Integration
* **Demonstrate Performance Superiority**:
Provide data showing that your solution outperforms at least one of the top two bridges listed on our [dashboard](https://dune.com/lifi/lifi-bridge-and-dex-aggregation-overview).
* **Align with Existing API Standards**:
Ensure your API closely mirrors the structure of an already integrated bridge, with matching endpoints, inputs, and outputs. This alignment will facilitate a more straightforward integration process, as it would allow us to adapt existing bridge implementation files with minimal changes. A list of implemented bridges can be found here: List: Chains, Bridges, DEX Aggregators, Solvers.
# For DEXs/Aggregators/Solvers
Source: https://docs.li.fi/introduction/learn-more/for-dexs
DEX/Aggregator/Solver integration requirements
## Eligibility for Integration
### As a solver
Solvers need to have the same user experience as DEXs and have similar flow to already integrated DEXs
### As a DEX or DEX aggregator
* **Architectural Limitations**
Currently, we do not support integrations for limit orders, order books, RFQ, or intent execution due to limitations in our backend and smart contract architecture. There is no timeline for adding support for these features.
* **Existing Aggregator Integrations**
If your project is already integrated with other aggregators we support (e.g., 1inch, 0x, Paraswap, DODO, OpenOcean), we are unlikely to integrate as your liquidity source is already a part of our routing.
## DEX Integration Requirements
### UniswapV2 forks
We can efficiently integrate UniswapV2 and V3 forks within a short period of time. However, to proceed, the following information must be provided for each chain on which your DEX operates:
```javascript theme={"system"}
{
name: 'Honeyswap',
chainId: 100,
webUrl: 'https://app.honeyswap.org/',
tokenlistUrl: 'https://tokens.honeyswap.org/',
routerAddress: '0x1C232F01118CB8B424793ae03F870aa7D0ac7f77',
factoryAddress: '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7',
initCodeHash: '0x3f88503e8580ab941773b59034fb4b2a63e86dbc031b3633a925533ad3ed2b93',
baseTokens: [
{
address: '0x71850b7e9ee3f13ab46d67167341e4bdc905eef9',
symbol: 'HNY',
decimals: 18,,
},
...
],
},
```
Your logo:
At least 300x300px and with a white or transparent background.
### Non-UniswapV2 Forks
If your DEX is not a UniswapV2 clone, an API connection is required. Provide documentation and ensure the following technical standards:
* **The ability to handle at least 1 request per second.**
* **You have legible error messages.**
* **Your avg. API response time is \< 1 second**
Furthermore:
* **Maintain a support channel and stay responsive throughout the integration process and beyond.**
* **Stable API without breaking changes, with at least 3 weeks’ notice for updates**
# Getting Integrated by LI.FI
Source: https://docs.li.fi/introduction/learn-more/getting-integrated-by-lifi
Are you a bridge, DEX, aggregator, or solver looking to get added into LI.FI routing?
## Integration Approach and Prioritization
LI.FI is a neutral, multi-chain liquidity aggregator committed to integrating trusted and secure solutions. We frequently receive requests from decentralized exchanges, bridges, aggregators, and solvers for integration. However, each new integration adds complexity and maintenance requirements, which can impact our ability to support additional projects.
Our prioritization strategy is driven by customer demand. We focus on integrating the ecosystems, bridges, and DEXs that our customers request most frequently.
If your project is selected for integration, you will need to meet the following technical requirements for [Bridges](/introduction/learn-more/for-bridges) and [DEXs/Aggregators/Solvers](/introduction/learn-more/for-dexs).
## To get the integration started:
Please get in touch with our [team](/introduction/integrating-lifi/partnership-opportunities) for parnership opportunities.
# Security and Audits
Source: https://docs.li.fi/introduction/learn-more/security-and-audits
Security is fundamental to our operations at LI.FI. Our approach combines multiple layers of defense, independent verification, and complete transparency with our users and partners.
## Security Team
We maintain a dedicated security team specializing in blockchain and DeFi security. This team is augmented by independent security researchers who provide external perspectives on potential vulnerabilities. Our objective is to identify and remediate security issues before they impact our users.
## Web2 Security Testing
We conduct annual penetration testing on our Web2 infrastructure, including APIs, web applications, and backend systems. These assessments are performed by specialized third-party security firms that provide independent evaluation of our security posture.
These tests are conducted annually and cover vulnerability scanning, manual penetration testing, authentication flows, API security, and infrastructure configuration review.
## Web3 Security
### Smart Contract Audits
All smart contracts deployed by LI.FI undergo independent security audits prior to production deployment. This applies to new contracts, upgrades, and material changes—any code going on-chain receives external audit review.
Our policy for Web3 smart contracts is that no code reaches production without independent security review. We engage multiple audit firms as different auditing teams bring varied expertise and methodologies, which strengthens our overall security assurance.
All audit reports are publicly available for review:
**[LI.FI Smart Contract Audit Reports](https://github.com/lifinance/contracts/tree/main/audit/reports)**
We maintain full transparency in our security practices. Users entrusting us with their assets can independently verify our security measures through our public audit disclosures.
### Automated Security Testing
We employ proactive Web3 automated security testing to continuously assess our smart contracts for potential vulnerabilities. Our automated testing infrastructure utilizes Olympix, which provides continuous security analysis and threat detection throughout the development and deployment lifecycle.
### Bug Bounty Program
We maintain an active bug bounty program offering rewards up to **\$1,000,000 USD** for critical vulnerabilities.
Security researchers are invited to participate through our program:
**[LI.FI Bug Bounty (Cantina)](https://cantina.xyz/bounties/260585d8-a3e8-4d70-8077-b6f3f5f0391b)**
The program encompasses smart contract vulnerabilities and other critical security issues. We have found that collaboration with the security research community provides valuable external scrutiny and strengthens our security posture.
### Smart Contract Monitoring
Beyond audits, we employ real-time monitoring of our smart contracts. Our internal monitoring systems track for anomalous patterns and suspicious activity. We also maintain partnerships with firms that provide independent monitoring capabilities—Hexagate being one example—which provides an additional layer of oversight.
Throughout 2024, we have expanded our monitoring infrastructure to include automated threat detection, anomaly detection using baseline behavioral models, transaction analysis for potential exploits, and emergency pause mechanisms for high-risk scenarios.
We continue to enhance our automated response capabilities, including implementing automated pause features that can activate immediately when specific risk thresholds are exceeded.
## Incident Response
We maintain established protocols for security incident management, including defined escalation procedures, communication frameworks for affected stakeholders, and post-incident analysis to implement corrective measures.
## Reporting Security Issues
We encourage responsible disclosure of security vulnerabilities. Security researchers and users who identify potential security issues can contact us through our dedicated channel:
**Security Contact**: [https://help.li.fi/](https://help.li.fi/)
All vulnerabilities may qualify for rewards through our [bug bounty program](https://cantina.xyz/bounties/260585d8-a3e8-4d70-8077-b6f3f5f0391b), with awards up to \$1,000,000 USD. All security reports are reviewed and addressed according to established protocols.
## Standards and Compliance
Our security practices align with recognized industry standards, including smart contract security best practices, OWASP guidelines for web application security, and secure development lifecycle methodologies. Our team receives ongoing security training to maintain current knowledge of evolving threat landscapes.
We maintain two non-negotiable commitments: mandatory independent audits for all smart contract deployments, and public disclosure of all audit reports.
## Our Approach
Security in the DeFi ecosystem requires continuous evolution. As the threat landscape changes, our security measures adapt accordingly. We maintain ongoing evaluation of new security tools, enhanced processes, and improved defensive capabilities.
Our security philosophy centers on defense in depth combined with operational transparency. We employ multiple layers of security controls, independent verification mechanisms, and public disclosure of security practices. This approach forms the foundation of trust with our users and partners.
***
*Last Updated: October 2025*
# Bitcoin Providers
Source: https://docs.li.fi/introduction/lifi-architecture/bitcoin-overview
Bitcoin Architecture
LI.FI offers seamless native Bitcoin bridging and swaps between native Bitcoin, major EVM chains and Solana.
## Requesting a quote
A quote for Bitcoin can be requested using the same endpoints as EVM. The only difference will be the transaction data when source chain is Bitcoin.
`fromAddress` when source chain is Bitcoin supports flexible formats for maximum convenience:
* **Single Bitcoin address**: `bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh`
* **Multiple addresses** (semicolon-separated): `address1;address2;address3`
* **Extended public key (xpub)**: `xpub6CUG...`
* **Multiple xpubs**: `xpub1,xpub2`
* **Combination of xpubs and addresses**: `xpub1;address1;address2`
**UTXO Collection**: The system builds transaction inputs by collecting UTXOs from:
* Provided addresses directly, OR
* Addresses derived from the provided xpub(s) that contain UTXOs
The `fromAddress` must have enough UTXOs to cover the requested transaction amount, otherwise no quote will be returned. UTXOs are checked and combined in the most efficient way to build the transaction.
**Transaction Signing**: All input addresses (wallets) that contribute UTXOs to the transaction must sign the PSBT. This ensures that funds from each participating address are properly authorized.
**Refund Address**: The third output in the PSBT is a refund/change output that will be returned to the address that contributed the most significant input to the transaction.
## Executing a transaction
### Transaction data
After retrieving the quote, the funds need to be sent to the BTC vault address provided in the response, along with a memo.
* Memo Functionality: Similar to Thorchain, LI.FI uses memos for BTC to EVM swaps. The memo in the BTC transaction specifies the swap's destination address and chain.
* Transaction Handling: The transaction that leaves BTC and goes to EVM needs to be sent to an EVM address. The memo ensures that the swap details are correctly processed by the validators.
NOTE: Only send transactions in a timely manner (\~30min). It is always
recommended to request an up-to-date quote to ensure to get the latest
information.
**Risk of modifying Bitcoin transaction data**
Modifying PSBT or raw Bitcoin transaction data received from our API (for
example removing outputs, changing amounts, or editing opcodes/scripts) can
invalidate signatures or spending conditions and lead to irreversible loss of
funds.
Do not alter PSBTs unless you are an expert and have explicitly confirmed with
us the modification you intend to make.
`data` in transactionRequest object is PSBT (partially signed bitcoin
transaction) and memo needs to be retrieved from PSBT by decoding it.
### Retrieving memo from PSBT
PSBT can be decoded using any library like [bitcoinjs](https://github.com/bitcoinjs/bitcoinjs-lib) or [scure-btc-signer](https://github.com/paulmillr/scure-btc-signer).
Here's an example using `bitcoinjs`
```typescript theme={"system"}
const psbtHex = transactionRequest.data;
// Create PSBT object from hex data
const psbt = Psbt.fromHex(psbtHex, { network: networks.bitcoin });
// Find OP_RETURN output in the transaction outputs
const opReturnOutput = psbt.txOutputs.find((output) => {
if (output?.script) {
// Convert the output script to hex string for checking
const scriptHex = Array.from(output.script)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
// Check if script starts with OP_RETURN opcode (0x6a)
return scriptHex.startsWith("6a");
}
return false;
});
// If an OP_RETURN output exists, decode its script data as UTF-8 text (memo)
const memo = opReturnOutput?.script
? new TextDecoder().decode(
new Uint8Array(Object.values(opReturnOutput.script))
)
: undefined;
```
# Smart Contract Addresses
Source: https://docs.li.fi/introduction/lifi-architecture/smart-contract-addresses
The LI.FI Diamond entryway contract address is `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` on most supported networks. Please note, on some networks the address is different. You can find the ABI on [Github](https://github.com/lifinance/lifi-contract-types/blob/main/dist/diamond.json).
More information and open-source code for each facet contract can be found on our [GitHub](https://github.com/lifinance/contracts/blob/main/docs/README.md).
Contract address of each facet and deployment information can be found in our [GitHub repo](https://github.com/lifinance/contracts/tree/main/deployments).
# Smart Contract Architecture
Source: https://docs.li.fi/introduction/lifi-architecture/smart-contract-overview
## Architecture
The LI.FI Contract is built using the **EIP-2535** (Multi-facet Proxy) standard. The contract logic lives behind a single contract that in turn uses `DELEGATECALL` to call facet contracts that contain the business logic.
All business logic is built using facet contracts that live in `src/Facets`.
For more information on EIP-2535 you can view the entire EIP [here](https://eips.ethereum.org/EIPS/eip-2535).
## Contract Flow
A basic example would be a user bridging from one chain to another using Stargate Protocol. The user would interact with the LI.FIDiamond contract which would pass the Stargate-specific call to the StargateV2Facet which then passes required calls + parameters to Stargate's contracts.
The basic flow is illustrated below.
## Diamond Helper Contracts
The LI.FI Diamond contract is deployed along with some helper contracts that facilitate things like upgrading facet contracts, look-ups for methods on facet contracts, ownership checking and withdrawals of funds. For specific details please check out [EIP-2535](https://eips.ethereum.org/EIPS/eip-2535).
# Solana Providers
Source: https://docs.li.fi/introduction/lifi-architecture/solana-overview
## Introduction
LI.FI offers seamless integration with the Solana blockchain through multiple bridges and exchanges.
We only support single step transactions per ecosystem at this point. Currently expanding to support two step transactions across the two ecosystems.
The native SOL is represented using the System Program address `11111111111111111111111111111111` when making requests to the backend. Native Solana doesn’t really have an address, so this is a representation specific to our system.
Wrapped Solana should use the wSOL address `So11111111111111111111111111111111111111112`
Solana chainID in LI.FI BE is `1151111081099710`.
## Mayan integration
Enables users to perform blue chip coins transfer between Solana and EVM chains supported by Mayan. This integration also supports the native USDC bridge CCTP.
Mayan is split into three different keys by provider:
`mayan` - Swift
`mayanMCTP` - CCTP
`mayanWH` - Wormhole
***
## AllBridge integration
Allbridge enables users to perform cost efficient stable coin transfers between Solana (USDC) and other supported Ethereum Virtual Machine (EVM) compatible chains, including ETH, POL, BSC (only USDT), OPT, AVA, ARB, and BAS.
## Jupiter integration
Enables users to perform Solana swaps of wide range of tokens.
Only Jupiter verified tokens are supported by LI.FI.
## Architectural differences with EVM
Solana works the same way in the LI.FI BE as any other chain so quote requests are the same except for token addresses and chainID.
Onchain tx submission is different from EVM because of fundamental differences between EVM and SVM. More detailed information and tx examples can be found on the [solana example page](/introduction/user-flows-and-examples/solana-tx-execution).
# Sui Providers
Source: https://docs.li.fi/introduction/lifi-architecture/sui-overview
Sui Architecture
LI.FI offers seamless same chain swaps on SUI and bridging between SUI and major EVM chains and Solana.
# System Overview
Source: https://docs.li.fi/introduction/lifi-architecture/system-overview
LI.FI architectural overview
## Introduction
LI.FI is a multi-chain liquidity aggregation platform that connects decentralized applications (dApps) with various liquidity sources, including bridges, decentralized exchanges (DEXs), and solvers. This architecture enables seamless cross-chain and same-chain trading by facilitating price discovery, smart order routing, and efficient execution.
***
## Key Components of LI.FI Architecture
### 1. **dApp Interface (Integrators)**
* **Function**: What the end-user interacts with. dApps initiate quote requests and route selection to LI.FI's API.
* **Process**: A user sends a request from the dApp for the best trading route or quote, which is forwarded to LI.FI API for processing. Once the optimal route is selected, the dApp submits a transaction to execute the trade.
### 2. **LI.FI API - Aggregation and Routing Layer**
* **Purpose**: This off-chain layer performs core price discovery and smart order routing by interfacing with various liquidity sources.
* **Functionality**:
* **Fetch Pricing**: LI.FI API retrieves quotes from multiple sources, including bridges, DEXs, and solvers, to determine the best price and route.
* **Return Quote**: Once the optimal route is identified, LI.FI API returns the quote to the dApp.
* **Order Routing**: The dApp submits the transaction with the selected route, which LI.FI API processes by routing it to the LI.FI Diamond Contract on-chain.
### 3. **LI.FI Diamond Contract**
* **Function**: Acts as the primary on-chain entry point, handling the execution of transactions based on the chosen route from LI.FI API.
* **Role**:
* Routes the transaction to the appropriate **facet contract** (bridge, DEX, or solver) based on the chosen liquidity source.
* Acts as the router for on-chain executions, allowing modular connections to different liquidity sources.
### 4. **LI.FI Facet Contracts**
Specialized on-chain contracts that interface with respective liquidity sources:
* **Bridge Facet Contracts**
* Route transactions to specific bridge contracts (e.g., Bridge A or Bridge B) for cross-chain transfers.
* Ensure bridge compatibility and secure asset transfer across blockchains.
* **DEX Facet Contracts**
* Route transactions to specific DEX contracts for same-chain swaps.
* Optimize execution based on DEX-specific parameters for efficient liquidity utilization.
* **Solver Facet Contracts**
* Route transactions to solver contracts for accessing extended liquidity sources.
* Enable advanced routing and pricing calculations based on solver protocols.
### 5. **Bridge/DEX/Solver Contracts**
Final execution of trades occurs on the blockchain network through interactions with selected liquidity providers (bridge, DEX, or solver contracts).
***
## End-to-End Order Flow
1. **Initiate Request**
* A user initiates a quote request from the dApp for a multi-chain or same-chain trade.
2. **Quote and Route Discovery**
* LI.FI BE receives the request and queries bridges, DEX aggregators, and solvers to gather pricing data and route options.
* The optimal route is determined and returned to the dApp.
3. **Transaction Submission**
* The user selects the best route in the dApp, which submits the transaction to LI.FI Diamond contract with the selected quote data.
4. **On-Chain Execution**
* LI.FI Diamond contract forwards the transaction to the respective LI.FI Facet contract for execution.
* The facet contract executes the transaction with the specific bridge, DEX, or solver on-chain contract, finalizing the trade or transfer.
5. **Output Return**
* Once the transaction is complete, the resulting assets are returned to the user.
# Product Stack
Source: https://docs.li.fi/introduction/product-stack
The layered architecture behind LI.FI's universal market access.
LI.FI is built in layers. Each layer does a distinct job, and together they make it possible to move, swap, stake, and deploy assets across any chain in a single step.
Plug-and-play swap and bridge interfaces any app can embed. Works out of the box, and fully customisable to match your product, from layout and branding to wallet handling. Compatible with React, Next.js, Vue, Svelte, and any modern web stack.
[Explore the Widget →](/widget/overview)
Knows where assets are, what they cost, and what yields are available across every supported chain. Finds the best route to execute any action and combines multiple steps into a single transaction where possible.
Powered by [LI.FI Composer](/composer/overview). Goes beyond swapping and bridging. Users can deposit into yield vaults, stake into protocols, and chain together multiple actions across chains, all in one transaction.
Most comprehensive meta-level aggregation in the market. Connects to dozens of DEXs and bridges across every major chain. Users get the best available price without needing to know which protocol powers it. Edge cases like wrapped token variants and non-EVM differences are handled quietly in the background.
A competitive network of solvers and market makers who bid to fill user trades. More competition means better prices and faster execution. LI.FI's own aggregation layer fills any gaps they leave. Solvers and market makers can plug in with their own liquidity and tap into LI.FI's order flow.
[Sign up as a Solver →](https://intents.li.fi/)
LI.FI's own solver. Ensures trades always execute, even when third-party solvers can't fill. Also helps enterprises launch and distribute tokenised assets (RWAs, stablecoins) across LI.FI's network at scale.
A DEX aggregator built for new and emerging chains. Gives users best-price swaps across every DEX on a chain from day one, and gives new chains instant access to competitive liquidity, including from private market makers.
# Solana Ecosystem Coverage
Source: https://docs.li.fi/introduction/solana-ecosystem
Overview of LI.FI's Solana ecosystem coverage including bridges, DEXs, and key features
LI.FI offers comprehensive Solana support with 12 integrated bridges, 4 meta-aggregator DEX integrations, and seamless interoperability between Solana and 40+ chains.
For technical implementation details, see the [Solana Providers](/introduction/lifi-architecture/solana-overview) page. For transaction examples, see [Solana Transaction Example](/introduction/user-flows-and-examples/solana-tx-execution).
***
## Bridges
LI.FI integrates 12 bridges for Solana connectivity:
Near Intents, Mayan Swift, Mayan CCTP, Mayan fastMCTP, MayanWH, Allbridge, Relay, Gaszip, Across V4, Glacis, Chainflip, Unit
* **Glacis (LI.FI exclusive)**: Interop token mint/burn aggregator with 1:1 native bridging (OFT, NTT, etc.)
* **Intent-based bridges**: Mayan Swift, Across, Relay, Gaszip, Near Intents
* **Unit**: Native Solana token deposits with abstracted deposit addresses
***
## DEXs
LI.FI aggregates liquidity across 4 major Solana meta-aggregators:
Jupiter, DFlow, Titan, OKX
This provides coverage of every major underlying DEX including Raydium, Orca, Meteora, Phoenix, Lifinity, and PropAMMs, ensuring best pricing across all available liquidity sources.
***
## Key Features
### Gasless Transactions
LI.FI supports gasless Solana transactions through `svmSponsor`:
* **Swaps**: Fully sponsored, no SOL needed
* **Bridges (no account creation)**: Sponsored for Gaszip, Unit, and Across
* **Bridges (with account creation)**: Coming soon
### Relayer Costs in Input Token
Users can pay relayer fees using their input token, removing the need to hold native SOL for relay costs.
### Refuel
LI.Fuel via Allbridge enables users to receive destination gas tokens as part of a bridge transaction.
### Destination Swaps
Destination swaps are available through select bridges, enabling one-click bridge + swap flows.
### Jito Bundles
Jito Bundle functionality is available for partners, enabling more complex swap combinations with zero dust.
***
## Major Integrators
Phantom, Kamino Finance, MetaMask, Binance Wallet, Titan, BackPack, KuCoin, AliPay, Robinhood, Crypto.com, OneKey, Hyperliquid
***
## Integration Options
LI.FI's Solana support is available through all standard integration methods:
* [API](/api-reference)
* [SDK](/sdk/overview)
* [Widget](/widget/overview)
# EVM Providers
Source: https://docs.li.fi/introduction/tools
A list of providers/tools LI.FI aggregates
The list of supported tools can also be found on our [API](/api-reference/get-available-bridges-and-exchanges).
# TRON Ecosystem Coverage
Source: https://docs.li.fi/introduction/tron-ecosystem
Overview of LI.FI's TRON ecosystem coverage including bridges and cross-chain connectivity
LI.FI offers TRON support with 4 integrated bridges and seamless interoperability between TRON and 65 chains.
For the full list of supported chains, see [Chain Overview](/introduction/chains). For bridge and exchange tooling, see [Tools](/introduction/tools).
***
## Bridges
LI.FI integrates 4 bridges for TRON connectivity:
GasZip, Symbiosis, Near Intents, Allbridge
* **GasZip**: Intent-based bridging to 60 chains including major EVM networks and Solana
* **Symbiosis**: Cross-chain liquidity protocol connecting TRON to 22 chains across EVM ecosystems
* **Near Intents**: Intent-based routing between TRON and 13 chains including Ethereum, Solana, Bitcoin, and major L2s
* **Allbridge**: Multi-chain bridge connecting TRON to 12 chains including Ethereum, Solana, Sui, and major L2s
***
## DEXs
LI.FI does not currently list native TRON DEX aggregators in the tools API. On-chain swap routing on TRON is primarily available through cross-chain bridge flows.
***
## Key Features
### Cross-Chain Bridging
TRON is supported as a first-class TVM chain (chain ID `728126428`) with routing through GasZip, Symbiosis, Near Intents, and Allbridge.
### Intent-Based Routing
Near Intents enables intent-based quotes between TRON and major ecosystems including Ethereum, Solana, Bitcoin, and popular L2s.
### Broad Chain Connectivity
Bridge integrations connect TRON to 65 unique destination and source chains across EVM, Solana, Bitcoin, and Sui.
***
## Availability
TRON support is live on the LI.FI API and [Jumper](https://jumper.exchange).
***
## Integration Options
LI.FI's TRON support is available through all standard integration methods:
* [API](/api-reference)
* [SDK](/sdk/overview)
* [Widget](/widget/overview)
# Bitcoin Transaction Example
Source: https://docs.li.fi/introduction/user-flows-and-examples/bitcoin-tx-example
## Requesting Bitcoin-specific information via the API
### Chains
```JS theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/chains?chainTypes=UTXO' \
--header 'accept: application/json'
```
### Tools
```JS theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/tools?chains=20000000000001' \
--header 'accept: application/json'
```
### Tokens
```JS theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/tokens?chains=BTC' \
--header 'accept: application/json'
```
### Token details
```JS theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/token?chain=20000000000001&token=bitcoin' \
--header 'accept: application/json'
```
## Requesting a Quote
### Bitcoin to Ethereum
The quote and advanced route calls target the same transfer but differ in shape: `GET /quote` accepts query parameters such as `fromChain` and `fromToken`, whereas `POST /advanced/routes` expects a JSON body with fields like `fromChainId` and `fromTokenAddress`.
```javascript /quote theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/quote?fromAddress=bc1qmdpxhzarlxrygtvlxrkkl0eqguszkzqdgg4py5&fromAmount=500000&fromChain=BTC&fromToken=bitcoin&toAddress=0x39333638696578786b61393361726b63717a6773&toChain=1&toToken=0x0000000000000000000000000000000000000000' \
--header 'accept: application/json'
```
```javascript /advanced/routes theme={"system"}
curl --request POST \
--url https://li.quest/v1/advanced/routes \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"toTokenAddress": "0x0000000000000000000000000000000000000000",
"fromTokenAddress": "bitcoin",
"fromChainId": 20000000000001,
"fromAmount": "10000000",
"toChainId": 1,
"fromAddress": "YOUR_BTC_WALLET",
"toAddress": "YOUR_EVM_WALLET"
}
'
```
### Ethereum to Bitcoin
```javascript /quote theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/quote?fromChain=1&toChain=20000000000001&fromToken=0x0000000000000000000000000000000000000000&toToken=bitcoin&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&toAddress=bc1qmdpxhzarlxrygtvlxrkkl0eqguszkzqdgg4py5&fromAmount=500000000000000000' \
--header 'accept: application/json'
```
```javascript /advanced/routes theme={"system"}
curl --request POST \
--url https://li.quest/v1/advanced/routes \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"toTokenAddress": "bitcoin",
"fromTokenAddress": "0x0000000000000000000000000000000000000000",
"fromChainId": 1,
"fromAmount": "500000000000000000",
"toChainId": 20000000000001,
"fromAddress": "YOUR_EVM_WALLET",
"toAddress": "YOUR_BTC_WALLET"
}
'
```
## Executing the transaction
### Building Custom Bitcoin Transactions
Partners may want to build custom Bitcoin transactions to select specific UTXOs for various reasons such as coin control, UTXO consolidation, or fee optimization.
**Requirements when building custom transactions:**
* Preserve the exact output structure and order from the API response
* Ensure selected UTXOs have sufficient value to cover:
* Bridge deposit amount (1st output)
* Refund output if required by the bridge (3rd output, must be above dust threshold)
* Integrator fees (additional outputs)
**Critical:** The output order and structure cannot be modified. Deviating from the API response structure will result in stuck or failed transactions.
### Transaction Data
**BTC to Ethereum, Avalanche, or BNB Smart Chain (BSC)**
After retrieving the quote, the funds need to be sent to the BTC vault address provided in the response, along with a memo.
**Memo Functionality:** Similar to Thorchain, LI.FI uses memos for BTC to EVM swaps. Depending on the tool, the memo in the BTC transaction specifies the bridge-specific tx data to execute and internal LI.FI details for the tracking.
**Transaction Handling:** The transaction that leaves BTC and goes to EVM needs to be sent to an EVM address. The memo ensures that the swap details are correctly processed by the validators.
NOTE: Only send transactions in a timely manner (\~30 min). It is always
recommended to request an up-to-date quote to ensure to get the latest
information.
**Risk of modifying Bitcoin transaction data**
Modifying PSBT or raw Bitcoin transaction data received from our API (for
example removing outputs, changing amounts, or editing opcodes/scripts) can
invalidate signatures or spending conditions and lead to irreversible loss of
funds.
Do not alter PSBTs unless you are an expert and have explicitly confirmed with
us the modification you intend to make.
The following is an example of transaction data
```JS theme={"system"}
"transactionRequest": {
"to": "bc1qawcdxplxprc64fh38ryy4crndmfgwrffpac743", //thorswap vault to send BTC to
"data": "=:ETH.USDC:0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7::lifi:0|0x4977d81c2a5d6bd8",
"value": "500000"
}
```
### Extracting Transaction Data from PSBT
When building custom Bitcoin transactions with selected UTXOs, partners need to extract the memo and other transaction details from the PSBT (Partially Signed Bitcoin Transaction) returned by the API.
**Key Information:**
* The memo is contained in the **OP\_RETURN output** (2nd output) of the PSBT
* This memo must be preserved exactly as provided - it contains bridge-specific calldata and LI.FI tracking details
* The depositor address is in the 1st output
* The refund address (if required) is in the 3rd output
Partners can parse the PSBT to extract these values and reconstruct the transaction with their selected UTXOs while maintaining the exact output structure.
### Bitcoin transaction requirements per tool
**Critical: Output Structure Cannot Be Modified**
The output order and structure provided by the API must be replicated exactly. Changing the order, removing outputs, or modifying amounts will result in stuck transactions. Partners building custom transactions must preserve the exact structure while only changing the input UTXOs.
The general requirements for the outputs structure are the following:
* **1st output:** Bridged amount sent to the bridge depositor address
* **2nd output:** OP\_RETURN containing the memo with bridge-specific and LI.FI tracking details (must be preserved exactly)
* **3rd output:** Refund output back to sender's address (optional for some bridges, mandatory for others - see details below)
* **Remaining outputs:** Integrator-specific fee transfers
**Dust Threshold Requirements:**
All outputs containing value must exceed the dust threshold, which is determined by the output address type:
* Pay To Witness Public Key Hash (p2wpkh) - 294 sats
* Pay To Witness Script Hash (p2wsh) - 330 sats
* Pay To Script Hash (p2sh) - 540 sats
* Pay To Public Key Hash (p2pkh) - 546 sats
* Pay To Taproot (p2tr) - 330 sats
#### Bridge Requirements Summary
##### **Thorswap**
The memo (OP\_RETURN output) contains Thorswap calldata to be executed and LI.FI tracking id. It's important to keep both to avoid stuck or failed transactions.
```JS theme={"system"}
// Memo example
=:ETH.USDC:0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7::lifi:0|0x4977d81c2a5d6bd8
```
##### **Unit**
Unit bridge doesn't have any bridge-specific details stored in memo so it includes only LI.FI tracking details.
```JS theme={"system"}
// Memo example
// =|lifi02bf57fe
```
##### **Symbiosis**
Symbiosis bridge doesn't have any bridge-specific details stored in memo so it includes only LI.FI tracking details.
```JS theme={"system"}
// Memo example
=|lifi02bf57fe
```
Incorrect transaction structure will cause stuck transfers that require manual refund intervention. Ensure the output order matches the API response exactly.
##### **Relay**
The memo (OP\_RETURN output) contains Relay calldata to be executed and LI.FI tracking id. It's important to keep both to avoid stuck or failed transactions.
```JS theme={"system"}
// Memo example
0x986c2efd25b8887e9c187cfe2162753567339b6313e7137b749e83d4a1a79b03=|lifi92c9cbbc5
```
##### **Chainflip**
Chainflip PSBT requires to have three outputs as described above. The refund output is required, if it's skipped, the transaction will not be correctly processed.
The memo (OP\_RETURN output) contains Chainflip payload to be executed and LI.FI tracking id. It's important to keep both to avoid stuck or failed transactions.
```JS theme={"system"}
// Memo example
0x01071eb6638de8c571c787d7bc24f98bfa735425731c6400f4c5ef05000000000000000000000000ff010002001e0200=|lifi92c9cbbc5
```
**CRITICAL: Chainflip Fund Loss Risk**
Chainflip transactions with incorrect output structure will result in **permanent, unrecoverable loss of funds**. Unlike other bridges, Chainflip cannot manually refund stuck transactions.
**Mandatory Requirements:**
* All three outputs must be present in exact order: (1) deposit amount, (2) OP\_RETURN memo, (3) refund output
* Refund output (3rd output) is mandatory and must be above dust threshold
* Do not modify, remove, or reorder any outputs from the API response
Failure to follow these requirements exactly will result in irreversible fund loss with no recovery option.
# Quote vs Route
Source: https://docs.li.fi/introduction/user-flows-and-examples/difference-between-quote-and-route
Difference between /quote and /advanced/routes
## /quote
/quote endpoint returns **the best single-step route only**. So only one route is returned and it includes transaction data that is needed to be sent onchain to execute the route.
## /advanced/routes
The `/advanced/routes` endpoint allows more complex routes, in which the user needs to bridge funds first and then needs to trigger a second transaction on the destination chain to swap into the desired asset.
After retrieving the routes, the tx data needs to be generated and retrieved using the `/advanced/stepTransaction` endpoint. This endpoint expects a full Step object which usually is retrieved by calling the `/advanced/routes` endpoint and selecting the most suitable Route.
The `/advanced/stepTransaction` endpoint needs to be called to retrieve transaction data for every Step. Internally both endpoints use the same routing algorithm, but with the described different settings.
The `/advanced/routes` endpoint can return single-step routes only by using the `allowChainSwitch: false` parameter in the request.
# End-to-end Transaction Example
Source: https://docs.li.fi/introduction/user-flows-and-examples/end-to-end-example
**Want to go beyond swaps and bridges?** With [Composer](/composer/overview), you can deposit into vaults, stake, and lend — all in a single transaction using the same API pattern shown below. See the [Composer Quickstart](/composer/quickstart).
## Step by step
```ts TypeScript theme={"system"}
const getQuote = async (fromChain, toChain, fromToken, toToken, fromAmount, fromAddress) => {
const result = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
}
});
return result.data;
}
const fromChain = 42161;
const fromToken = 'USDC';
const toChain = 100;
const toToken = 'USDC';
const fromAmount = '1000000';
const fromAddress = YOUR_WALLET_ADDRESS;
const quote = await getQuote(fromChain, toChain, fromToken, toToken, fromAmount, fromAddress);
```
This step is only needed if `/advanced/routes` endpoint was used. `/quote` already returns the transaction data within the response. Difference between `/quote` and `/advanced/routes` is described [here](/introduction/user-flows-and-examples/difference-between-quote-and-route)
Before any transaction can be sent, it must be made sure that the user is allowed to send the requested amount from the wallet. This example uses the classic `approve()` approach. To reduce approval transactions using off-chain EIP-712 signatures, see the [Permit & Permit2 Approval Flow](/introduction/user-flows-and-examples/permit2-approval-flow).
```ts TypeScript theme={"system"}
const { Contract } = require('ethers');
const ERC20_ABI = [
{
"name": "approve",
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"name": "allowance",
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
];
// Get the current allowance and update it if needed
const checkAndSetAllowance = async (wallet, tokenAddress, approvalAddress, amount) => {
// Transactions with the native token don't need approval
if (tokenAddress === ethers.constants.AddressZero) {
return
}
const erc20 = new Contract(tokenAddress, ERC20_ABI, wallet);
const allowance = await erc20.allowance(await wallet.getAddress(), approvalAddress);
if (allowance.lt(amount)) {
const approveTx = await erc20.approve(approvalAddress, amount);
await approveTx.wait();
}
}
await checkAndSetAllowance(wallet, quote.action.fromToken.address, quote.estimate.approvalAddress, fromAmount);
```
After receiving a quote, the transaction has to be sent to trigger the transfer.
Firstly, the wallet has to be configured. The following example connects your wallet to the Gnosis Chain.
```ts TypeScript theme={"system"}
const provider = new ethers.providers.JsonRpcProvider('https://rpc.xdaichain.com/', 100);
const wallet = ethers.Wallet.fromMnemonic(YOUR_PERSONAL_MNEMONIC).connect(
provider
);
```
Afterward, the transaction can be sent using the `transactionRequest` inside the previously retrieved quote:
```ts TypeScript theme={"system"}
const tx = await wallet.sendTransaction(quote.transactionRequest);
await tx.wait();
```
If two-step route was used, the second step has to be executed after the first step is complete. Fetch the status of the first step like described in next step and then request transactionData from the `/advanced/stepTransaction` endpoint.
To check if the token was successfully sent to the receiving chain, the /status endpoint can be called:
```ts TypeScript theme={"system"}
const getStatus = async (bridge, fromChain, toChain, txHash) => {
const result = await axios.get('https://li.quest/v1/status', {
params: {
bridge,
fromChain,
toChain,
txHash,
}
});
return result.data;
}
result = await getStatus(quote.tool, fromChain, toChain, tx.hash);
```
## Full example
```ts TypeScript theme={"system"}
const ethers = require('ethers');
const axios = require('axios');
const API_URL = 'https://li.quest/v1';
// Get a quote for your desired transfer
const getQuote = async (fromChain, toChain, fromToken, toToken, fromAmount, fromAddress) => {
const result = await axios.get(`${API_URL}/quote`, {
params: {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
}
});
return result.data;
}
// Check the status of your transfer
const getStatus = async (bridge, fromChain, toChain, txHash) => {
const result = await axios.get(`${API_URL}/status`, {
params: {
bridge,
fromChain,
toChain,
txHash,
}
});
return result.data;
}
const fromChain = 42161;
const fromToken = 'USDC';
const toChain = 100;
const toToken = 'USDC';
const fromAmount = '1000000';
const fromAddress = YOUR_WALLET_ADDRESS;
// Set up your wallet
const provider = new ethers.providers.JsonRpcProvider('https://rpc.xdaichain.com/', 100);
const wallet = ethers.Wallet.fromMnemonic(YOUR_PERSONAL_MNEMONIC).connect(
provider
);
const run = async () => {
const quote = await getQuote(fromChain, toChain, fromToken, toToken, fromAmount, fromAddress);
const tx = await wallet.sendTransaction(quote.transactionRequest);
await tx.wait();
// Only needed for cross chain transfers
if (fromChain !== toChain) {
let result;
do {
result = await getStatus(quote.tool, fromChain, toChain, tx.hash);
} while (result.status !== 'DONE' && result.status !== 'FAILED')
}
}
run().then(() => {
console.log('DONE!')
});
```
# LI.FI Composer
Source: https://docs.li.fi/introduction/user-flows-and-examples/lifi-composer
One-click DeFi operations across any chain — bundle swaps, bridges, deposits, and staking into a single transaction.
**This page has moved.** Composer now has its own dedicated documentation section with quickstarts, integration guides, recipes, and a full reference.
What is Composer, why use it, and supported protocols
Execute your first Composer transaction in under 5 minutes
Architecture deep-dive: Onchain VM, eDSL, and transaction lifecycle
Step-by-step guide to integrating Composer via the REST API
# Messaging flow
Source: https://docs.li.fi/introduction/user-flows-and-examples/messaging-flow
LI.FI Messaging flow Documentation
# LI.FI Messaging Flow Documentation
## Overview
LI.FI Messaging Flow enables seamless interactions with centralized and hybrid exchanges that use message-based APIs (such as Hyperliquid) instead of traditional on-chain transactions. **This flow delivers gasless, approval-free operations for cross-chain transfers involving protocols that operate with off-chain signed messages.**
Traditional DeFi operations require users to send on-chain transactions, manage gas fees, and approve token spending for each interaction. Messaging flow eliminates these friction points by using off-chain signed messages (EIP-712) that are relayed to destination protocols through LI.FI's backend infrastructure.
## Key Benefits of Messaging Flow
* **No Token Approvals Required**: Unlike transaction-based flows, messaging flow doesn't require users to approve token spending
* **Gasless Operations**: Users sign messages off-chain without paying gas fees for the message itself (some operation might require fee payments, not gas)
* **Asynchronous Execution**: Messages are relayed and processed asynchronously, with status tracking via `taskId`
* **Seamless Integration**: Works out-of-the-box with LI.FI API, SDK, and Widget
***
## How Messaging Flow Works
The messaging flow operates through a multi-step process that replaces traditional on-chain transactions with off-chain signed messages:
1. **Quote/Route Generation**: User requests a quote or route with `executionType=message` (will generate ONLY message-based routes) or `executionType=all` (both transaction and messages options)
2. **Message Creation**: LI.FI generates an EIP-712 typed message containing the operation details
3. **User Signature**: User signs the message off-chain in their wallet (no gas required)
4. **Message Relay**: Signed message is submitted to LI.FI's `/v1/advanced/relay` endpoint
5. **Backend Processing**: LI.FI backend validates and forwards the message to the destination protocol (e.g., Hyperliquid)
6. **Task Tracking**: Backend returns a `taskId` for tracking the asynchronous operation
7. **Status Monitoring**: Status can be checked via `/v1/status` endpoint using the `taskId` parameter
### Flow Diagram
```
User Wallet → Sign EIP-712 Message (off-chain, no gas)
↓
LI.FI SDK/API → POST /v1/advanced/relay
↓
LI.FI Backend → Validates & relays to protocol
↓
Returns taskId → Track via GET /v1/status?taskId=...
↓
Protocol Execution → (e.g., Hyperliquid withdrawal)
```
***
## Key Differences from Transaction Flow
| Aspect | Transaction Flow | Messaging Flow |
| ------------------- | ---------------------------------- | ----------------------------------- |
| **Execution Type** | On-chain transaction | Off-chain signed message |
| **Gas Fees** | User pays gas for each transaction | No gas for signing messages |
| **Token Approvals** | Required (separate transaction) | Not required (`skipApproval: true`) |
| **Status Tracking** | `txHash` | `taskId` |
| **User Action** | Send transaction | Sign typed message |
### Important Parameters
* **`estimate.skipApproval`**: Automatically set to `true` for messaging flows, indicating no approval transaction is needed
* **`estimate.executionType`**: Set to `"message"` to identify steps that use messaging flow
* **`typedData`**: Contains the EIP-712 message structure that users need to sign
***
## The executionType Parameter
The `executionType` parameter controls which types of routes are returned by the LI.FI API. This optional parameter is available in:
* `GET /v1/quote`
* `POST /v1/advanced/routes`
### Values
* **`transaction`** (default): Returns only routes using traditional on-chain transactions, **excluding** messaging flow routes
* **`message`**: Returns only routes that use messaging flow
* **`all`**: Returns both transaction-based and message-based routes
### Example Usage
**Get only message-based routes:**
```bash theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1337&toChain=999&fromToken=0x8F254b963e8468305d409b33aA137C6700000000&toToken=0x9FDBdA0A5e284c32744D2f17Ee5c74B284993463&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&fromAmount=1000000&executionType=message'
```
**Get all available routes (both types):**
```bash theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1337&toChain=999&fromToken=0x8F254b963e8468305d409b33aA137C6700000000&toToken=0x9FDBdA0A5e284c32744D2f17Ee5c74B284993463&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&fromAmount=1000000&executionType=all'
```
***
## The /relay Endpoint
### POST /v1/advanced/relay
**Purpose**: Submit signed EIP-712 messages for relaying to destination protocols.
**Endpoint**: `https://li.quest/v1/advanced/relay`
### Request Body
The request body is a `RelayRequest` object containing:
* **Step Information**: Standard LI.FI step data (tool, action, estimate)
* **Typed Data**: Array of signed EIP-712 messages
* **Signature**: User's signature for each message
### Request Schema
```typescript theme={"system"}
{
id: string // Step ID
type: 'lifi' // Step type
tool: string // Bridge tool (e.g., 'hyperliquidSA')
toolDetails: { // Tool metadata
key: string
name: string
logoURI: string
}
action: { // Transfer action details
fromChainId: number
toChainId: number
fromToken: Token
toToken: Token
fromAmount: string
fromAddress: string
toAddress: string
slippage?: number
}
estimate: { // Estimated results
fromAmount: string
toAmount: string
toAmountMin: string
tool: string
executionDuration: number
approvalAddress: string
skipApproval: true // Always true for messaging flow
feeCosts: FeeCost[]
gasCosts: GasCost[]
executionType: string
}
includedSteps: Step[] // Nested steps
typedData: TypedData[] // EIP-712 messages with signatures
}
```
### Response
**Success Response** (`200 OK`):
```json theme={"system"}
{
"status": "ok",
"data": {
"taskId": "0x3078316542363633386445386335373163373837443762433234463938624641373335343235373331437c313735393438383039323538347c65646461643630632d373730392d346165312d623431652d3834643834333064306135623a30"
}
}
```
**Error Response** (`400 Bad Request`):
```json theme={"system"}
{
"status": "error",
"data": {
"code": 400,
"message": "Invalid request"
}
}
```
### Response Fields
* **`status`**: Either `"ok"` or `"error"`
* **`data.taskId`**: Unique hex-encoded identifier for tracking the message relay operation
* **`data.code`**: Error code (only present when status is "error")
* **`data.message`**: Error message (only present when status is "error")
***
## Status Tracking with taskId
After relaying a message, you receive a `taskId` that uniquely identifies the operation. Use this to track the message processing status.
### GET /v1/status
**Endpoint**: `https://li.quest/v1/status`
**Query Parameters**:
* **`taskId`** (optional): The task ID returned from `/relay` endpoint
* **`txHash`** (optional): Transaction hash (for traditional transactions)
* **`toChain`** (optional): Destination chain ID or key
* **`bridge`** (optional): Bridge tool identifier
* **`fromChain`** (optional): Source chain ID or key
**Note**: You must provide either `taskId` or `txHash`. For messaging flow, use `taskId`.
### Example Request
```bash theme={"system"}
curl -X GET 'https://li.quest/v1/status?taskId=0x3078316542363633386445386335373163373837443762433234463938624641373335343235373331437c313735393438383039323538347c65646461643630632d373730392d346165312d623431652d3834643834333064306135623a30'
```
### Response Format
The status endpoint returns the current state of the transfer:
```json theme={"system"}
{
"status": "DONE",
"substatus": "COMPLETED",
"sending": {
"txHash": "0x...",
"amount": "1000000",
"token": {
/* token details */
},
"chainId": 1337,
"timestamp": 1234567890
},
"receiving": {
"txHash": "0x...",
"amount": "1000000",
"token": {
/* token details */
},
"chainId": 999,
"timestamp": 1234567890
}
}
```
## Current Usage & Supported Protocols
Messaging flow is currently used for interactions with the following protocols:
### 1. Hyperliquid (Primary Use Case)
**Protocol**: [Hyperliquid](https://hyperliquid.xyz/)
**Operation**: Withdrawals from Hyperliquid to EVM chains
**Bridge Tool**: `hyperliquidSA`
**Message Type**: `SendAsset`, \`\`
**How it works**:
1. User has tokens on Hyperliquid spot account
2. Signs a `SendAsset` message to withdraw to an EVM chain
3. LI.FI relays the message to Hyperliquid's API
4. Hyperliquid processes the withdrawal and sends tokens to destination chain
### 2. Unit Protocol
**Protocol**: [Unit Protocol](https://unit.network/)
**Operation**: Withdrawals to Hyperliquid via Unit bridge
**Bridge Tool**: `unit`
**Message Type**: `SpotSend`
**Chain IDs**:
* From: 1337 (Hyperliquid/Hypercore)
* To: EVM chains, Bitcoin, Solana
***
## Supported Message Types
### Hyperliquid
LI.FI Messaging Flow supports three EIP-712 message types for Hyperliquid operations. Each message type follows a specific structure and is used for different operations.
#### 1. SpotSend
**Purpose**: Spot token transfers
**Use Case**: Used by Unit protocol for deposits to Hyperliquid
**Bridge Tool**: `unit`
**Message Structure**:
```typescript theme={"system"}
{
type: 'spotSend',
signatureChainId: '0x1',
hyperliquidChain: 'Mainnet',
destination: '0x...', // Recipient address
token: 'USOL:0x49b67c39...', // Token identifier
amount: '1.0', // Amount to transfer
time: 1234567890 // Timestamp in milliseconds
}
```
#### 2. SendAsset
**Purpose**: Asset transfers between DEXs (spot accounts)
**Use Case**: Used for Hyperliquid withdrawals to EVM chains
**Bridge Tool**: `hyperliquidSA`
**Message Structure**:
```typescript theme={"system"}
{
type: 'sendAsset',
signatureChainId: '0x1',
hyperliquidChain: 'Mainnet',
destination: '0x2000...00fe', // System address
sourceDex: 'spot', // Source DEX type
destinationDex: 'spot', // Destination DEX type
token: 'USOL:0x49b67c39...', // Token identifier
amount: '1.0', // Amount to transfer
fromSubAccount: '', // Agent wallet address (if used)
nonce: 1757944034747 // Timestamp/nonce
}
```
**Note**: All messages follow the EIP-712 typed data standard and include domain information:
```typescript theme={"system"}
{
domain: {
name: 'HyperliquidSignTransaction',
version: '1',
chainId: 999,
verifyingContract: '0x0000000000000000000000000000000000000000'
},
types: { /* EIP712Domain and message types */ },
primaryType: 'HyperliquidTransaction:SendAsset',
message: { /* message content */ }
}
```
***
## Integration Guide
### Using the API Directly
**Step 1: Get a Quote/Route**
Request a quote with `executionType=message` or `executionType=all`:
```bash theme={"system"}
curl -X GET 'https://li.quest/v1/quote?fromChain=1337&toChain=999&fromToken=0x8F254b963e8468305d409b33aA137C6700000000&toToken=0x9FDBdA0A5e284c32744D2f17Ee5c74B284993463&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&fromAmount=1000000&executionType=all'
```
**Step 2: Identify Message Steps**
Check the route for steps with `estimate.executionType === "message"` and `estimate.skipApproval === true`.
**Step 3: Sign the Message**
Use the `typedData` from the route to request a signature from the user's wallet:
```typescript theme={"system"}
const signature = await walletClient.signTypedData({
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
})
```
**Step 4: Relay the Message**
Submit the signed message to the `/relay` endpoint:
```bash theme={"system"}
curl -X POST 'https://li.quest/v1/advanced/relay' \
-H 'Content-Type: application/json' \
-H 'x-lifi-api-key: YOUR_API_KEY' \
-d '{
"id": "step-id",
"typedData": [{
...typedData,
"signature": "0x..."
}],
...stepData
}'
```
**Step 5: Track Status**
Use the returned `taskId` to check status:
```bash theme={"system"}
curl -X GET 'https://li.quest/v1/status?taskId=RETURNED_TASK_ID'
```
***
### Best Practices
1. **Always check `estimate.skipApproval`**: If true, skip approval transaction
2. **Validate signatures**: Ensure the message is signed correctly before relaying
3. **Store taskId**: Save the taskId returned from `/relay` for status tracking
4. **Poll status endpoint**: Check status periodically until completion
***
## Limitations & Considerations
### Current Limitations
* **Supported Protocols**: Currently limited to Hyperliquid and Unit protocol
### Future Enhancements
As the messaging flow matures, additional protocols and chains may be supported. Protocol teams interested in integration can contact the LI.FI team.
***
## FAQ
**Q: Do I need to do anything special to use messaging flow?**
A: No. If you're using the LI.FI SDK or Widget, messaging routes are automatically included and handled. For direct API usage, set `executionType=all` to see message routes.
**Q: Why does my route have `skipApproval: true`?**
A: This indicates the route uses messaging flow and doesn't require a token approval transaction.
**Q: How long does it take for a message to be processed?**
A: Processing time varies by protocol. For Hyperliquid, withdrawals typically complete within a few seconds.
**Q: Can I cancel a message after relaying?**
A: Once a message is relayed and accepted by the protocol, it cannot be cancelled through LI.FI. Check with the specific protocol for their cancellation policies.
**Q: How do I know if a route uses messaging flow?**
A: Check the `estimate.executionType` field. If it's `"message"`, the route uses messaging flow.
***
## Next Steps
* **Integrate**: Use the LI.FI SDK, Widget, or API to access messaging flow routes
* **Test**: Try a small withdrawal from Hyperliquid using the messaging flow
* **Monitor**: Use the `taskId` to track your operations via the status endpoint
* **Contact**: Reach out to the [LI.FI team](https://li.fi/contact-us/) for protocol integration requests
For more information, visit the [LI.FI Documentation](https://docs.li.fi/).
# Permit & Permit2 Approval Flow
Source: https://docs.li.fi/introduction/user-flows-and-examples/permit2-approval-flow
LI.FI supports three token approval strategies on EVM chains. In addition to the classic `approve()` transaction, integrators can use **EIP-2612 Permits** or **Uniswap Permit2** to authorize token transfers via off-chain signatures, reducing the number of on-chain transactions required.
If you use the **LI.FI SDK** or **Widget**, Permit2 is handled automatically. This guide is for integrators building directly against the API who want to understand or implement the permit flow themselves.
## Overview of Approval Strategies
| Strategy | On-chain Transactions | How It Works |
| -------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Classic `approve()`** | 1 approval tx + 1 swap/bridge tx | User sends an ERC-20 `approve()` to the LI.FI Diamond, then submits the swap/bridge transaction. |
| **EIP-2612 Native Permit** | 1 swap/bridge tx only | User signs an off-chain EIP-712 message. The signature is submitted alongside the swap calldata to the `Permit2Proxy`, which calls `permit()` on the token contract. Only works with tokens that implement EIP-2612. |
| **Uniswap Permit2** | 1 one-time approval + 1 swap/bridge tx (signature only) | User approves the Permit2 contract once (unlimited). For each subsequent transaction, the user signs an off-chain EIP-712 message authorizing a specific transfer. Works with any ERC-20 token. |
### Why Permit2?
With the classic approval model, every new dApp interaction requires a separate `approve()` transaction. Permit2 replaces this with a single, one-time unlimited approval to the canonical Permit2 contract. All subsequent authorizations happen through gasless EIP-712 signatures with per-transfer granularity (exact amount, deadline, nonce).
## Architecture
All permit-based flows go through the **Permit2Proxy** periphery contract, which acts as an intermediary between the user and the LI.FI Diamond:
### Key Addresses
| Contract | Address | Notes |
| ------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **LI.FI Diamond** | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` | Most EVM chains. Some networks use a different address — always query `GET /v1/chains` or check [Smart Contract Addresses](/introduction/lifi-architecture/smart-contract-addresses). |
| **Uniswap Permit2** | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Most EVM chains. Several networks use a different deployment (e.g. zkSync, Abstract, Lens, Flare, Sophon, XDC) — always read `permit2` from `GET /v1/chains`. |
| **Permit2Proxy** | Chain-specific | Query `GET /v1/chains` — each chain object includes `permit2` and `permit2Proxy` fields. |
### Discovering Addresses via the API
```ts theme={"system"}
const response = await fetch('https://li.quest/v1/chains');
const { chains } = await response.json();
const arbitrum = chains.find((c) => c.id === 42161);
console.log(arbitrum.permit2); // "0x000000000022D473030F116dDEE9F6B43aC78BA3"
console.log(arbitrum.permit2Proxy); // chain-specific Permit2Proxy address
console.log(arbitrum.diamondAddress); // "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE"
```
## API Flow: Permit2 (Standard Self-Execute)
This is the most common permit flow. It works with **any ERC-20 token** on chains where Permit2 is deployed.
Request a quote as usual. The response includes `estimate.approvalAddress` (the Diamond address for classic approve) and the chain metadata you need.
```ts theme={"system"}
const quote = await fetch('https://li.quest/v1/quote?' + new URLSearchParams({
fromChain: '42161',
toChain: '10',
fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism
fromAmount: '10000000', // 10 USDC
fromAddress: '0xYourWalletAddress',
})).then(r => r.json());
```
The user needs to approve the **Permit2 contract** (not the Diamond) once. This only needs to happen if the user hasn't already approved Permit2 for this token.
Resolve the Permit2 and Permit2Proxy addresses from the chains API — do not hardcode them, as several networks use non-canonical deployments.
```ts theme={"system"}
import { createPublicClient, createWalletClient, http, maxUint256, parseAbi } from 'viem';
import { arbitrum } from 'viem/chains';
const publicClient = createPublicClient({ chain: arbitrum, transport: http() });
const walletClient = createWalletClient({ chain: arbitrum, transport: http(), account });
const chainsResponse = await fetch('https://li.quest/v1/chains').then(r => r.json());
const fromChain = chainsResponse.chains.find((c) => c.id === quote.action.fromChainId);
const permit2Address = fromChain.permit2;
const permit2ProxyAddress = fromChain.permit2Proxy;
const erc20Abi = parseAbi([
'function allowance(address owner, address spender) view returns (uint256)',
'function approve(address spender, uint256 amount) returns (bool)',
]);
const tokenAddress = quote.action.fromToken.address;
const allowance = await publicClient.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'allowance',
args: [account.address, permit2Address],
});
if (allowance < BigInt(quote.action.fromAmount)) {
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'approve',
args: [permit2Address, maxUint256],
});
await publicClient.waitForTransactionReceipt({ hash });
}
```
Permit2 uses unordered nonces. The `Permit2Proxy` contract provides a `nextNonce()` helper that finds the next unused nonce for the signer.
```ts theme={"system"}
const permit2ProxyAbi = parseAbi([
'function nextNonce(address owner) view returns (uint256)',
'function callDiamondWithPermit2(bytes diamondCalldata, ((address token, uint256 amount) permitted, uint256 nonce, uint256 deadline) permit, bytes signature) external',
]);
const nonce = await publicClient.readContract({
address: permit2ProxyAddress,
abi: permit2ProxyAbi,
functionName: 'nextNonce',
args: [account.address],
});
```
Construct the EIP-712 typed data for `PermitTransferFrom`. The `spender` is the **Permit2Proxy** (not the Diamond).
```ts theme={"system"}
const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60); // 30 minutes
const permitTransferFrom = {
permitted: {
token: tokenAddress,
amount: BigInt(quote.action.fromAmount),
},
spender: permit2ProxyAddress,
nonce,
deadline,
};
const signature = await walletClient.signTypedData({
account,
primaryType: 'PermitTransferFrom',
domain: {
name: 'Permit2',
chainId: arbitrum.id,
verifyingContract: permit2Address,
},
types: {
TokenPermissions: [
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
PermitTransferFrom: [
{ name: 'permitted', type: 'TokenPermissions' },
{ name: 'spender', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
message: permitTransferFrom,
});
```
Wrap the Diamond calldata inside a `callDiamondWithPermit2` call targeting the **Permit2Proxy**, not the Diamond.
```ts theme={"system"}
import { encodeFunctionData } from 'viem';
const diamondCalldata = quote.transactionRequest.data;
const txData = encodeFunctionData({
abi: permit2ProxyAbi,
functionName: 'callDiamondWithPermit2',
args: [
diamondCalldata,
[
[permitTransferFrom.permitted.token, permitTransferFrom.permitted.amount],
permitTransferFrom.nonce,
permitTransferFrom.deadline,
],
signature,
],
});
const txHash = await walletClient.sendTransaction({
to: permit2ProxyAddress,
data: txData,
value: BigInt(quote.transactionRequest.value ?? 0),
gasLimit: BigInt(quote.transactionRequest.gasLimit ?? 0),
});
```
Track the transaction status as you normally would using the `/status` endpoint.
```ts theme={"system"}
const getStatus = async (txHash) => {
const result = await fetch(`https://li.quest/v1/status?txHash=${txHash}`);
return result.json();
};
let status;
do {
status = await getStatus(txHash);
if (status.status === 'PENDING') await new Promise(r => setTimeout(r, 5000));
} while (status.status !== 'DONE' && status.status !== 'FAILED');
```
## API Flow: EIP-2612 Native Permit
EIP-2612 permits are only available for tokens that implement the `permit()` function (e.g., USDC, AAVE, UNI). No prior `approve()` transaction is needed at all.
Not all tokens support EIP-2612. DAI uses a non-standard permit signature that LI.FI does not currently support. If the token does not implement EIP-2612, fall back to classic `approve()` or Permit2.
### Detecting EIP-2612 Support
There is no on-chain registry or ERC-165 interface for EIP-2612. The only reliable method is to probe the token contract for the required functions. If `nonces()` and `DOMAIN_SEPARATOR()` both return successfully, the token supports EIP-2612.
```ts theme={"system"}
const eip2612DetectAbi = parseAbi([
'function nonces(address owner) view returns (uint256)',
'function DOMAIN_SEPARATOR() view returns (bytes32)',
]);
async function supportsEIP2612(tokenAddress: string): Promise {
try {
await Promise.all([
publicClient.readContract({
address: tokenAddress,
abi: eip2612DetectAbi,
functionName: 'nonces',
args: [account.address],
}),
publicClient.readContract({
address: tokenAddress,
abi: eip2612DetectAbi,
functionName: 'DOMAIN_SEPARATOR',
}),
]);
return true;
} catch {
return false;
}
}
```
Tokens deployed with OpenZeppelin v4.9+ or v5.x also expose `eip712Domain()` ([EIP-5267](https://eips.ethereum.org/EIPS/eip-5267)), which returns all domain fields in a single call. For older tokens, read `name()`, `version()`, and `DOMAIN_SEPARATOR()` separately and recompute the separator to validate it.
Same as the standard flow: request a quote, then use the `transactionRequest.data` as your diamond calldata.
EIP-2612 tokens track nonces per-owner. Read the current nonce from the token contract.
```ts theme={"system"}
const eip2612Abi = parseAbi([
'function nonces(address owner) view returns (uint256)',
'function name() view returns (string)',
'function version() view returns (string)',
'function DOMAIN_SEPARATOR() view returns (bytes32)',
]);
const [nonce, name, version] = await Promise.all([
publicClient.readContract({
address: tokenAddress, abi: eip2612Abi,
functionName: 'nonces', args: [account.address],
}),
publicClient.readContract({
address: tokenAddress, abi: eip2612Abi, functionName: 'name',
}),
publicClient.readContract({
address: tokenAddress, abi: eip2612Abi, functionName: 'version',
}),
]);
```
The `spender` is the **Permit2Proxy** address.
```ts theme={"system"}
const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60);
const permitSignature = await walletClient.signTypedData({
account,
primaryType: 'Permit',
domain: {
name,
version,
chainId: arbitrum.id,
verifyingContract: tokenAddress,
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
message: {
owner: account.address,
spender: permit2ProxyAddress,
value: BigInt(quote.action.fromAmount),
nonce,
deadline,
},
});
```
```ts theme={"system"}
import { parseSignature, encodeFunctionData } from 'viem';
const { v, r, s } = parseSignature(permitSignature);
const permit2ProxyEip2612Abi = parseAbi([
'function callDiamondWithEIP2612Signature(address tokenAddress, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s, bytes diamondCalldata) external payable',
]);
const txData = encodeFunctionData({
abi: permit2ProxyEip2612Abi,
functionName: 'callDiamondWithEIP2612Signature',
args: [
tokenAddress,
BigInt(quote.action.fromAmount),
deadline,
Number(v),
r,
s,
quote.transactionRequest.data,
],
});
const txHash = await walletClient.sendTransaction({
to: permit2ProxyAddress,
data: txData,
value: BigInt(quote.transactionRequest.value ?? 0),
});
```
## SDK Usage
The `@lifi/sdk` handles Permit2 automatically during route execution. No manual signature construction is needed.
```ts theme={"system"}
import { createConfig, EVM, executeRoute } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { arbitrum } from 'viem/chains';
createConfig({
integrator: 'your-integrator-id',
providers: [
EVM({
getWalletClient: () => Promise.resolve(walletClient),
}),
],
});
// The SDK automatically:
// 1. Checks if Permit2 is deployed on the source chain
// 2. Approves the Permit2 contract if needed (one-time)
// 3. Signs a PermitTransferFrom message per transaction
// 4. Encodes the calldata for Permit2Proxy
await executeRoute({ route });
```
To disable Permit2 and force classic `approve()` transactions:
```ts theme={"system"}
await executeRoute({
route,
executionOptions: {
disableMessageSigning: true,
},
});
```
## When Permit2 Is Not Used
The SDK skips Permit2 and falls back to classic `approve()` when:
* The source chain does not have Permit2 deployed (`chain.permit2` is not set)
* The source chain does not have a Permit2Proxy (`chain.permit2Proxy` is not set)
* The source token is the chain's native token (ETH, MATIC, etc.)
* Message signing is disabled (`disableMessageSigning: true`)
* The transaction uses batched execution (EIP-5792)
* The step's estimate has `skipApproval: true` or `skipPermit: true` (rare, optional fields only present on certain chain-specific steps such as Hyperliquid)
## Reference
### Permit2Proxy Contract Functions
| Function | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `callDiamondWithPermit2(diamondCalldata, permit, signature)` | Transfers tokens via Permit2 `permitTransferFrom`, approves the Diamond, and forwards calldata. |
| `callDiamondWithEIP2612Signature(token, amount, deadline, v, r, s, diamondCalldata)` | Calls `permit()` on the EIP-2612 token, transfers tokens, approves the Diamond, and forwards calldata. |
| `nextNonce(owner)` | Returns the next available Permit2 nonce for the given address. |
### EIP-712 Type Definitions
**PermitTransferFrom** (Permit2 standard flow):
```json theme={"system"}
{
"TokenPermissions": [
{ "name": "token", "type": "address" },
{ "name": "amount", "type": "uint256" }
],
"PermitTransferFrom": [
{ "name": "permitted", "type": "TokenPermissions" },
{ "name": "spender", "type": "address" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
}
```
**Permit** (EIP-2612 native permit):
```json theme={"system"}
{
"Permit": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
}
```
### EIP-712 Domain
**Permit2** (for `PermitTransferFrom`):
```json theme={"system"}
{
"name": "Permit2",
"chainId": "",
"verifyingContract": ""
}
```
**EIP-2612** (for native `Permit` -- domain varies per token):
```json theme={"system"}
{
"name": "",
"version": "",
"chainId": "",
"verifyingContract": ""
}
```
# Fetching a Quote/Route
Source: https://docs.li.fi/introduction/user-flows-and-examples/requesting-route-fetching-quote
Guide to make a quote and route request
## Using SDK
```TypeScript theme={"system"}
import { getRoutes } from '@lifi/sdk';
const routesRequest: RoutesRequest = {
fromChainId: 42161, // Arbitrum
toChainId: 10, // Optimism
fromTokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toTokenAddress: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // DAI on Optimism
fromAmount: '10000000', // 10 USDC
};
const result = await getRoutes(routesRequest);
const routes = result.routes;
```
When you make a route request, you receive an array of route objects containing the essential information to determine which route to take for a swap or bridging transfer. At this stage, transaction data is not included and must be requested separately.
Additionally, if you would like to receive just one best option that our smart routing API can offer, it might be better to request a quote using getQuote.
## Using API
To generate a quote based on the amount you are sending, use the /quote endpoint. This method is useful when you know the exact amount you want to send and need to calculate how much the recipient will receive.
```TypeScript theme={"system"}
const getQuote = async (fromChain, toChain, fromToken, toToken, fromAmount, fromAddress) => {
const result = await axios.get('https://li.quest/v1/quote', {
params: {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
}
});
return result.data;
}
const fromChain = 42161;
const fromToken = 'USDC';
const toChain = 10;
const toToken = 'USDC';
const fromAmount = '1000000';
const fromAddress = YOUR_WALLET_ADDRESS;
const quote = await getQuote(fromChain, toChain, fromToken, toToken, fromAmount, fromAddress);
```
# Solana Transaction Example
Source: https://docs.li.fi/introduction/user-flows-and-examples/solana-tx-execution
## Requesting Solana specific information via the API
### Chains
```javascript theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/chains?chainTypes=SVM' \
--header 'accept: application/json'
```
### Tokens
```javascript theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/tokens?chains=SOL&chainTypes=SVM' \
--header 'accept: application/json'
```
### Token details
```javascript theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/token?chain=SOL&token=BONK' \
--header 'accept: application/json'
```
## Requesting a Quote or Routes
```javascript /quote theme={"system"}
curl --request GET \
--url 'https://li.quest/v1/quote?fromChain=ARB&toChain=SOL&fromToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&toToken=7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs&fromAddress=YOUR_EVM_WALLET&toAddress=YOUR_SOL_WALLET&fromAmount=1000000000' \
--header 'accept: application/json'
```
```javascript /advanced/routes theme={"system"}
curl --request POST \
--url https://li.quest/v1/advanced/routes \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"fromChainId": "ARB",
"fromAmount": "1000000000",
"toChainId": "SOL",
"fromTokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toTokenAddress": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
"fromAddress": "YOUR_EVM_WALLET",
"toAddress": "YOUR_SOL_WALLET"
}'
```
### Response
The key difference between **EVM -> SOL** and **SOL -> EVM** transfers is the structure of the transactionRequest. For **SOL -> EVM** transfers, it contains only a data parameter, which represents `base64` encoded Solana transaction data:
```json theme={"system"}
"transactionRequest": {
"data": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAsUBmw6CY1QcV7385AuJb6tDdM71YrLbjDGeWn6/zWFZAEcjcsOlIINY3LYFWBe38OO1l26BSpzB1L1bYnVNorsXkDqoJZ5Mb5PNE07yLa8RJGvFV55ILi1+vklkapJoW1yUKv7UyXP9sO3ptc4QOktFqSHRb9AYoDxZXcodBKfc4vN6ai03uOqBMXcmI4cih1E71LnDKMQljw0rqlnVVKOn98YHXWKE3PmeT4MetR4/Ep7+sfN+1vkcpHlwGeEHZgK4EIcmnLsIpOTZxLFhBBVIsDwUJkuCB/B43O01pI8fuLzyjGxJMo5db7lPEcx8Ns2BJ8kYOoL0ob3fnQ0eN3JwPzibblpkKkSjSk1qpqwB4d5rSn1PrbBHf6rOIO/O/W6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABceQrkgrAQHdCyf7CIDjBD/Y4pzKA7iTYhaafiX1eik37c9HIG4v1EeQo1ENAm3KHS+LCOKkZ4WQntGZQgIyu7ixIazui3zX0pmHiw3K3u/XdzSJfZ+ugLzVfJnnOn3v2RmLUngAdF+k4G2bOshpHaEkSZUr1Y1/vT3G9R/qU8zKFLzTYC6vfOspR18AlAfdoQQSzchbXIKs9TVzmL7XEYAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAADG+nrzvtutOj1l82qryXQxsbvkwtL24OR8pgIDRS9dYceCg/3UzgsruALUdoCSm3XiyBz8VtBPnGEIhrYpcBQ26zvkSidWkhDjuJPqY+9JDKulE8Bq2dVUioc+URRKUUsG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqTLHwyN3CxGdzcZNzliJWl0TJu3X7nF6oB9sysdDGG/6Ag8ABQJAQg8ADhMAAAcQBAMMAQgRAgYLDRIFChMJccw/qau6fVaf/aDz3hWN+Sh9oNuBvkrLv0ttxmLuxpa1pXsmnZ0BxMAAAAAAAAAAAAAAAABVIAjA9ocML3flzB0uub3/A+MOoAUAAAAAAAAAAAAAAAAnkbyh8t5GYe2IowyZp6lEmqhBdOkDAAAAAAAA"
}
```
# Transaction Status Tracking
Source: https://docs.li.fi/introduction/user-flows-and-examples/status-tracking
Complete guide to checking cross-chain transaction statuses using the LI.FI API
# Transaction Status
This guide explains how to check the status of cross-chain and swap transactions using the `/status` endpoint provided by LI.FI.
***
## Querying the Status Endpoint
To fetch the status of a transfer, the `/status` endpoint can be queried with:
1. sending transaction hash
2. receiving transaction hash
3. transactionId
Only one of the above values are required and need to be passed in `txHash` param.
### Required:
* `txHash`
### Optional:
* `fromChain`: Speeds up the request (recommended)
* `toChain`
* `bridge`
For swap transactions, set `fromChain` and `toChain` to the same value. The `bridge` parameter can be omitted.
```typescript theme={"system"}
const getStatus = async (txHash: string) => {
const result = await axios.get('https://li.quest/v1/status', {
params: { txHash },
});
return result.data;
};
```
## Sample Response
```json theme={"system"}
{
"transactionId": "0x0959ee0fbb37a868752d7ae40b25dbfa3b7d72f499fa8386fd5f4105b18b62bd",
"sending": {
"txHash": "0x5862726dbc6643c6a34b3496bb15e91f11771f6756ccf83826304846bbc93c0v",
"txLink": "https://etherscan.io/tx/0x5862726dbc6643c6a34b3496bb15e91f11771f6756ccf83826304846bbc93c0v",
"amount": "60000000000000000000000",
"token": {
"symbol": "ORDS",
"priceUSD": "0.012027801612559667"
},
"gasPrice": "23079962248",
"gasUsed": "231727",
"gasAmountUSD": "14.0296",
"amountUSD": "721.6681",
"includedSteps": [
{
"tool": "feeCollection",
"fromAmount": "60000000000000000000000",
"toAmount": "59820000000000000000000"
},
{
"tool": "1inch",
"fromAmount": "59820000000000000000000",
"toAmount": "275101169247651913"
}
]
},
"receiving": {
"txHash": "0x2862726dbc6643c6a34b3496bb15e91f11771f6756ccf83826604846bbc93c0v",
"amount": "275101169247651913",
"token": {
"symbol": "ETH",
"priceUSD": "2623.22"
},
"gasAmountUSD": "14.0296",
"amountUSD": "721.6509"
},
"lifiExplorerLink": "https://scan.li.fi/tx/0x5862726dbc6643c6a34b3496bb15e91f11771f6756ccf83826304846bbc93c0e",
"fromAddress": "0x14a980237fa9797fa27c5152c496cab65e36da4f",
"toAddress": "0x14a980237fa9797fa27c5152c496cab65e36da4f",
"tool": "1inch",
"status": "DONE",
"substatus": "COMPLETED",
"substatusMessage": "The transfer is complete.",
"metadata": {
"integrator": "example_integrator"
}
}
```
***
## Status Values
| Status | Description |
| ----------- | ------------------------------------------- |
| `NOT_FOUND` | Transaction doesn't exist or not yet mined. |
| `INVALID` | Hash is not tied to the requested tool. |
| `PENDING` | Bridging is still in progress. |
| `DONE` | Transaction completed successfully. |
| `FAILED` | Bridging process failed. |
***
## Substatus Definitions
### PENDING
* `WAIT_SOURCE_CONFIRMATIONS`: Waiting for source chain confirmations
* `WAIT_DESTINATION_TRANSACTION`: Waiting for destination transaction
* `BRIDGE_NOT_AVAILABLE`: Bridge API is unavailable
* `CHAIN_NOT_AVAILABLE`: Source/destination chain RPC unavailable
* `REFUND_IN_PROGRESS`: Refund in progress (if supported)
* `UNKNOWN_ERROR`: Status is indeterminate
### DONE
* `COMPLETED`: Transfer was successful
* `PARTIAL`: Only partial transfer completed (common for across, hop, stargate, amarok)
* `REFUNDED`: Tokens were refunded
### FAILED
* `NOT_PROCESSABLE_REFUND_NEEDED`: Cannot complete, refund needed
* `OUT_OF_GAS`: Transaction ran out of gas
* `SLIPPAGE_EXCEEDED`: Received amount too low
* `INSUFFICIENT_ALLOWANCE`: Not enough allowance
* `INSUFFICIENT_BALANCE`: Not enough balance
* `EXPIRED`: Transaction expired
* `UNKNOWN_ERROR`: Unknown or invalid state
* `REFUNDED`: Tokens were refunded
# API Overview
Source: https://docs.li.fi/lifi-intents/intents-api/api-overview
Base URLs, endpoints, authentication, and order lifecycle for the LI.FI Intents API.
The Intents API is the integrator-facing interface for requesting quotes, submitting orders, and tracking status. All endpoints are open and require no API key.
For solver-facing endpoints (quote submission, account management), see the [Solver API Overview](/lifi-intents/for-solvers/api-overview). For authentication details, see [Authentication](/lifi-intents/authentication#integrators).
***
## Base URLs
| Environment | URL | Use |
| ----------- | ------------------------- | -------------------------------------------- |
| Production | `https://order.li.fi` | Live orders with real assets |
| Development | `https://order-dev.li.fi` | Testnet orders (Sepolia, Base Sepolia, etc.) |
Interactive API documentation is available at [`/docs`](https://order.li.fi/docs) on both environments.
***
## Endpoints
| Method | Path | Description |
| ------ | ---------------------------------- | ----------------------------------------------------------- |
| `POST` | `/quote/request` | Request a quote for an intent (exact-input or exact-output) |
| `GET` | `/api/v1/integrator/quote/request` | V1 quote endpoint for integrator-friendly quote parameters |
| `POST` | `/orders/submit` | Submit an order (intent) to the solver network |
| `GET` | `/orders` | List orders with optional filters |
| `GET` | `/orders/status` | Get order status by `onChainOrderId` or `catalystOrderId` |
| `GET` | `/chains/supported` | Get the list of supported chains |
| `GET` | `/routes` | Get supported routes (chain/asset pairs) |
***
## Order Lifecycle
Orders can include transitional states depending on submission path and indexing timing. In most integrations, the primary execution progression is:
```
Signed → Delivered → Settled
```
| Status | What happened |
| ------------- | --------------------------------------------------------- |
| **Signed** | Order signed and broadcast to the solver network |
| **Delivered** | Solver has delivered assets on the destination chain |
| **Settled** | Oracle verified delivery, locked funds released to solver |
If the order is not filled before `fillDeadline`, it can be refunded after `expires`.
***
## Interoperable Addresses
The Intents API uses [EIP-7930 interoperable addresses](https://eips.ethereum.org/EIPS/eip-7930), which encode both the chain and address in a single bytes field. This applies to `user`, `asset`, and `receiver` fields in quote requests.
Example encoding of `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` on Base (chain ID 8453):
```
0x0001|0000|02|2105|14|833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
^^^^ Version: 1
^^^^ ChainType: EVM
^^ Chain Reference length: 2 bytes
^^^^ Chain Reference: 8453 (hex 2105)
^^ Address length: 20 bytes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Address
```
***
## Amounts
All amounts are in the token's smallest unit (wei-equivalent), with no decimals. For example, 1.5 USDC (6 decimals) is `"1500000"`.
***
## Next Steps
Fetch solver pricing for your intent
Construct and submit an order to the solver network
Monitor orders via the API or on-chain events
Solver endpoints, quoting, and filling orders
# Compact Orders
Source: https://docs.li.fi/lifi-intents/intents-api/compact-orders
Submit gasless off-chain orders via The Compact resource lock. Deposit once, issue multiple intents without per-order gas costs.
The Compact flow uses [The Compact](https://github.com/Uniswap/the-compact) resource lock for off-chain, gasless order submission. Users deposit tokens once and can issue multiple intents from that balance without paying gas per order.
If you're adding LI.FI Intents alongside other bridges, the [standard escrow flow](/lifi-intents/quickstart) is simpler and recommended. Use the Compact flow when you need gasless submissions or are building a resource-lock-native application.
***
## How It Differs from Escrow
| | Escrow | Compact |
| ---------------- | -------------------------------- | --------------------------------------------- |
| Token locking | Per-intent, on-chain each time | Deposit once, reuse balance |
| Order submission | On-chain (`open`/`openFor`) | Off-chain (`POST /orders/submit`) |
| Gas per intent | User pays gas to lock | Gasless after initial deposit |
| Settlement | `finalise` (no signature needed) | `finaliseWithSignature` (signatures required) |
| Order type | N/A (detected on-chain) | `CatalystCompactOrder` |
***
## Contract Addresses
These are the same across all supported chains:
| Contract | Address |
| ------------------------ | -------------------------------------------- |
| InputSettlerCompact | `0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf` |
| The Compact | `0x00000000000000171ede64904551eeDF3C6C9788` |
| Polymer Oracle (mainnet) | `0x0000003E06000007A224AeE90052fA6bb46d43C9` |
| Output Settler | `0x0000000000eC36B683C2E6AC89e9A75989C22a2e` |
***
## Flow
Deposit tokens into The Compact resource lock. This is a one-time on-chain transaction. Subsequent intents draw from this balance without additional gas.
See the [lintent.org demo](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L199) for a UI example of depositing and registering intents.
Same as the escrow flow. Call `POST /quote/request`. Include `"oif-resource-lock-v0"` in `supportedTypes` to indicate Compact support.
```ts TypeScript theme={"system"}
const response = await fetch('https://order.li.fi/quote/request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: '0x0001000002210514YOUR_WALLET_ADDRESS_20_BYTES',
intent: {
intentType: 'oif-swap',
inputs: [{
user: '0x0001000002210514YOUR_WALLET_ADDRESS_20_BYTES',
asset: '0x0001000002210514833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: '10000000',
}],
outputs: [{
receiver: '0x0001000002A4B114YOUR_WALLET_ADDRESS_20_BYTES',
asset: '0x0001000002A4B114af88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
amount: null,
}],
swapType: 'exact-input',
},
supportedTypes: ['oif-escrow-v0', 'oif-resource-lock-v0'],
}),
});
const { quotes } = await response.json();
const bestQuote = quotes[0];
```
Compact orders are authorized via EIP-712 signatures rather than on-chain transactions. Sign a `BatchClaim` using The Compact's domain separator.
```ts TypeScript theme={"system"}
// The claim is signed as a BatchCompact struct:
// struct BatchCompact {
// address arbiter; // InputSettlerCompact address
// address sponsor; // User address
// uint256 nonce; // Allocator nonce
// uint256 expires; // Claim expiry
// uint256[2][] idsAndAmounts;
// Mandate mandate; // fillDeadline, inputOracle, outputs
// }
```
For a complete signing example, see the [lintent.org demo](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L144).
Submit the signed order to `POST /orders/submit`. Include the `quoteId` for preferential solver matching.
```bash curl theme={"system"}
curl -X POST 'https://order.li.fi/orders/submit' \
-H 'Content-Type: application/json' \
-d '{
"orderType": "CatalystCompactOrder",
"inputSettler": "0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf",
"quoteId": "QUOTE_ID_FROM_STEP_2",
"order": {
"expires": 1942819670,
"user": "0xYOUR_WALLET_ADDRESS",
"nonce": "1004",
"originChainId": "8453",
"fillDeadline": 1942819670,
"inputOracle": "0x0000003E06000007A224AeE90052fA6bb46d43C9",
"inputs": [
["749071750893463290574776461331093852760741783827", "10000000"]
],
"outputs": [{
"oracle": "0x0000000000000000000000000000003E06000007A224AeE90052fA6bb46d43C9",
"settler": "0x0000000000000000000000000000000000eC36B683C2E6AC89e9A75989C22a2e",
"chainId": "42161",
"token": "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831",
"amount": "9986765",
"recipient": "0x000000000000000000000000YOUR_WALLET_ADDRESS",
"callbackData": "0x",
"context": "0x"
}]
}
}'
```
`CatalystCompactOrder` submissions require a sponsor signature or Compact registration tx hash. The payload above is a reference template. You must include the appropriate signature fields. Always test on `order-dev.li.fi` before production.
The response includes a `meta` object with `orderIdentifier` and `onChainOrderId` for tracking.
Same as the escrow flow. Poll `GET /orders/status` until settlement.
```ts TypeScript theme={"system"}
const trackOrder = async (orderId: string) => {
let status: string;
do {
const res = await fetch(
`https://order.li.fi/orders/status?catalystOrderId=${orderId}`
);
const data = await res.json();
status = data.meta.orderStatus;
console.log(`Status: ${status}`);
if (status !== 'Settled' && status !== 'Expired') {
await new Promise(r => setTimeout(r, 3000));
}
} while (status !== 'Settled' && status !== 'Expired');
return status;
};
await trackOrder(order.meta.orderIdentifier);
```
***
## Integration Examples
* [Signing a BatchCompact claim](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L144) (UI example)
* [Depositing and registering intents](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L199) (UI example)
* [RegisterIntentLib.sol](https://github.com/catalystsystem/catalyst-intent/blob/27ce0972c150ed113f66ae91069eb953f23d920b/src/libs/RegisterIntentLib.sol#L100-L131) (smart contract example)
***
## Next Steps
Compact claim structure, registration, and Permit2 details
How resource locks, sponsors, allocators, and arbiters work
Full order construction reference and validation rules
Monitor orders via the API or on-chain events
# Create and Submit Orders
Source: https://docs.li.fi/lifi-intents/intents-api/create-and-submit
Construct a StandardOrder, choose a resource lock, attach optional calldata, and submit to the LI.FI Intents solver network.
An order (intent) describes what the user wants: input assets, desired outputs, settlement parameters, and deadlines. Once constructed, submit it to the order server for distribution to the solver network.
***
## Order Structure
LI.FI intents use the OIF `StandardOrder`, which supports single-chain inputs and multi-chain outputs.
```ts theme={"system"}
type StandardOrder = {
user: `0x${string}`;
nonce: bigint;
originChainId: bigint;
expires: number;
fillDeadline: number;
inputOracle: `0x${string}`;
inputs: [bigint, bigint][];
outputs: MandateOutput[];
};
type MandateOutput = {
oracle: `0x${string}`;
settler: `0x${string}`;
chainId: bigint;
token: `0x${string}`;
amount: bigint;
recipient: `0x${string}`;
call: `0x${string}`;
context: `0x${string}`;
};
```
### Field Reference
| Field | Description |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user` | Owner of the input assets. Refund recipient if the order is not filled. |
| `nonce` | Unique identifier per order. For The Compact, this is an allocator nonce. |
| `originChainId` | Chain where the intent originates |
| `expires` | Deadline (unix timestamp) for submitting proof that the order was filled. After this, the order can be refunded. |
| `fillDeadline` | Deadline for the solver to fill the order. Must be before `expires`. |
| `inputOracle` | Oracle address on the origin chain. Must correspond to `outputs[].oracle`. |
| `inputs` | Array of `[tokenIdentifier, amount]` pairs as `uint256`. For escrow, the token identifier is the address cast to `uint256`. For The Compact, it is the ERC-6909 identifier. |
| `outputs` | Array of `MandateOutput` describing desired deliveries |
### Output Fields
| Field | Description |
| ----------- | ---------------------------------------------------------------------------------------- |
| `oracle` | Oracle address on the output chain. Must be in the same oracle network as `inputOracle`. |
| `settler` | Output settler contract. Determines auction type and settlement logic. |
| `chainId` | Destination chain identifier |
| `token` | Token identifier on the destination chain |
| `amount` | Output token amount |
| `recipient` | Recipient address for the output tokens and any calldata |
| `call` | Calldata delivered to `recipient` after token delivery. Set to `0x` if not needed. |
| `context` | Auction parameters and settlement context. Set to `0x` for simple limit orders. |
***
## Choosing a Resource Lock
Resource locks determine how user funds are held while the solver fills the order.
| Lock | Best for | How it works |
| ---------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Simple Escrow** (`InputSettlerEscrow`) | Standard integrations alongside non-resource-lock bridges | User approves the escrow contract (via direct approval or Permit2). Tokens are escrowed per-intent. |
| **The Compact** (`InputSettlerCompact`) | Resource-lock-native applications | Long-lived deposit. Users deposit once and can issue multiple intents from the same balance. |
If you're adding LI.FI Intents alongside other bridges, Simple Escrow is recommended. If you're building a resource-lock-first application, consider The Compact. See [Input Settlement](/lifi-intents/architecture/input-settlement) for registration details.
***
## Submitting an Order
For non-gasless escrow integrations, submit orders on-chain (for example through `open` / `openFor` flow) and use the order server for quote discovery + status tracking.
Use `POST /orders/submit` only when your integration needs off-chain order submission (for example gasless escrow or signed Compact claims). If you used a quote from the order server, include the `quoteId` for preferential processing.
```bash curl theme={"system"}
curl -X POST 'https://order.li.fi/orders/submit' \
-H 'Content-Type: application/json' \
-d '{
"orderType": "CatalystCompactOrder",
"inputSettler": "0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf",
"quoteId": "QUOTE_ID_FROM_STEP_1",
"order": {
"expires": 1942819670,
"user": "0xYOUR_WALLET_ADDRESS",
"nonce": "1004",
"originChainId": "8453",
"fillDeadline": 1942819670,
"inputOracle": "0x0000003E06000007A224AeE90052fA6bb46d43C9",
"inputs": [
["749071750893463290574776461331093852760741783827", "10000000"]
],
"outputs": [{
"oracle": "0x0000000000000000000000000000003E06000007A224AeE90052fA6bb46d43C9",
"settler": "0x0000000000000000000000000000000000eC36B683C2E6AC89e9A75989C22a2e",
"chainId": "42161",
"token": "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831",
"amount": "9986765",
"recipient": "0x000000000000000000000000YOUR_WALLET_ADDRESS",
"callbackData": "0x",
"context": "0x"
}]
}
}'
```
The response includes the full order, associated quote (if any), and a `meta` object with `orderIdentifier` and `onChainOrderId` for tracking.
This is a reference template. `CatalystCompactOrder` submissions require a sponsor signature or Compact registration tx hash. Always validate on `order-dev.li.fi` before mainnet rollout.
***
## On-Chain vs Off-Chain Orders
| Type | Examples | Order server required? |
| ----------------------- | ------------------------------------------------ | ------------------------------------------------------------ |
| **On-chain** | Escrow opened intents, registered Compact claims | No. Solvers and the order server detect these automatically. |
| **Off-chain (gasless)** | Gasless escrow intents, signed Compact claims | Yes. Submit via `POST /orders/submit`. |
The order server is a convenience layer, not a requirement for on-chain orders. All on-chain intents can be submitted without it. Some solvers detect on-chain intents independently.
***
## Calls on Delivery
Each `MandateOutput` supports a `call` field for calldata executed on the destination chain after token delivery. The recipient receives tokens first, then the output settler calls `outputFilled(bytes32 token, uint256 amount, bytes executionData)` on the `recipient` contract.
When using calls on delivery:
* Tokens are delivered before the call executes for each individual output
* If the call fails, the entire intent cannot be filled
* The recipient must be a contract that implements `outputFilled`, not an EOA
* For multi-chain outputs, only the first output should include calldata
For arbitrary contract calls, use a batching contract or Single-Call Architecture (SCA) wrapper since the call is wrapped in `outputFilled`. Contact LI.FI if you need an SCA implementation.
***
## Intent Validation
Before submitting, validate your order against these rules.
### Security
1. Ensure the oracle network is secure relative to the intent value
2. All oracles (input and output) should belong to the same network
3. Multi-output orders can be vulnerable to DoS by filling only the first output. Make the first output the most valuable.
4. Calls from the output settler cannot be trusted for authentication since anyone can fill outputs
5. Set `user` as the intended refund recipient, not the intent issuer or relay address.
Ensure that the refund recipient can properly receive and handle funds. Using an address that cannot accept tokens (e.g. a contract without a receive function, or the wrong chain address) will result in permanent loss of funds.
### Correctness
1. Use known, reliable tokens, settlement contracts, and oracles
2. Use the fewest tokens possible. Low-value or obscure tokens reduce fill likelihood.
3. Provide sufficient time between `fillDeadline` and `expires` to accommodate messaging delays
4. Ensure the nonce is unique. Escrow requires user uniqueness, while The Compact requires allocator uniqueness.
5. For The Compact, ensure all inputs share the same allocator and the resource lock expiry is after the intent expiry
***
## Next Steps
Monitor order progress and handle terminal states
Fetch solver pricing before constructing an order
Escrow and Compact registration details
Oracle networks and verification
# Request a Quote
Source: https://docs.li.fi/lifi-intents/intents-api/request-quote
Fetch solver pricing for cross-chain and same-chain intents using the LI.FI Intents order server.
The order server caches quotes from its solver network. Call `POST /quote/request` to get pricing for an intent before constructing an order.
Using the order server for quotes is optional but recommended. It matches intents against solver standing quotes and gives stronger execution guarantees.
For simpler integrator parameter handling, a V1 quote endpoint is also available at `GET /api/v1/integrator/quote/request`.
***
## Request Format
```bash curl theme={"system"}
curl -X POST 'https://order.li.fi/quote/request' \
-H 'Content-Type: application/json' \
-d '{
"user": "0x0001000002210514d8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"intent": {
"intentType": "oif-swap",
"inputs": [{
"user": "0x0001000002210514d8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"asset": "0x0001000002210514833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000000"
}],
"outputs": [{
"receiver": "0x0001000002A4B114d8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"asset": "0x0001000002A4B114af88d065e77c8cC2239327C5EDb3A432268e5831",
"amount": null
}],
"swapType": "exact-input"
},
"supportedTypes": ["oif-escrow-v0"]
}'
```
```ts TypeScript theme={"system"}
const response = await fetch('https://order.li.fi/quote/request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: '0x0001000002210514d8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
intent: {
intentType: 'oif-swap',
inputs: [{
user: '0x0001000002210514d8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
asset: '0x0001000002210514833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: '10000000', // 10 USDC (6 decimals)
}],
outputs: [{
receiver: '0x0001000002A4B114d8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
asset: '0x0001000002A4B114af88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
amount: null,
}],
swapType: 'exact-input',
},
supportedTypes: ['oif-escrow-v0'],
}),
});
const { quotes } = await response.json();
```
### Request Fields
| Field | Type | Description |
| ------------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user` | string | Interoperable address of the user (refund recipient on failure) |
| `intent.intentType` | string | Always `"oif-swap"` |
| `intent.inputs[]` | array | Input assets with `user`, `asset` (interoperable address), and `amount` |
| `intent.outputs[]` | array | Desired outputs with `receiver`, `asset`, and `amount` (`null` for exact-input) |
| `intent.swapType` | string | `"exact-input"` fixes the input amount and lets the solver determine the output (set `outputs[].amount` to `null`). This is the most common mode. `"exact-output"` fixes the output amount and lets the solver determine the required input (set `inputs[].amount` to `null`). Use this when the destination amount matters, for example to repay a loan or meet a contract requirement. |
| `supportedTypes` | array | Resource lock types the user supports. Use `"oif-escrow-v0"` for standard escrow integrations; include `"oif-resource-lock-v0"` only if your flow supports Compact/resource-lock claims |
| `intent.metadata.exclusiveFor` | array | Optional. Restrict quotes to specific solver addresses. Useful for compliance or partnerships, but over-restriction can reduce fill reliability. |
***
## Response Format
```ts theme={"system"}
interface QuoteResponse {
quotes: {
order: null;
validUntil: number;
quoteId: string; // e.g. "quote_yCyE5aWW4NILo2UdM-8ETpia05TCLv"
preview: {
inputs: { user: string; asset: string; amount: string }[];
outputs: { receiver: string; asset: string; amount: string }[];
};
metadata: {
exclusiveFor: string | null;
};
partialFill: boolean;
failureHandling: string; // e.g. "refund-automatic"
}[];
}
```
The best quote is always at index 0. Save the `quoteId` for use when [submitting the order](/lifi-intents/intents-api/create-and-submit).
### Response Fields
| Field | Description |
| ------------------------------------ | ---------------------------------------------------------------------------------- |
| `validUntil` | Unix timestamp after which the quote expires. Re-fetch if stale. |
| `quoteId` | Reference ID to include when submitting an order for preferential solver matching. |
| `preview.inputs` / `preview.outputs` | The expected input and output amounts for this quote. |
| `metadata.exclusiveFor` | The solver address selected for this quote, or `null`. |
| `partialFill` | Whether the solver supports partial fills for this quote. |
| `failureHandling` | How failures are handled (e.g. `"refund-automatic"`). |
### Exclusive Solver Matching
The `metadata.exclusiveFor` field in the response identifies the solver selected for this quote. If you want to enforce exclusivity when constructing the order, encode the solver address and an expiry into the output's `context` field:
```ts theme={"system"}
const exclusiveFor = bestQuote.metadata.exclusiveFor;
if (exclusiveFor) {
const currentTime = Math.floor(Date.now() / 1000);
const exclusiveExpiry = (currentTime + 60).toString(16);
const paddedExclusiveFor = exclusiveFor.replace('0x', '').padStart(64, '0');
mandateOutput.context = `0xe0${paddedExclusiveFor}${exclusiveExpiry}`;
}
```
Apply exclusivity only if you fully control solver policy and fallback behavior.
***
## Next Steps
Construct a StandardOrder and submit it to the solver network
Monitor order progress via the API or on-chain events
# Track Order Status
Source: https://docs.li.fi/lifi-intents/intents-api/track-status
Monitor LI.FI intent orders via the order server API or on-chain events.
After submitting an order, you can track its progress through the order server API or by monitoring on-chain events directly.
***
## Order Server Status
Call `GET /orders/status` with either the `onChainOrderId` or the `catalystOrderId` returned when you submitted the order.
```bash curl theme={"system"}
curl -X GET 'https://order.li.fi/orders/status?catalystOrderId=intent_qVo7_1TkJ7VekL99O7SGS9cRHv0si6'
```
```ts TypeScript theme={"system"}
const getOrderStatus = async (orderId: string) => {
const res = await fetch(
`https://order.li.fi/orders/status?catalystOrderId=${orderId}`
);
return res.json();
};
const status = await getOrderStatus('intent_qVo7_1TkJ7VekL99O7SGS9cRHv0si6');
console.log('Status:', status.meta.orderStatus);
```
### Query Parameters
| Parameter | Description |
| ----------------- | -------------------------------------------------------------------------------- |
| `onChainOrderId` | The on-chain order ID emitted in contract events |
| `catalystOrderId` | The order server ID returned from `POST /orders/submit` (e.g. `intent_qVo7_...`) |
At least one parameter is required.
### Response
The response includes the full order, any associated quote, and a `meta` object with status information.
```ts theme={"system"}
{
order: StandardOrder,
quote: QuoteInfo | null,
sponsorSignature: string | null,
allocatorSignature: string | null,
inputSettler: string,
meta: {
submitTime: number,
orderStatus: "Submitted" | "Open" | "Signed" | "Delivered" | "Settled",
destinationAddress: string,
orderIdentifier: string,
onChainOrderId: string,
signedAt: string | null,
deliveredAt: string | null,
settledAt: string | null,
expiredAt: string | null,
orderInitiatedTxHash: string | null,
orderDeliveredTxHash: string | null,
orderVerifiedTxHash: string | null,
orderSettledTxHash: string | null,
solverAddress: string | null
}
}
```
### Status Lifecycle
| Status | Meaning |
| ----------- | ---------------------------------------------------------------- |
| `Submitted` | Order received by the order server (transitional) |
| `Open` | Order registered on-chain (transitional) |
| `Signed` | Order signed and available for solver pickup |
| `Delivered` | Solver has delivered assets on the destination chain |
| `Settled` | Proof verified, locked funds released to solver. Order complete. |
Most integrations should treat `Signed`, `Delivered`, and `Settled` as the primary execution states. `Submitted` and `Open` may not be visible in every path.
### Polling Example
```ts theme={"system"}
const pollUntilDone = async (orderId: string) => {
let data;
do {
data = await getOrderStatus(orderId);
const { orderStatus } = data.meta;
console.log(`Status: ${orderStatus}`);
if (orderStatus === 'Settled') {
console.log('Complete. Delivery tx:', data.meta.orderDeliveredTxHash);
return data;
}
if (data.meta.expiredAt) {
console.log('Order expired.');
return data;
}
await new Promise(r => setTimeout(r, 3000));
} while (true);
};
```
***
## Listing Orders
Use `GET /orders` to query multiple orders with filters.
```bash theme={"system"}
curl -X GET 'https://order.li.fi/orders?user=0xYOUR_ADDRESS&status=Delivered&limit=10'
```
| Parameter | Description |
| ----------------- | ----------------------------------- |
| `user` | Filter by initiator address |
| `status` | Filter by order status |
| `nonce` | Filter by nonce |
| `exclusiveFor` | Filter by solver address |
| `onChainOrderId` | Filter by on-chain order ID |
| `catalystOrderId` | Filter by order server ID |
| `limit` | Max results (1-50, default 50) |
| `offset` | Results to skip (0-1000, default 0) |
***
## On-Chain Events
You can also track orders directly from contract events. The `orderId` is emitted as `topic1` on all relevant events.
### Order Opened
```solidity theme={"system"}
event Open(bytes32 indexed orderId, StandardOrder order);
```
### Output Filled (by solver)
```solidity theme={"system"}
event OutputFilled(
bytes32 indexed orderId,
bytes32 solver,
uint32 timestamp,
MandateOutput output,
uint256 finalAmount
);
```
### Order Finalized (settlement complete)
```solidity theme={"system"}
event Finalised(
bytes32 indexed orderId,
bytes32 solver,
bytes32 destination
);
```
### Order Refunded
```solidity theme={"system"}
event Refunded(bytes32 indexed orderId);
```
The on-chain `orderId` is deterministic and computed as:
```solidity theme={"system"}
function orderIdentifier(StandardOrder calldata order) internal view returns (bytes32) {
return keccak256(
abi.encodePacked(
block.chainid,
address(this),
order.user,
order.nonce,
order.expires,
order.fillDeadline,
order.inputOracle,
keccak256(abi.encodePacked(order.inputs)),
abi.encode(order.outputs)
)
);
}
```
***
## Next Steps
Full endpoint reference and base URLs
Order construction and submission
How input and output settlement works
Full architecture of the Intent/Solver Marketplace
# Chains and Tools
Source: https://docs.li.fi/sdk/chains-tools
Request all available chains, bridges, and exchanges.
Get an overview of which options (chains, bridges, DEXs) are available at this moment.
## Get available chains
### `getChains`
Fetches a list of all available chains supported by the SDK.
**Parameters**
* `params` (ChainsRequest, optional): Configuration for the requested chains.
* `chainTypes` (ChainType\[], optional): List of chain types.
* `options` (RequestOptions, optional): Additional request options.
**Returns**
A Promise that resolves to an array of `ExtendedChain` objects.
```typescript Example theme={"system"}
import { ChainType, getChains } from '@lifi/sdk';
try {
const chains = await getChains({ chainTypes: [ChainType.EVM] });
console.log(chains);
} catch (error) {
console.error(error);
}
```
## Get available bridges and DEXs
### `getTools`
Fetches the tools available for bridging and swapping tokens.
**Parameters**
* `params` (ToolsRequest, optional): Configuration for the requested tools.
* `chains` ((ChainKey | ChainId)\[], optional): List of chain IDs or keys.
* `options` (RequestOptions, optional): Additional request options.
**Returns**
A Promise that resolves to `ToolsResponse` and contains information about available bridges and DEXs.
```typescript Example theme={"system"}
import { getTools } from '@lifi/sdk';
try {
const tools = await getTools();
console.log(tools);
} catch (error) {
console.error(error);
}
```
## Get available connections
A connection is a pair of two tokens (on the same chain or on different chains) that can be exchanged via our platform.
Read more [Getting all possible Connections](https://docs.li.fi/li.fi-api/li.fi-api/getting-all-possible-connections)
### `getConnections`
Gets all the available connections for swapping or bridging tokens.
**Parameters**
* `connectionRequest` (ConnectionsRequest): Configuration of the connection request.
* `fromChain` (number, optional): The source chain ID.
* `fromToken` (string, optional): The source token address.
* `toChain` (number, optional): The destination chain ID.
* `toToken` (string, optional): The destination token address.
* `allowBridges` (string\[], optional): Allowed bridges.
* `denyBridges` (string\[], optional): Denied bridges.
* `preferBridges` (string\[], optional): Preferred bridges.
* `allowExchanges` (string\[], optional): Allowed exchanges.
* `denyExchanges` (string\[], optional): Denied exchanges.
* `preferExchanges` (string\[], optional): Preferred exchanges.
* `allowSwitchChain` (boolean, optional): Whether connections that require chain switch (multiple signatures) are included. Default is true.
* `allowDestinationCall` (boolean, optional): Whether connections that include destination calls are included. Default is true.
* `chainTypes` (ChainType\[], optional): Types of chains to include.
* `options` (RequestOptions, optional): Request options.
**Returns**
A Promise that resolves to a `ConnectionsResponse`.
```typescript Example theme={"system"}
import { getConnections } from '@lifi/sdk';
const connectionRequest = {
fromChain: 1,
fromToken: '0x0000000000000000000000000000000000000000',
toChain: 10,
toToken: '0x0000000000000000000000000000000000000000',
};
try {
const connections = await getConnections(connectionRequest);
console.log('Connections:', connections);
} catch (error) {
console.error('Error:', error);
}
```
For more detailed information on each endpoint and their responses, please refer to the [LI.FI API](https://docs.li.fi/li.fi-api/li.fi-api) documentation.
# Configure SDK
Source: https://docs.li.fi/sdk/configure-sdk
Get started and set up LI.FI SDK in just a few lines of code.
## Create Config
To get started, you need to create an initial configuration for the LI.FI SDK. This configuration contains the shared settings and data required for the proper functioning of other SDK features that developers will use. Additionally, the configuration can be updated later as needed.
```typescript theme={"system"}
import { createConfig } from "@lifi/sdk";
createConfig({
integrator: "Your dApp/company name",
});
```
## Parameters
| Parameter | Required | Default | Description |
| --------------------- | -------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `integrator` | Yes | | LI.FI SDK requires an integrator option to identify partners and allows them to monitor their activity on the partner dashboard, such as the transaction volume, enabling better management and support. Usually, the integrator option is your dApp or company name. This string must consist only of letters, numbers, hyphens, underscores, and dots and be a maximum of 23 characters long. |
| `apiKey` | No | | Unique API key for accessing LI.FI API services. Necessary for higher rate limits. Read more Rate Limits & API Key |
| `apiUrl` | No | `https://li.quest/v1` | The base URL for the LI.FI API. This is the endpoint through which all API requests are routed. It can be changed to the staging environment to test new features, for example. |
| `userId` | No | | A unique identifier for the user of your application. This can be used to track user-specific data and interactions within the LI.FI. |
| `routeOptions` | No | | Custom options for routing, applied when using `getQuote`, `getRoutes`, and `getContractCallsQuote` endpoints. These options can be configured once during SDK initialization or passed each time those functions are called. |
| `rpcUrls` | No | | A mapping of chain IDs to arrays of RPC URLs. These URLs might be used for transaction execution and data retrieval. |
| `chains` | No | fetched from LI.FI API during initialization | An array of chains that the SDK will support. Each chain must be configured with necessary details like chain ID, name, RPCs, etc. This information is used during the quote and route execution. |
| `preloadChains` | No | `true` | A flag indicating whether to preload chain configurations. By default, the SDK will load chain details during initialization. |
| `disableVersionCheck` | No | `false` | A flag to disable version checking of the SDK. By default, the SDK checks its version on initialization and logs a message in the console if a new version is available, prompting the user to update the SDK. |
| `providers` | No | | An array of provider configurations is used by the SDK. Providers are optional and only necessary if you plan to execute quotes or routes through the SDK. Read more [Configure SDK Providers](/sdk/configure-sdk-providers). |
To learn how to use `routeOptions` for monetization, including configuring
fees, see the [Monetize the SDK](/sdk/monetize-sdk) guide.
Setting up providers is not required if you are using the SDK solely to access
the LI.FI API without quote/route SDK execution functionality and plan to
handle the execution independently.
## Setting custom RPC URLs
```typescript theme={"system"}
import { createConfig, ChainId } from "@lifi/sdk";
createConfig({
integrator: "Your dApp/company name",
rpcUrls: {
[ChainId.ARB]: ["https://arbitrum-example.node.com/"],
[ChainId.SOL]: ["https://solana-example.node.com/"],
},
});
```
In a production app, it is recommended to pass through your authenticated RPC provider URL (Alchemy, Infura, Ankr, etc).
If no RPC URLs are provided, LI.FI SDK will default to public RPC providers.
Public RPC endpoints (especially Solana) can sometimes rate-limit users depending on location or during periods of heavy load, leading to issues such as incorrectly displaying balances or errors with transaction simulation.
## Update SDK configuration
LI.FI SDK provides various methods to manage and manipulate the SDK settings dynamically. To update the configuration, you need to import the global configuration object and use its methods.
```typescript theme={"system"}
import { config } from "@lifi/sdk";
```
After creating your configuration with the `createConfig` function, `config` acts as a global configuration object.
## Configuration Methods
| Method | Description |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get()` | Returns the current SDK configuration. |
| `set(options: SDKConfig)` | Sets the SDK configuration with the provided options. |
| `setProviders(providers: SDKProvider[])` | Sets the providers in the SDK configuration. If a provider already exists, it will be updated with the new information. |
| `getChains()` | Returns a promise that resolves to the list of configured chains. If the configuration is still loading, the promise will wait until the loading is complete. |
| `setChains(chains: ExtendedChain[])` | Sets the chains in the SDK configuration and updates chains RPC URLs. This method also clears the loading state. |
| `getChainById(chainId: ChainId)` | Returns a promise that resolves to the chain configuration for the specified chain ID. If the configuration is still loading, the promise will wait until the loading is complete. |
| `getRPCUrls()` | Returns a promise that resolves to the list of RPC URLs for the configured chains. If the configuration is still loading, the promise will wait until the loading is complete. |
| `setRPCUrls(rpcUrls: RPCUrls)` | Sets the RPC URLs for the chains in the SDK configuration. If some RPC URLs already exist for a chain, the new URLs will be appended to the existing ones. |
# Multi-VM Support
Source: https://docs.li.fi/sdk/configure-sdk-providers
Seamlessly connecting every ecosystem for your needs
## Introduction to SDK Ecosystem Providers
The LI.FI SDK supports different blockchain ecosystems, allowing you to integrate with EVM and Solana networks, with more ecosystems on the way. Internally, providers act as abstractions for each ecosystem, handling crucial tasks such as address resolution, balance checking, and transaction handling during route/quote execution.
These ecosystem providers are designed with modularity in mind and are fully tree-shakable, ensuring that they do not add unnecessary weight to your bundle if not used.
The SDK offers four providers, `EVM`, `Solana`, `UTXO` and `Sui`, each with similar configuration options respective to their ecosystems.
```typescript theme={"system"}
import { EVM, Solana, Sui, UTXO } from '@lifi/sdk'
```
The setup for all providers focuses on utilizing a wallet client, wallet adapter, or a similar wallet interface concept depending on the ecosystem-specific libraries and standards. This unified approach simplifies managing wallets and transactions across EVM-compatible, Solana, and Sui chains.
### Different types of wallets/accounts
To execute `GET /quote` or `POST /advanced/routes` via a specific provider, that provider must be capable of signing transactions. SDK providers support signing transactions over the following types of wallets/accounts:
* **Local Accounts (e.g. private key/mnemonic wallets).**
Local accounts are wallets managed using private keys or mnemonic phrases. This setup is often used in backend services or scenarios where automated signing and transaction management are required.
* **JSON-RPC Accounts (e.g. Browser Extension Wallets, WalletConnect, etc.).**
Using JSON-RPC accounts involves connecting through a Web3 provider, e.g. `window.ethereum`, and managing the user's account within the browser or mobile context. This setup is popular among dApps UIs and is often used together with libraries like `Wagmi`, `@solana/web3.js`, or `@mysten/dapp-kit`.
These account types and interaction methods allow developers to choose the most suitable approach for integrating the SDK with their applications.
## Setup EVM Provider
The EVM provider execution logic is built based on the `Viem` library, using some of its types and terminology.
**Options available for configuring the EVM provider:**
* `getWalletClient`: A function that returns a `WalletClient` instance.
* `switchChain`: A hook for switching between different networks.
### Local Accounts
When using local accounts, developers need a predefined list of chains they plan to interact with in order to switch chains during transaction execution. These chains can be either from the `viem/chains` package or fetched from LI.FI API and adopted to viem's `Chain` type.
Here's a basic example using chains from `viem/chains`:
```typescript theme={"system"}
import { createConfig, EVM } from '@lifi/sdk'
import type { Chain } from 'viem'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { arbitrum, mainnet, optimism, polygon, scroll } from 'viem/chains'
const account = privateKeyToAccount('PRIVATE_KEY')
const chains = [arbitrum, mainnet, optimism, polygon, scroll]
const client = createWalletClient({
account,
chain: mainnet,
transport: http(),
})
createConfig({
integrator: 'Your dApp/company name',
providers: [
EVM({
getWalletClient: async () => client,
switchChain: async (chainId) =>
// Switch chain by creating a new wallet client
createWalletClient({
account,
chain: chains.find((chain) => chain.id == chainId) as Chain,
transport: http(),
}),
}),
],
})
```
### JSON-RPC Accounts
The best way to interact with JSON-RPC accounts and pass `WalletClient` to the `EVM` provider is to use the [Wagmi](https://wagmi.sh/) library. Developers can configure Wagmi chains either by using chains from the `viem/chains` package or fetching chains from LI.FI API and adapting them to Viem's `Chain` type.
Below is a simplified example of how to set up the EVM provider using chains from the LI.FI API in conjunction with Wagmi and React.
We provide a `useSyncWagmiConfig` hook that synchronizes fetched chains with the Wagmi configuration and updates connectors. Please note that we do not initialize the Wagmi configuration with connectors. Additionally, we set `reconnectOnMount` to `false` since the `reconnect` action will be called within `useSyncWagmiConfig` hook after the chains are synchronized with the configuration and connectors.
```typescript theme={"system"}
import { ChainType, EVM, config, createConfig, getChains } from '@lifi/sdk';
import { useSyncWagmiConfig } from '@lifi/wallet-management';
import { useQuery } from '@tanstack/react-query';
import { getWalletClient, switchChain } from '@wagmi/core';
import { type FC, type PropsWithChildren } from 'react';
import { createClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import type { Config, CreateConnectorFn } from 'wagmi';
import { WagmiProvider, createConfig as createWagmiConfig } from 'wagmi';
import { injected } from 'wagmi/connectors';
// List of Wagmi connectors
const connectors: CreateConnectorFn[] = [injected()];
// Create Wagmi config with default chain and without connectors
const wagmiConfig: Config = createWagmiConfig({
chains: [mainnet],
client({ chain }) {
return createClient({ chain, transport: http() });
},
});
// Create SDK config using Wagmi actions and configuration
const config = createConfig({
integrator: 'Your dApp/company name',
providers: [
EVM({
getWalletClient: () => getWalletClient(wagmiConfig),
switchChain: async (chainId) => {
const chain = await switchChain(wagmiConfig, { chainId });
return getWalletClient(wagmiConfig, { chainId: chain.id });
},
}),
],
// We disable chain preloading and will update chain configuration in runtime
preloadChains: false,
});
export const CustomWagmiProvider: FC = ({ children }) => {
// Load EVM chains from LI.FI API using getChains action from LI.FI SDK
const { data: chains } = useQuery({
queryKey: ['chains'] as const,
queryFn: async () => {
const chains = await getChains({
chainTypes: [ChainType.EVM],
});
// Update chain configuration for LI.FI SDK
config.setChains(chains);
return chains;
},
});
// Synchronize fetched chains with Wagmi config and update connectors
useSyncWagmiConfig(wagmiConfig, connectors, chains);
return (
{children}
);
};
```
### Update provider configuration
Additionally, providers allow for dynamic updates to its initial configuration via the `setOptions` function.
Here's an example of how to modify the initial configuration for `EVM` provider:
```typescript theme={"system"}
import { createConfig, EVM } from '@lifi/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { arbitrum, mainnet } from 'viem/chains'
const account = privateKeyToAccount('PRIVATE_KEY')
const mainnetClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
})
const evmProvider = EVM({
getWalletClient: async () => mainnetClient,
})
createConfig({
integrator: 'Your dApp/company name',
providers: [evmProvider],
})
const optimismClient = createWalletClient({
account,
chain: arbitrum,
transport: http(),
})
evmProvider.setOptions({
getWalletClient: async () => optimismClient,
})
```
### Support for Ethers.js and other alternatives
Developers can still use Ethers.js or any other alternative Web3 library in their project and [convert](https://viem.sh/docs/ethers-migration) `Signer`/`Provider` objects to Viem's `WalletClient` before passing it to the EVM provider configuration.
## Setup Solana Provider
The Solana provider execution logic is built based on the [@solana/web3.js](https://solana-labs.github.io/solana-web3.js/) and [@solana/wallet-adapter-base](https://github.com/anza-xyz/wallet-adapter) libraries, using some of its types and terminology.
**Options available for configuring the EVM provider:**
* `getWalletAdapter`: A function that returns a WalletAdapter instance.
### Local Wallet Adapter
Standard Solana libraries do not offer a built-in method for creating a wallet adapter directly from a private key. To address this limitation, we provide the `KeypairWalletAdapter`. This custom adapter enables users to create a wallet adapter from a private key.
It is worth noting that the `KeypairWalletAdapter` is designed specifically for backend or testing purposes and should not be used in user-facing code to prevent the risk of exposing your private key.
```typescript theme={"system"}
import { createConfig, KeypairWalletAdapter, Solana } from '@lifi/sdk'
const walletAdapter = new KeypairWalletAdapter('PRIVATE_KEY')
createConfig({
integrator: 'Your dApp/company name',
providers: [
Solana({
getWalletAdapter: async () => walletAdapter,
}),
],
})
```
### JSON-RPC Wallet Adapter
To interact with JSON-RPC accounts and pass `WalletAdapter` to the Solana provider, we recommend using the [@solana/wallet-adapter-base](https://github.com/anza-xyz/wallet-adapter) and [@solana/wallet-adapter-react](https://github.com/anza-xyz/wallet-adapter) libraries. Unlike Wagmi, Solana configuration for React does not have global configurations. Therefore, we need to use React hooks to update the SDK configuration at runtime.
Below is a simplified example of how to set up the Solana provider.
```typescript SDKProviders.tsx theme={"system"}
import { Solana, config, createConfig } from '@lifi/sdk';
import type { SignerWalletAdapter } from '@solana/wallet-adapter-base';
import { useWallet } from '@solana/wallet-adapter-react';
import { useEffect } from 'react';
createConfig({
integrator: 'Your dApp/company name',
});
export const SDKProviders = () => {
const { wallet } = useWallet();
useEffect(() => {
// Configure SDK Providers
config.setProviders([
Solana({
async getWalletAdapter() {
return wallet?.adapter as SignerWalletAdapter;
},
}),
]);
}, [wallet?.adapter]);
return null;
};
```
```typescript SolanaProvider.tsx theme={"system"}
import type { Adapter } from '@solana/wallet-adapter-base';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import {
ConnectionProvider,
WalletProvider,
} from '@solana/wallet-adapter-react';
import { clusterApiUrl } from '@solana/web3.js';
import { type FC, type PropsWithChildren } from 'react';
import { SDKProviders } from './SDKProviders.js';
const endpoint = clusterApiUrl(WalletAdapterNetwork.Mainnet);
/**
* Wallets that implement either of these standards will be available automatically.
*
* - Solana Mobile Stack Mobile Wallet Adapter Protocol
* (https://github.com/solana-mobile/mobile-wallet-adapter)
* - Solana Wallet Standard
* (https://github.com/solana-labs/wallet-standard)
*
* If you wish to support a wallet that supports neither of those standards,
* instantiate its legacy wallet adapter here. Common legacy adapters can be found
* in the npm package `@solana/wallet-adapter-wallets`.
*/
const wallets: Adapter[] = [];
export const SVMBaseProvider: FC = ({ children }) => {
return (
{/* Configure Solana SDK provider */}
{children}
);
};
```
## Setup Sui Provider
The Sui provider execution logic is built based on the [@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit) and [@mysten/sui](https://sdk.mystenlabs.com/typescript) libraries, using some of its types and terminology.
**Options available for configuring the Sui provider:**
* `getWallet`: A function that returns a `WalletWithRequiredFeatures` instance (defined by `@mysten/wallet-standard`).
### JSON-RPC Wallet
To interact with user wallets (like Sui Wallet, Ethos, etc.) and pass `WalletWithRequiredFeatures` to the Sui provider, we recommend using the [@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit) library.
Below is a simplified example of how to set up the Sui provider with user wallets.
```typescript SDKProviders.tsx theme={"system"}
import { Sui, config, createConfig } from '@lifi/sdk';
import { useCurrentWallet } from '@mysten/dapp-kit';
import { useEffect } from 'react';
createConfig({
integrator: 'Your dApp/company name',
});
export const SDKProviders = () => {
const { currentWallet } = useCurrentWallet();
useEffect(() => {
// Configure Sui SDK provider
config.setProviders([
Sui({
async getWallet() {
return currentWallet!;
},
}),
]);
}, [currentWallet]);
return null;
};
```
```typescript SuiProvider.tsx theme={"system"}
import { SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { getFullnodeUrl } from '@mysten/sui/client';
import { type FC, type PropsWithChildren } from 'react';
import { SDKProviders } from './SDKProviders.js';
const { networkConfig } = createNetworkConfig({
mainnet: { url: getFullnodeUrl('mainnet') },
});
export const SuiBaseProvider: FC = ({ children }) => {
return (
{/* Configure Sui SDK provider */}
{children}
);
};
```
## Setup UTXO (Bitcoin) Provider
The Bitcoin provider execution logic is built based on the [Bigmi](https://github.com/lifinance/bigmi) library, using some of its types and terminology.
**Options available for configuring the UTXO provider:**
* `getWalletClient`: A function that returns a `Client` instance
### JSON-RPC Wallet
To interact with user wallets like Phantom, Xverse, use the `getConnectorClient` action to return the Bigmi Client object required by the SDK.
```typescript SDKProvider.tsx theme={"system"}
import { createConfig, UTXO } from '@lifi/sdk'
import { getConnectorClient } from '@bigmi/client'
import { useConfig } from '@bigmi/react'
const config = createConfig({
integrator: 'Your dApp/company name',
})
export const SDKProviders = () => {
const bigmiConfig = useConfig();
useEffect(() => {
// Configure SDK Provider
config.setProviders([
UTXO({
async getWalletClient() {
return getConnectorClient(bigmiConfig)
},
}),
]);
}, [bigmiConfig]);
return null;
};
```
```typescript UTXOProvider.tsx theme={"system"}
import { bitcoin, createClient, http } from '@bigmi/core'
import { createConfig, phantom, getConnectorClient, type CreateConnectorFn, type Config } from '@bigmi/client'
import { BigmiProvider } from '@bigmi/react'
import { SDKProvider } from './SDKProvider.js'
const connectors: CreateConnectorFn[] = [phantom()]
const bigmiConfig = createConfig({
chains: [bitcoin],
connectors,
client({ chain }) {
return createClient({ chain, transport: http() })
}
}) as Config
export const UTXOProvider: FC = ({ children }) => {
return (
{ children }
)
}
```
# Execute Routes/Quotes
Source: https://docs.li.fi/sdk/execute-routes
We allow you to execute any on-chain or cross-chain swap and bridging transfer and a combination of both.
The LI.FI SDK offers functionality to execute routes and quotes. In this guide, you'll learn how to utilize the SDK's features to handle complex cross-chain transfers, manage execution settings, and control the transaction flow.
## Execute route
Let's say you have obtained the route. Refer to [Request Routes/Quotes](https://docs.li.fi/integrate-li.fi-sdk/request-routes-quotes) for more details.
Please make sure you've configured SDK with EVM/Solana providers. Refer to [Configure SDK Providers](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk-providers) for more details.
Now, to execute the route, we can use the `executeRoute` function. Here is a simplified example of how to use it:
```typescript theme={"system"}
import { executeRoute, getRoutes } from '@lifi/sdk'
const result = await getRoutes({
fromChainId: 42161, // Arbitrum
toChainId: 10, // Optimism
fromTokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toTokenAddress: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // DAI on Optimism
fromAmount: '10000000', // 10 USDC
// The address from which the tokens are being transferred.
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0',
})
const route = result.routes[0]
const executedRoute = await executeRoute(route, {
// Gets called once the route object gets new updates
updateRouteHook(route) {
console.log(route)
},
})
```
The `executeRoute` function internally manages allowance and balance checks, chain switching, transaction data retrieval, transactions submission, and transactions status tracking.
* **Parameters:**
* `route` (`Route`): The route to be executed.
* `executionOptions` (`ExecutionOptions`, optional): An object containing settings and callbacks for execution.
* **Returns:**
* `Promise`: Resolves when execution is done or halted and rejects when it is failed.
### Execution Options
All execution options are optional, but we recommend reviewing their descriptions to determine which ones may be beneficial for your use case.
Certain options, such as [acceptExchangeRateUpdateHook](https://docs.li.fi/integrate-li.fi-sdk/execute-routes-quotes#acceptexchangerateupdatehook), can be crucial for successfully completing a transfer if the exchange rate changes during the process.
#### `updateRouteHook`
The function is called when the route object changes during execution. This function allows you to handle route updates, track execution status, transaction hashes, etc. See [Monitor route execution](https://docs.li.fi/integrate-li.fi-sdk/execute-routes-quotes#monitor-route-execution) section for more details.
* **Parameters**:
* `updatedRoute` (`RouteExtended`): The updated route object.
#### `updateTransactionRequestHook`
The function is intended for advanced usage, and it allows you to modify swap/bridge transaction requests or token approval requests before they are sent, e.g., updating gas information.
* **Parameters**:
* `updatedTxRequest` (`TransactionRequestParameters`): The transaction request parameters need to be updated.
* **Returns**: `Promise`: The modified transaction parameters.
#### `acceptExchangeRateUpdateHook`
This function is called whenever the exchange rate changes during a swap or bridge operation. It provides you with the old and new amount values. To continue the execution, you should return `true`. If this hook is not provided or if you return `false`, the SDK will throw an error. This hook is an ideal place to prompt your users to accept the new exchange rate.
* **Parameters**:
* `toToken` (`Token`): The destination token.
* `oldToAmount` (`string`): The previous amount of the target token.
* `newToAmount` (`string`): The new amount of the target token.
* **Returns**: `Promise`: Whether the update is accepted.
* **Throws:** `TransactionError: Exchange rate has changed!`
#### `switchChainHook`
* **Parameters**:
* `chainId` (`number`): The ID of the chain to which to switch.
* **Returns**: `Promise`: The new wallet client after switching chains.
#### `executeInBackground`
A boolean flag indicating whether the route execution should continue in the background without requiring user interaction. See [Update route execution](https://docs.li.fi/integrate-li.fi-sdk/execute-routes-quotes#update-route-execution) and [Resume route execution](https://docs.li.fi/integrate-li.fi-sdk/execute-routes-quotes#resume-route-execution) sections for details on how to utilize this option.
* **Type**: `boolean`
* **Default**: `false`
#### `disableMessageSigning`
A boolean flag indicating whether to disable message signing during execution. Certain operations require signing EIP-712 messages, including Permit approvals (ERC-2612) and gasless transactions. This functionality may not be compatible with all smart accounts or wallets, in which case this flag should be set to true to disable message signing.
EIP-7702 delegated smart wallets currently require source-chain native gas because gasless or relayer routes are not offered for this wallet type. See [EIP-7702 delegated wallet troubleshooting](/faqs/troubleshooting#eip-7702-delegated-smart-wallets).
* **Type**: `boolean`
* **Default**: `false`
## Manage route execution
After starting route execution, there might be use cases when you need to adjust execution settings, stop execution and come back later, or move execution to the background. We provide several functions to achieve that.
### Update route execution
The `updateRouteExecution` function is used to update the settings of an ongoing route execution.
One common use case is to push the execution to the background, for example, when a user navigates away from the execution page in your dApp. When this function is called, the execution will continue until it requires user interaction (e.g., signing a transaction or switching the chain). At that point, the execution will halt, and the `executeRoute` promise will be resolved.
To move the execution back to the foreground and make it active again, you can call `resumeRoute` with the same route object. The execution will then resume from where it was halted.
```typescript theme={"system"}
import { updateRouteExecution } from '@lifi/sdk'
updateRouteExecution(route, { executeInBackground: true });
```
* **Parameters:**
* `route` (`Route`): The active route to be updated.
* `executionOptions` (`ExecutionOptions`, **required**): An object containing settings and callbacks for execution.
### Resume route execution
The `resumeRoute` function is used to resume a halted, aborted, or failed route execution from the point where it stopped. It is crucial to call `resumeRoute` with the latest active route object returned from the `executeRoute` function or the most recent version of the updated route object from the `updateRouteHook`.
#### Common Use Cases
* **Move Execution to Foreground**: When a user navigates back to the execution page in your dApp, you can call this function to move the execution back to the foreground. The execution will resume from where it was halted.
* **Page Refresh**: If the user refreshes the page in the middle of the execution process, calling this function will attempt to resume the execution.
* **User Interaction Errors**: If the user rejects a chain switch, declines to sign a transaction, or encounters any other error, you can call this function to attempt to resume the execution.
```typescript theme={"system"}
import { resumeRoute } from '@lifi/sdk'
const route = await resumeRoute(route, { executeInBackground: false });
```
* **Parameters:**
* `route` (`Route`): The route to be resumed to execution.
* `executionOptions` (`ExecutionOptions`, optional): An object containing settings and callbacks for execution.
* **Returns:**
* `Promise`: Resolves when execution is done or halted and rejects when it is failed.
### Stop route execution
The `stopRouteExecution` function is used to stop the ongoing execution of an active route. It stops any remaining user interaction within the ongoing execution and removes the route from the execution queue. However, if a transaction has already been signed and sent by the user, it will be executed on-chain.
```typescript theme={"system"}
import { stopRouteExecution } from '@lifi/sdk'
const stoppedRoute = stopRouteExecution(route);
```
* **Parameters:**
* `route` (`Route`): The route that is currently being executed and needs to be stopped.
* **Returns:**
* `Route`: The route object that was stopped.
## Monitor route execution
Monitoring route execution is important and we provide tools for tracking progress, receiving data updates, accessing transaction hashes, and explorer links.
### Brief description of steps
A `route` object includes multiple `step` objects, each representing a set of transactions that should be completed in the specified order. Each step can include multiple transactions that require a signature, such as an allowance transaction followed by the main swap or bridge transaction. Read more [LI.FI Terminology](https://docs.li.fi/overview/li.fi-terminology).
### Understanding the `execution` object
Each `step` within a `route` has an `execution` object. This object contains all the necessary information to track the execution progress of that step. The `execution` object has a `process` array where each entry represents a sequential stage in the execution. The latest process entry contains the most recent information about the execution stage.
### Process array
The `process` array within the `execution` object details each step’s progression. Each `process` object has a type and status and might also include a transaction hash and a link to a blockchain explorer after the user signs the transaction.
### Tracking progress
To monitor the execution progress, you leverage the `updateRouteHook` callback and iterate through the route steps, checking their `execution` objects. Look at the `process` array to get the latest information about the execution stage. The most recent entry in the `process` array will contain the latest transaction hash, status, and other relevant details.
### Example to access transaction hashes
```typescript theme={"system"}
const getTransactionLinks = (route: RouteExtended) => {
route.steps.forEach((step, index) => {
step.execution?.process.forEach((process) => {
if (process.txHash) {
console.log(
`Transaction Hash for Step ${index + 1}, Process ${process.type}:`,
process.txHash
)
}
})
})
}
const executedRoute = await executeRoute(route, {
updateRouteHook(route) {
getTransactionLinks(route)
},
})
```
### Get active routes
To get routes that are currently being executed (active), you can use `getActiveRoutes` and `getActiveRoute` functions.
```typescript theme={"system"}
import { getActiveRoute, getActiveRoutes, RouteExtended } from '@lifi/sdk'
const activeRoutes: RouteExtended[] = getActiveRoutes();
const routeId = activeRoutes[0].routeId;
const activeRoute = getActiveRoute(routeId);
```
## Execute quote
To execute a quote using the `executeRoute`, you need to convert it to a route object first. We provide `convertQuoteToRoute` helper function to transform quote objects to route objects. This applies to both standard and contract call quotes.
```typescript theme={"system"}
import { convertQuoteToRoute, executeRoute, getQuote } from '@lifi/sdk';
const quoteRequest: QuoteRequest = {
fromChain: 42161, // Arbitrum
toChain: 10, // Optimism
fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // DAI on Optimism
fromAmount: '10000000', // 10 USDC
// The address from which the tokens are being transferred.
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0',
};
const quote = await getQuote(quoteRequest);
const route = convertQuoteToRoute(quote)
const executedRoute = await executeRoute(route, {
// Gets called once the route object gets new updates
updateRouteHook(route) {
console.log(route)
},
})
```
## Manual route execution
In addition to using the `executeRoute` function, you can execute routes and quotes manually. This approach requires developers to handle the logic for obtaining transaction data, switching chains, sending transactions, and tracking transaction status independently.
Initially, when route objects are requested, they do not include transaction data. This is because multiple route options are provided, and generating transaction data for all options would substantially delay the response. Each route consists of multiple steps, and once a user selects a route, transaction data for each step should be requested individually using the `getStepTransaction` function (see example below). Each step should be executed sequentially, as each step depends on the outcome of the previous one.
On the other hand, quote objects are returned with transaction data included, so the `getStepTransaction` call is not necessary, and they can be executed immediately.
After sending a transaction using the obtained transaction data, you can track the status of the transaction using the `getStatus` function. This function helps you monitor the progress and completion of each transaction. Read more [Status of a Transaction](https://docs.li.fi/li.fi-api/li.fi-api/status-of-a-transaction).
Here's a simplified example. For the sake of simplicity, this example omits balance checks, transaction replacements, error handling, chain switching, etc. However, in a real implementation, you should include these additional functionalities to have a robust solution and ensure reliability.
```typescript theme={"system"}
import { getStepTransaction, getStatus } from '@lifi/sdk';
// Simplified example function to execute each step of the route sequentially
async function executeRouteSteps(route) {
for (const step of route.steps) {
// Request transaction data for the current step
const step = await getStepTransaction(step);
// Send the transaction (e.g. using Viem)
const transactionHash = await sendTransaction(step.transactionRequest);
// Monitor the status of the transaction
let status;
do {
const result = await getStatus({
txHash: transactionHash,
fromChain: step.action.fromChainId,
toChain: step.action.toChainId,
bridge: step.tool,
})
status = result.status
console.log(`Transaction status for ${transactionHash}:`, status);
// Wait for a short period before checking the status again
await new Promise(resolve => setTimeout(resolve, 5000));
} while (status !== 'DONE' && status !== 'FAILED');
if (status === 'FAILED') {
console.error(`Transaction ${transactionHash} failed`);
return;
}
}
console.log('All steps executed successfully');
}
```
#### `getStepTransaction`
* **Parameters:**
* `step` (`LiFiStep`): The step object for which we need to get transaction data.
* `options` (`RequestOptions`, optional): An object containing request options, such as `AbortSignal`, which can be used to cancel the request if necessary.
* **Returns:**
* `Promise`: A promise that resolves to the step object containing the transaction data.
#### `getStatus`
* **Parameters:**
* `params` (`GetStatusRequest`): The parameters for checking the status include the transaction hash, source and destination chain IDs, and the DEX or bridge name.
* `options` (`RequestOptions`, optional): An object containing request options, such as `AbortSignal`, which can be used to cancel the request if necessary.
* **Returns:**
* `Promise`: A promise that resolves to a status response containing all relevant information about the transfer.
# Install LI.FI SDK
Source: https://docs.li.fi/sdk/installing-the-sdk
Integrate our LI.FI SDK to your dApp/Wallet/Swap UI
The LI.FI SDK package provides access to the LI.FI API to find and execute the best on-chain and cross-chain routes across various bridges and exchanges.
## Installation
```typescript yarn theme={"system"}
yarn add @lifi/sdk
```
```typescript pnpm theme={"system"}
pnpm add @lifi/sdk
```
```typescript bun theme={"system"}
bun add @lifi/sdk
```
```typescript npm theme={"system"}
npm install @lifi/sdk
```
Check out our complete examples in the [SDK repository](https://github.com/lifinance/sdk/tree/main/examples), and feel free to [file an issue](https://github.com/lifinance/sdk/issues) if you encounter any problems.
## Quick Start
Firstly, create SDK config with your integrator string.
```typescript theme={"system"}
import { createConfig } from '@lifi/sdk'
createConfig({
integrator: 'Your dApp/company name',
})
```
Now you can interact with the SDK and for example request a quote.
```typescript theme={"system"}
import { ChainId, getQuote } from '@lifi/sdk'
const quote = await getQuote({
fromAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
fromChain: ChainId.ARB,
toChain: ChainId.OPT,
fromToken: '0x0000000000000000000000000000000000000000',
toToken: '0x0000000000000000000000000000000000000000',
fromAmount: '1000000000000000000',
})
```
Firstly, create SDK config with your integrator string.
```JS theme={"system"}
import { createConfig } from '@lifi/sdk'
createConfig({
integrator: 'Your dApp/company name',
})
```
## Request a Quote
Now you can interact with the SDK and for example request a quote.
```JS theme={"system"}
import { ChainId, getQuote } from '@lifi/sdk'
const quote = await getQuote({
fromAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
fromChain: ChainId.ARB,
toChain: ChainId.OPT,
fromToken: '0x0000000000000000000000000000000000000000',
toToken: '0x0000000000000000000000000000000000000000',
fromAmount: '1000000000000000000',
})
```
You can learn more about the configuring the SDK in the next section.
# Migrate from v2 to v3
Source: https://docs.li.fi/sdk/migrate-v2-to-v3
Migration guide for upgrading LI.FI SDK v2 to v3
## Overview
**LI.FI SDK v3** has undergone a major update, improving compatibility with popular libraries like [Viem](https://viem.sh/) and adding new features, including support for multiple ecosystems, starting with [Solana](https://github.com/anza-xyz/wallet-adapter). Consequently, as detailed in this guide, you need to be aware of some breaking changes and deprecations. We also recommend reviewing the updated documentation for additional new features not covered here.
To get started, install the latest version of LI.FI SDK.
```typescript yarn theme={"system"}
yarn add @lifi/sdk
```
```typescript pnpm theme={"system"}
pnpm add @lifi/sdk
```
```typescript bun theme={"system"}
bun add @lifi/sdk
```
```typescript npm theme={"system"}
npm install @lifi/sdk
```
## Configuration
We have made significant changes to how the LI.FI SDK is configured. It is no longer class-based, so you don't need to create, maintain, and share a LiFi class instance to use SDK functionality. Instead, it is now function-based, allowing you to configure it once and update the configuration from any place without needing to maintain or share the configuration object's reference. You can simply import the necessary functions from the package wherever needed. Read more [Configure SDK](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk)
In this example, you can call the `getQuote` function from anywhere using SDK v3, whereas with SDK v2, you had to share a created `LiFi` class instance and call `getQuote` as a method of that class.
```typescript theme={"system"}
// SDK v2
import { ChainId, LiFi } from '@lifi/sdk'
const lifi = new LiFi({
integrator: 'Your dApp/company name'
})
const quote = await lifi.getQuote({
fromAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
fromChain: ChainId.ARB,
toChain: ChainId.OPT,
fromToken: '0x0000000000000000000000000000000000000000',
toToken: '0x0000000000000000000000000000000000000000',
fromAmount: '1000000000000000000',
})
// SDK v3
import { ChainId, createConfig, getQuote } from '@lifi/sdk'
createConfig({
integrator: 'Your dApp/company name',
})
const quote = await getQuote({
fromAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
fromChain: ChainId.ARB,
toChain: ChainId.OPT,
fromToken: '0x0000000000000000000000000000000000000000',
toToken: '0x0000000000000000000000000000000000000000',
fromAmount: '1000000000000000000',
})
```
Due to these changes, the configuration-related functions from the `LiFi` interface are no longer needed. The previous interface included:
```typescript theme={"system"}
interface LiFi {
getConfig(): Config
getConfigAsync(): Promise
getRpcProvider(chainId: number, archive: boolean): Promise
setConfig(configUpdate: ConfigUpdate): Config
// ...
}
```
Now, you can import the configuration object directly and make any necessary changes to it. This simplifies the process and improves code maintainability. Read more [Update SDK configuration](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk#update-sdk-configuration).
```typescript theme={"system"}
// SDK v2
import { ChainId, LiFi } from '@lifi/sdk'
const lifi = new LiFi({
integrator: 'Your dApp/company name'
})
lifi.setConfig({
integrator: 'Your dApp/company name'
})
// SDK v3
import { config } from '@lifi/sdk';
config.set({
integrator: 'Your dApp/company name',
});
```
## Renamed methods
All methods previously supported by the class-based approach are now individual functions that you can import directly from the `@lifi/sdk` package. Some of these methods have been renamed to better reflect their functionality or to have a more concise name.
* `getContractCallQuote` -> `getContractCallsQuote`
* `getTokenApproval` -> `getTokenAllowance`
* `bulkGetTokenApproval` -> `getTokenAllowanceMulticall`
* `approveToken` -> `setTokenAllowance`
* `moveExecutionToBackground` -> `updateRouteExecution`
See [Request contract call Quote](https://docs.li.fi/integrate-li.fi-sdk/request-routes-quotes#request-contract-call-quote), [Manage route execution](https://docs.li.fi/integrate-li.fi-sdk/execute-routes-quotes#manage-route-execution) and [Token Management](https://docs.li.fi/integrate-li.fi-sdk/token-management) for more details.
## Moving from Ethers.js to Viem
Since the first version of the LI.FI SDK, we have relied on the `Ethers.js` library for all EVM-related interactions. However, as the industry evolves, `Viem` is gaining wide adoption and starting to replace `Ethers.js`.
To ensure our product remains robust and future-proof, we have decided to transition our entire stack to a more reliable solution - [`Viem`](https://viem.sh/) ([`Wagmi`](https://wagmi.sh/) for the Widget).
Please see the [Ethers v5 → viem Migration Guide](https://viem.sh/docs/ethers-migration) for more details. Among other notable changes, this transition also replaces all BigNumber utilities we previously used in v2 with the native `bigint` primitive.
## Multiple ecosystem support
LI.FI SDK v3 now supports two ecosystems — EVM and Solana — with more on the way. To accommodate these new ecosystems, we have introduced the concept of ecosystem providers in SDK v3. We extracted all EVM-related functionality from SDK v2 and integrated it into an EVM provider. Additionally, a Solana provider has been added to enable Solana-related functionality across all SDK functions.
These ecosystem providers are designed with modularity in mind and are fully tree-shakable, ensuring that they do not add unnecessary weight to your bundle if not used.
Previously, it was necessary to pass an `Ethers.js` Signer object to `executeRoute` and other functions for EVM interactions. With the introduction of ecosystem providers, you only need to configure them once when setting up the SDK. After that, you can use `executeRoute` and other functions without passing any additional options. The SDK will automatically determine the appropriate ecosystem provider and notify you if no suitable provider is configured.
Read more [Introduction to SDK Ecosystem Providers](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk-providers#introduction-to-sdk-ecosystem-providers).
## Examples
Check out our complete examples in the [SDK repository](https://github.com/lifinance/sdk/tree/main/examples), and feel free to [file an issue](https://github.com/lifinance/sdk/issues) if you encounter any problems.
## Changelog
For a detailed view of all the changes, please see the [CHANGELOG](https://github.com/lifinance/sdk/blob/main/CHANGELOG.md).
# Monetize SDK
Source: https://docs.li.fi/sdk/monetize-sdk
Learn how to configure fees and monetize your LI.FI SDK integration.
For more details about how fees work, fee collection on different chains, and
setting up fee wallets, see the [Monetizing the
integration](/introduction/integrating-lifi/monetizing-integration) guide.
When using the LI.FI SDK, you can monetize your integration by collecting fees from the transactions processed through your application. The SDK supports fee configuration in two ways: globally when creating the SDK config, or per-request through route options.
## Global fee configuration
The recommended approach is to configure fees globally when creating your SDK config using `createConfig`. This ensures all requests use the same fee configuration automatically:
```typescript theme={"system"}
import { createConfig } from "@lifi/sdk";
const config = createConfig({
integrator: "Your dApp/company name",
routeOptions: {
fee: 0.01, // 1% fee applied to all requests
},
// other options...
});
```
With this configuration, all route requests, quotes, and contract calls will automatically include the specified fee without needing to add it to each individual request.
## Per-request fee configuration
Alternatively, you can configure fees on a per-request basis. This is useful when you need different fee rates for different types of transactions or users. The `fee` parameter can be added to the `options` object when requesting routes or quotes.
### Basic route request with fees
```typescript theme={"system"}
import { getRoutes } from "@lifi/sdk";
const routesRequest: RoutesRequest = {
fromChainId: 42161, // Arbitrum
toChainId: 10, // Optimism
fromTokenAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
toTokenAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", // DAI on Optimism
fromAmount: "10000000", // 10 USDC
options: {
integrator: "Your dApp/company name",
fee: 0.01, // 1% fee
},
};
const result = await getRoutes(routesRequest);
const routes = result.routes;
```
### Quote request with fees
```typescript theme={"system"}
import { getQuote } from "@lifi/sdk";
const quoteRequest: QuoteRequest = {
fromChain: 42161, // Arbitrum
toChain: 10, // Optimism
fromToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
toToken: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", // DAI on Optimism
fromAmount: "10000000", // 10 USDC
fromAddress: "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
integrator: "Your dApp/company name",
fee: 0.01, // 1% fee
};
const quote = await getQuote(quoteRequest);
```
# LI.FI SDK Overview
Source: https://docs.li.fi/sdk/overview
Cross-chain and on-chain swap and bridging toolkit
**Building an AI agent?** For AI integrations, we recommend using the [REST API](/api-reference/introduction) directly instead of the SDK. See our [Agent Integration Guide](/agents/overview) for the minimal endpoint set.
## Introduction
LI.FI SDK provides a powerful toolkit for developers to enable seamless cross-chain and on-chain swaps and bridging within their applications. Our JavaScript/TypeScript SDK can be implemented in front-end or back-end environments, allowing you to build robust UX/UI around our advanced bridge and swap functionalities. LI.FI SDK efficiently manages all communications between our smart routing API and smart contracts and ensures optimal performance, security, and scalability for your cross-chain and on-chain needs.
**LI.FI SDK features include:**
* All ecosystems, chains, bridges, exchanges, and solvers that LI.FI supports
* Complete functionality covering full-cycle from obtaining routes/quotes to executing transactions
* Easy tracking of the route and quote execution through the robust event and hooks handling
* Highly customizable settings to tailor the SDK to your specific needs including configuration of RPCs and options to allow or deny certain chains, tokens, bridges, exchanges, solvers
* Supports widely adopted industry standards, including [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792), [ERC-2612](https://eips.ethereum.org/EIPS/eip-2612), [EIP-712](https://eips.ethereum.org/EIPS/eip-712), and [Permit2](https://github.com/Uniswap/permit2)
* SDK ecosystem providers are based on industry-standard libraries ([Viem](https://viem.sh/), [Wallet Standard](https://github.com/wallet-standard/wallet-standard), [Bigmi](https://github.com/lifinance/bigmi))
* Support for arbitrary contract calls on the destination chain
* Designed for optimal performance with tree-shaking and dead-code elimination, ensuring minimal bundle sizes and faster page load times in front-end environments
* Compatibility tested with Node.js and popular front-end tools like Vite
**Looking for one-click DeFi operations?** The SDK fully supports [Composer](/composer/overview) — deposit into vaults, stake, and lend across chains with a single transaction. See the [SDK Composer Integration Guide](/composer/guides/sdk-integration).
## How to integrate the SDK
```typescript yarn theme={"system"}
yarn add @lifi/sdk
```
```typescript pnpm theme={"system"}
pnpm add @lifi/sdk
```
```typescript bun theme={"system"}
bun add @lifi/sdk
```
```typescript npm theme={"system"}
npm install @lifi/sdk
```
Learn more in the [installation guide](/sdk/installing-the-sdk).
```typescript theme={"system"}
import { createConfig } from "@lifi/sdk";
createConfig({
integrator: "YourCompanyName",
});
```
Find all [config parameters](/sdk/configure-sdk).
Setup [EVM](/sdk/configure-sdk-providers#setup-evm-provider), [Solana](sdk/configure-sdk-providers#setup-solana-provider), [Bitcoin](/sdk/configure-sdk-providers#setup-utxo-bitcoin-provider) and [SUI](/sdk/configure-sdk-providers#setup-sui-provider) Providers.
Request [swap and bridge routes](/sdk/request-routes).
Load lists of available [chains and tools](/sdk/chains-tools).
Find [supported tokens](/sdk/token-management) with all metadata you need.
# Request Routes/Quotes
Source: https://docs.li.fi/sdk/request-routes
Prior to executing any swap or bridging, you need to request the best route from our smart routing API..
The LI.FI SDK provides functionality to request routes and quotes, as well as to execute them. This guide will walk you through the process of making a request using `getRoutes` and `getQuote` functions.
## How to request Routes
To get started, here is a simple example of how to request routes to bridge and swap 10 USDC on Arbitrum to the maximum amount of DAI on Optimism.
```typescript theme={"system"}
import { getRoutes } from '@lifi/sdk';
const routesRequest: RoutesRequest = {
fromChainId: 42161, // Arbitrum
toChainId: 10, // Optimism
fromTokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toTokenAddress: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // DAI on Optimism
fromAmount: '10000000', // 10 USDC
};
const result = await getRoutes(routesRequest);
const routes = result.routes;
```
When you request routes, you receive an array of route objects containing the essential information to determine which route to take for a swap or bridging transfer. At this stage, transaction data is not included and must be requested separately. Read more [Execute Routes/Quotes](/sdk/execute-routes).
Additionally, if you would like to receive just one best option that our smart routing API can offer, it might be better to request a quote using `getQuote`.
## Routes request parameters
The `getRoutes` function expects a `RoutesRequest` object, which specifies a desired *any-to-any* transfer and includes all the information needed to calculate the most efficient routes.
### Parameters
Below are the parameters for the `RoutesRequest` interface along with their descriptions:
| Parameter | Type | Required | Description |
| ------------------ | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `fromChainId` | number | yes | The ID of the source chain (e.g., Ethereum mainnet is 1). |
| `fromTokenAddress` | string | yes | The contract address of the token on the source chain. Ensure this address corresponds to the specified `fromChainId`. |
| `fromAmount` | string | yes | The amount to be transferred from the source chain, specified in the smallest unit of the token (e.g., wei for ETH). |
| `fromAddress` | string | no | The address from which the tokens are being transferred. |
| `toChainId` | number | yes | The ID of the destination chain (e.g., Optimism is 10). |
| `toTokenAddress` | string | yes | The contract address of the token on the destination chain. Ensure this address corresponds to the specified `toChainId`. |
| `toAddress` | string | no | The address to which the tokens will be sent on the destination chain once the transaction is completed. |
| `fromAmountForGas` | string | no | Part of the LI.Fuel. Allows receiving a part of the bridged tokens as gas on the destination chain. Specified in the smallest unit of the token. |
| `options` | RouteOptions | no | Additional options for customizing the route. This is defined by the RouteOptions interface (detailed below, see Route Options). |
## Route Options
The `RouteOptions` interface allows for further customization of the route request. Below are the parameters for the `RouteOptions` interface along with their descriptions:
| Parameter | Type | Required | Description |
| ---------------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `integrator` | string | no | The identifier of the integrator, usually the dApp or company name. Ideally, this should be specified when configuring the SDK, but it can also be modified during a request |
| `fee` | number | no | The integrator fee percentage (e.g., 0.03 represents a 3% fee). This requires the integrator to be verified. |
| `maxPriceImpact` | number | no | Hides routes with a price impact greater than or equal to this value. (e.g., 0.3 represents 30%) |
| `order` | string | no | `CHEAPEST` - This sorting option prioritises routes with the highest estimated return amount. Users who value capital efficiency at the expense of speed and route complexity should choose the cheapest routes. `FASTEST` - This sorting option prioritizes routes with the shortest estimated execution time. Users who value speed and want their transactions to be completed as quickly as possible should choose the fastest routes. |
| `slippage` | number | no | The slippage tolerance, expressed as a decimal proportion (e.g., 0.005 represents 0.5%). |
| `referrer` | string | no | The wallet address of the referrer, for tracking purposes. |
| `allowSwitchChain` | boolean | no | Specifies whether to return routes that require chain switches (2-step routes). |
| `allowDestinationCall` | boolean | no | Specifies whether destination calls are enabled. |
| `bridges` | AllowDenyPrefer | no | An `AllowDenyPrefer` object to specify preferences for bridges. |
| `exchanges` | AllowDenyPrefer | no | An `AllowDenyPrefer` object to specify preferences for exchanges. |
| `timing` | Timing | no | A Timing object to specify preferences for Timing Strategies. |
## Allow/Deny/Prefer
The `AllowDenyPrefer` interface is used to specify preferences for bridges or exchanges. Using the `allow` option, you can allow tools, and only those tools will be used to find the best routes. Tools specified in `deny` will be blocklisted.
You can find all available keys in [List: Chains, Bridges, DEX Aggregators, Solvers](https://docs.li.fi/list-chains-bridges-dex-aggregators-solvers) or get the available option from the API. See [Chains and Tools](https://docs.li.fi/integrate-li.fi-sdk/chains-and-tools).
Below are the parameters for the `AllowDenyPrefer` interface:
| Parameter | Type | Required | Description |
| --------- | --------- | -------- | ----------------------------------------------------------------------------------------- |
| `allow` | string\[] | no | A list of allowed bridges or exchanges (default: all). |
| `deny` | string\[] | no | A list of denied bridges or exchanges (default: none). |
| `prefer` | string\[] | no | A list of preferred bridges or exchanges (e.g., \['1inch'] to prefer 1inch if available). |
## Timing
The `Timing` interface allows you to specify preferences for the timing of route execution. This can help optimize the performance of your requests based on timing strategies.
Parameters for the `Timing` interface:
| Parameter | Type | Required | Description |
| -------------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `swapStepTimingStrategies` | TimingStrategy\[] | no | An array of timing strategies specifically for each swap step in the route. This allows you to define custom strategies for timing control during the execution of individual swap steps. |
| `routeTimingStrategies` | TimingStrategy\[] | no | An array of timing strategies that apply to the entire route. This enables you to set preferences for how routes are timed overall, potentially improving execution efficiency and reliability. |
## Timing Strategy
This can help optimize the timing of requests based on specific conditions.
| Parameter | Type | Required | Description |
| ------------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `strategy` | string | | The strategy type, which must be set to 'minWaitTime'. This indicates that the timing strategy being applied is based on a minimum wait time. |
| `minWaitTimeMs` | number | | The minimum wait time in milliseconds before any results are returned. This value ensures that the request waits for a specified duration to allow for more accurate results. |
| `startingExpectedResults` | number | | The initial number of expected results that should be returned after the minimum wait time has elapsed. This helps in managing user expectations regarding the outcomes of the request. |
| `reduceEveryMs` | number | | The interval in milliseconds at which the expected results are reduced as the wait time progresses. This parameter allows for dynamic adjustments to the expected results based on the elapsed time. |
You can implement [custom timing strategies](https://docs.li.fi/li.fi-api/li.fi-api/requesting-a-quote/optimizing-quote-response-timing) to improve the user experience and optimize the performance of your application by controlling the timing of route execution.
## Request a Quote
When you request a quote, our smart routing API provides the best available option. The quote includes all necessary information and transaction data required to initiate a swap or bridging transfer.
Here is a simple example of how to request a quote to bridge and swap 10 USDC on Arbitrum to the maximum amount of DAI on Optimism.
```typescript theme={"system"}
import { getQuote } from '@lifi/sdk';
const quoteRequest: QuoteRequest = {
fromChain: 42161, // Arbitrum
toChain: 10, // Optimism
fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
toToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // DAI on Optimism
fromAmount: '10000000', // 10 USDC
// The address from which the tokens are being transferred.
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0',
};
const quote = await getQuote(quoteRequest);
```
## Quote request parameters
The `getQuotes` function expects a `QuoteRequest` object. `RoutesRequest` and `QuoteRequest` have some similarities and slight differences, and below, you can find a description of the `QuoteRequest` interface's parameters.
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `fromChain` | number | yes | The ID of the source chain (e.g., Ethereum mainnet is 1). |
| `fromToken` | string | yes | The contract address of the token on the source chain. Ensure this address corresponds to the specified `fromChain`. |
| `fromAmount` | string | yes | The amount to be transferred from the source chain, specified in the smallest unit of the token (e.g., wei for ETH). |
| `fromAddress` | string | yes | The address from which the tokens are being transferred. |
| `toChain` | number | yes | The ID of the destination chain (e.g., Optimism is 10). |
| `toToken` | string | yes | The contract address of the token on the destination chain. Ensure this address corresponds to the specified `toChain`. |
| `toAddress` | string | no | The address to which the tokens will be sent on the destination chain once the transaction is completed. |
| `fromAmountForGas` | string | no | Part of the LI.Fuel. Allows receiving a part of the bridged tokens as gas on the destination chain. Specified in the smallest unit of the token. |
### Other Quote parameters
In addition to the parameters mentioned above, all parameters listed in the [Route Options](##route-options) section are also available when using `getQuote`, except for `allowSwitchChain`, which is used exclusively to control chain switching in route requests.
Also, parameters to specify options for allowing, denying, or preferring certain bridges and exchanges have slightly different names:
* `allowBridges` (string\[], optional)
* `denyBridges` (string\[], optional)
* `preferBridges` (string\[], optional)
* `allowExchanges` (string\[], optional)
* `denyExchanges` (string\[], optional)
* `preferExchanges` (string\[], optional)
Additionally, you can specify [timing strategies](https://docs.li.fi/li.fi-api/li.fi-api/requesting-a-quote/optimizing-quote-response-timing#parameters-overview) for the swap steps using the `swapStepTimingStrategies` parameter:
* **`swapStepTimingStrategies`** (string\[], optional) Specifies the timing strategy for swap steps. This parameter allows you to define how long the request should wait for results and manage expected outcomes. The format is:
```typescript theme={"system"}
minWaitTime-${minWaitTimeMs}-${startingExpectedResults}-${reduceEveryMs}
```
## Request contract call Quote
Besides requesting general quotes, the LI.FI SDK also provides functionality to request quotes for destination contract calls.
Read more in the [Composer documentation](/composer/overview). For SDK-specific Composer integration, see [SDK Composer Integration Guide](/composer/guides/sdk-integration).
Here is a simple example of how to request a quote to bridge and purchase an NFT on the OpenSea marketplace costing 0.0000085 ETH on the Base chain using ETH from Optimism. The call data for this example was obtained using the OpenSea Seaport SDK.
```typescript theme={"system"}
import { getContractCallsQuote } from '@lifi/sdk';
const contractCallsQuoteRequest = {
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0',
fromChain: 10,
fromToken: '0x0000000000000000000000000000000000000000',
toAmount: '8500000000000',
toChain: 8453,
toToken: '0x0000000000000000000000000000000000000000',
contractCalls: [
{
fromAmount: '8500000000000',
fromTokenAddress: '0x0000000000000000000000000000000000000000',
toContractAddress: '0x0000000000000068F116a894984e2DB1123eB395',
toContractCallData:
'0xe7acab24000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000064000000000000000000000000090884b5bd9f774ed96f941be2fb95d56a029c99c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066757dd300000000000000000000000000000000000000000000000000000000669d0a580000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000ad0303de3e1093e50000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000029f25e8a71e52e795e5016edf7c9e02a08c519b40000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ff0cbadd00000000000000000000000000000000000000000000000000000006ff0cbadd0000000000000000000000000090884b5bd9f774ed96f941be2fb95d56a029c99c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003179fcad000000000000000000000000000000000000000000000000000000003179fcad000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a88c37e000000000000000000000000000000000000000000000000000000008a88c37e000000000000000000000000009323bb21a4c6122f60713e4a1e38e7b94a40ce2900000000000000000000000000000000000000000000000000000000000000e3b5b41791fe051471fa3c2da1325a8147c833ad9a6609ffc07a37e2603de3111b262911aaf25ed6d131dd531574cf54d4ea61b479f2b5aaa2dff7c210a3d4e203000000f37ec094486e9092b82287d7ae66fbf8cd6148233c70813583e3264383afbd0484b80500070135f54edd2918ddd4260c840f8a6957160766a4e4ef941517f2a0ab3077a2ac6478f0ad7fad9b821766df11ca3fdb16a8e95782faaed6e0395df2f416651ac87a5c1edec0a36ad42555083e57cff59f4ad98617a48a3664b2f19d46f4db85e95271c747d03194b5cfdcfc86bb0b08fb2bc4936d6f75be03ab498d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
toContractGasLimit: '210000',
},
],
};
const contractCallQuote = await getContractCallsQuote(contractCallsQuoteRequest);
```
## Contract call Quote request parameters
The `getContractCallsQuote` function expects a `ContractCallsQuoteRequest` object, which includes all the information needed to request a quote for a destination contract call.
Contract call quote request can be treated as an extension to the quote request and in addition to the parameters mentioned below, all parameters listed in the [Other Quote parameters](https://docs.li.fi/integrate-li.fi-sdk/request-routes-quotes#other-quote-parameters) section (such as `integrator`, `fee`, `slippage`, etc.) are also available when using `getContractCallsQuote`.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| | | | |
Array of contract call objects.
| Parameter | Type | Required | Description |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fromAmount` | string | yes | The amount of tokens to be sent to the contract. This amount is independent of any previously bridged or deposited tokens. |
| `fromTokenAddress` | string | yes | The address of the token to be sent to the contract. For example, an ETH staking transaction would require ETH. |
| `toContractAddress` | string | yes | The address of the contract to interact with on the destination chain. |
| `toContractCallData` | string | yes | The call data to be sent to the contract for the interaction on the destination chain. |
| `toContractGasLimit` | string | yes | The estimated gas required for the contract call. Incorrect values may cause the interaction to fail. |
| `toApprovalAddress` | string | no | The address to approve the token transfer if it is different from the contract address. |
| `contractOutputsToken` | string | no | The address of the token that will be output by the contract, if applicable (e.g., staking ETH produces stETH). |
## Difference between `Route` and `Quote`
Even though `Route` and `Quote` terms lie in the same field of providing you with the best option to make a swap or bridging transfer, there are some differences you need to be aware of.
A `Route` in LI.FI represents a detailed transfer plan that may include *multiple steps*. Each step corresponds to an individual transaction, such as swapping tokens or bridging funds between chains. These steps must be executed in a specific sequence, as each one depends on the output of the previous step. A `Route` provides a detailed pathway for complex transfers involving multiple actions.
In contrast, a `Quote` is a *single-step* transaction. It contains all the necessary information to perform a transfer in one go, without requiring any additional steps. `Quotes` are used for simpler transactions where a single action, such as a token swap or a cross-chain transfer, is sufficient. Thus, while `Routes` can involve multiple steps to complete a transfer, a `Quote` always represents just one step.
# Testing Integration
Source: https://docs.li.fi/sdk/testing-integration
Run test transactions on mainnets.
Testing your integration is a crucial step to ensure everything functions correctly before going live and we understand that.
We no longer support testnets and advise running your test transactions on mainnets. This is because bridges and exchanges have limited support for testnets and there is almost no liquidity on those networks.
Running test transactions on mainnets allows you to validate your setup in a real-world environment.
To minimize costs, we recommend testing on chains with low gas fees. Optimism and other Layer 2 (L2) chains are excellent choices for cost-effective testing.
Before testing, make sure you have followed the previous documentation to Configure SDK, Configure SDK Providers and Request Routes/Quotes for your integration tests. Proper setup ensures that your integration is configured correctly for interaction with the supported ecosystems.
# Token Management
Source: https://docs.li.fi/sdk/token-management
Request all available tokens and their balances, manage token approvals and more.
## Get available tokens
### `getTokens`
Retrieves a list of all available tokens on specified chains.
**Parameters**
* `params` (TokensRequest, optional): Configuration for the requested tokens.
* `chains` (ChainId\[], optional): List of chain IDs or keys. If not specified, returns tokens on all available chains.
* `chainTypes` (ChainType\[], optional): List of chain types.
* `options` (RequestOptions, optional): Additional request options.
**Returns**
A Promise that resolves to `TokensResponse`
```typescript Example theme={"system"}
import { ChainType, getTokens } from '@lifi/sdk';
try {
const tokens = await getTokens({
chainTypes: [ChainType.EVM, ChainType.SVM],
});
console.log(tokens);
} catch (error) {
console.error(error);
}
```
### `getToken`
Fetches details about a specific token on a specified chain.
**Parameters**
* `chain` (ChainKey | ChainId): ID or key of the chain that contains the token.
* `token` (string): Address or symbol of the token on the requested chain.
* `options` (RequestOptions, optional): Additional request options.
**Returns**
A Promise that resolves to `Token` object.
```typescript Example theme={"system"}
import { getToken } from '@lifi/sdk';
const chainId = 1;
const tokenAddress = '0x0000000000000000000000000000000000000000';
try {
const token = await getToken(chainId, tokenAddress);
console.log(token);
} catch (error) {
console.error(error);
}
```
## Get token balance
Please ensure that you configure the SDK with EVM/Solana providers first. They are required to use this functionality. Additionally, it is recommended to provide your private RPC URLs, as public ones are used by default and may rate limit you for multiple requests, such as getting the balance of multiple tokens at once.
Read more [Configure SDK Providers](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk-providers).
### `getTokenBalance`
Returns the balance of a specific token a wallet holds.
**Parameters**
* `walletAddress` (string): A wallet address.
* `token` (Token): A Token object.
**Returns**
A Promise that resolves to a `TokenAmount` or `null`.
```typescript Example theme={"system"}
import { getToken, getTokenBalance } from '@lifi/sdk';
const chainId = 1;
const tokenAddress = '0x0000000000000000000000000000000000000000';
const walletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0';
try {
const token = await getToken(chainId, tokenAddress);
const tokenBalance = await getTokenBalance(walletAddress, token);
console.log(tokenBalance);
} catch (error) {
console.error(error);
}
```
### `getTokenBalances`
Returns the balances for a list of tokens a wallet holds.
**Parameters**
* `walletAddress` (string): A wallet address.
* `tokens` (Token\[]): A list of Token objects.
**Returns**
A Promise that resolves to a list of `TokenAmount` objects.
```typescript Example theme={"system"}
import { ChainId, getTokenBalances, getTokens } from '@lifi/sdk';
const walletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0';
try {
const tokensResponse = await getTokens();
const optimismTokens = tokensResponse.tokens[ChainId.OPT];
const tokenBalances = await getTokenBalances(walletAddress, optimismTokens);
console.log(tokenBalances);
} catch (error) {
console.error(error);
}
```
### `getTokenBalancesByChain`
Queries the balances of tokens for a specific list of chains for a given wallet.
**Parameters**
* `walletAddress` (string): A wallet address.
* `tokensByChain` \[chainId: number]: Token\[]: A list of Token objects organized by chain IDs.
**Returns**
A Promise that resolves to an object containing the tokens and their amounts on different chains.
```typescript Example theme={"system"}
import { getTokenBalancesByChain } from '@lifi/sdk';
const walletAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0';
const tokensByChain = {
1: [
{
chainId: 1,
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
symbol: 'DAI',
name: 'DAI Stablecoin',
decimals: 18,
priceUSD: '0.9999',
},
],
10: [
{
chainId: 10,
address: '0x4200000000000000000000000000000000000042',
symbol: 'OP',
name: 'Optimism',
decimals: 18,
priceUSD: '1.9644',
},
],
};
try {
const balances = await getTokenBalancesByChain(walletAddress, tokensByChain);
console.log(balances);
} catch (error) {
console.error(error);
}
```
## Managing token allowance
Token allowance and approval functionalities are specific to EVM (Ethereum Virtual Machine) chains. It allows smart contracts to interact with ERC-20 tokens by approving a certain amount of tokens that a contract can spend from the user's wallet.
Please ensure that you configure the SDK with the EVM provider. It is required to use this functionality.
Read more [Configure SDK Providers](https://docs.li.fi/integrate-li.fi-sdk/configure-sdk-providers).
### `getTokenAllowance`
Fetches the current allowance for a specific token.
**Parameters**
* `token` (BaseToken): The token for which to check the allowance.
* `ownerAddress` (string): The owner of the token.
* `spenderAddress` (string): The spender address that was approved.
**Returns**
A Promise that resolves to a `bigint` representing the allowance or undefined if the token is a native token.
```typescript Example theme={"system"}
import { getTokenAllowance } from '@lifi/sdk';
const token = {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
chainId: 1,
};
const ownerAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0';
const spenderAddress = '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE';
try {
const allowance = await getTokenAllowance(token, ownerAddress, spenderAddress);
console.log('Allowance:', allowance);
} catch (error) {
console.error('Error:', error);
}
```
### `getTokenAllowanceMulticall`
Fetches the current allowance for a list of token/spender address pairs.
**Parameters**
* `ownerAddress` (string): The owner of the tokens.
* `tokens` (TokenSpender\[]): A list of token and spender address pairs.
**Returns**
A Promise that resolves to an array of `TokenAllowance` objects.
```typescript Example theme={"system"}
import { getTokenAllowanceMulticall } from '@lifi/sdk';
const ownerAddress = '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0';
const tokens = [
{
token: {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
chainId: 1,
},
spenderAddress: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
},
{
token: {
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
chainId: 1,
},
spenderAddress: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
},
];
try {
const allowances = await getTokenAllowanceMulticall(ownerAddress, tokens);
console.log('Allowances:', allowances);
} catch (error) {
console.error('Error:', error);
}
```
### `setTokenAllowance`
Sets the token allowance for a specific token and spender address.
**Parameters**
* `request` (ApproveTokenRequest): The approval request.
* `walletClient` (WalletClient): The wallet client used to send the transaction.
* `token` (BaseToken): The token for which to set the allowance.
* `spenderAddress` (string): The address of the spender.
* `amount` (bigint): The amount of tokens to approve.
* `infiniteApproval` (boolean, optional): If true, sets the approval to the maximum uint256 value.
**Returns**
A Promise that resolves to a `Hash` representing the transaction hash or `void` if no transaction is needed (e.g., for native tokens).
```typescript Example theme={"system"}
import { setTokenAllowance } from '@lifi/sdk';
const approvalRequest = {
walletClient: walletClient, // Viem wallet client
token: {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
chainId: 1,
},
spenderAddress: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
amount: 100000000n,
};
try {
const txHash = await setTokenAllowance(approvalRequest);
console.log('Transaction Hash:', txHash);
} catch (error) {
console.error('Error:', error);
}
```
### `revokeTokenApproval`
Revokes the token approval for a specific token and spender address.
**Parameters**
* `request` (RevokeApprovalRequest): The revoke request.
* `walletClient` (WalletClient): The wallet client used to send the transaction.
* `token` (BaseToken): The token for which to revoke the allowance.
* `spenderAddress` (string): The address of the spender.
**Returns**
A Promise that resolves to a `Hash` representing the transaction hash or `void` if no transaction is needed (e.g., for native tokens).
```typescript Example theme={"system"}
import { revokeTokenApproval } from '@lifi/sdk';
const revokeRequest = {
walletClient: walletClient, // Viem wallet client
token: {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
chainId: 1,
},
spenderAddress: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
};
try {
const txHash = await revokeTokenApproval(revokeRequest);
console.log('Transaction Hash:', txHash);
} catch (error) {
console.error('Error:', error);
}
```
# Compatibility with Next.js, Remix, Nuxt, etc.
Source: https://docs.li.fi/widget/compatibility
Integrate the widget with your Next.js, Remix or Nuxt application
## Next.js
**LI.FI Widget** is fully compatible with Next.js applications and requires minimal configuration for seamless integration.
Due to the nature of server-side rendering (SSR) in Next.js and how different wallet libraries manage their connections to wallet extensions, the LI.FI Widget needs to be specifically rendered on the client side. To achieve this, make use of the `'use client'` directive, which ensures that the widget component is only rendered in the client-side environment.
Please take a look at our complete example in the widget repository [here](https://github.com/lifinance/widget/tree/main/examples).
If you're using Next.js with the App Router, the following example shows how to add the widget to a page:
```typescript Widget.tsx theme={"system"}
'use client';
import type { WidgetConfig } from '@lifi/widget';
import { LiFiWidget, WidgetSkeleton } from '@lifi/widget';
import { ClientOnly } from './ClientOnly';
export function Widget() {
const config = {
appearance: 'light',
theme: {
container: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
},
} as Partial;
return (
}>
);
}
```
```typescript ClientOnly.tsx theme={"system"}
import { useSyncExternalStore, type PropsWithChildren } from 'react';
function subscribe() {
return () => {};
}
/**
* Return a boolean indicating if the JS has been hydrated already.
* When doing Server-Side Rendering, the result will always be false.
* When doing Client-Side Rendering, the result will always be false on the
* first render and true from then on. Even if a new component renders it will
* always start with true.
*/
export function useHydrated() {
return useSyncExternalStore(
subscribe,
() => true,
() => false,
);
}
interface ClientOnlyProps extends PropsWithChildren {
fallback?: React.ReactNode;
}
/**
* Render the children only after the JS has loaded client-side. Use an optional
* fallback component if the JS is not yet loaded.
*/
export function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
const hydrated = useHydrated();
return hydrated ? children : fallback;
}
```
```typescript page.tsx theme={"system"}
import { Widget } from './components/Widget';
export default function Home() {
return (
);
}
```
If you are using Next.js with the Pages Router, you need to import the LI.FI Widget dynamically:
```tsx theme={"system"}
import dynamic from 'next/dynamic';
const LiFiWidget = dynamic(() => import('@lifi/widget').then(mod => mod.LiFiWidget), {
ssr: false,
loading: () =>
Loading LI.FI Widget...
});
export function Widget() {
const config = {
appearance: 'light',
theme: {
container: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
},
} as Partial;
return (
);
}
```
## Widget Skeleton
As you can see from the Next.js example, for users convenience we provide the `WidgetSkeleton` component that can be used as a fallback while the main widget component is being loaded.
The `WidgetSkeleton` component is currently only tested within the Next.js environment and might not work with other SSR frameworks.
## Remix, Nuxt, Gatsby etc.
Please check out our complete examples for Remix, Nuxt and Gatsby.
* Remix ([Example](https://github.com/lifinance/widget/tree/main/examples/remix))
* Nuxt.js ([Example](https://github.com/lifinance/widget/tree/main/examples/nuxt))
* Gatsby ([Example](https://github.com/lifinance/widget/tree/main/examples/gatsby))
We are continuously working on improving our support for more frameworks.
# Configure Widget
Source: https://docs.li.fi/widget/configure-widget
Flexibility at your fingertips
The LI.FI Widget supports a range of configuration options, allowing you to:
* Allow or deny specific chains, tokens, bridges, and exchanges.
* Preselect default source and destination chains.
* Choose default tokens for both source and destination.
* Set the amount of the destination token.
* Specify a destination address.
* Customize various LI.FI SDK settings through the `sdkConfig` configuration.
These options enable precise control over the widget's behavior and improve the user experience by adjusting it to specific needs and preferences.
## LI.FI SDK configuration
The LI.FI Widget is built on top of the LI.FI SDK, leveraging its robust functionality for cross-chain swaps and bridging. The sdkConfig option allows you to configure various aspects of the SDK directly within the widget.
Let's look at the example of configuring private RPC endpoints using the sdkConfig option.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig, ChainId } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
sdkConfig: {
rpcUrls: {
[ChainId.ARB]: ["https://arbitrum-example.node.com/"],
[ChainId.SOL]: ["https://solana-example.node.com/"],
},
},
};
export const WidgetPage = () => {
return (
);
};
```
In a production app, it is recommended to pass through your authenticated RPC provider URL (Alchemy, Infura, Ankr, etc).
If no RPC URLs are provided, LI.FI Widget will default to public RPC providers.
Public RPC endpoints (especially Solana) can sometimes rate-limit users depending on location or during periods of heavy load, leading to issues such as incorrectly displaying balances or errors with transaction simulation.
Please see other SDK configuration options in the [Configure SDK](/sdk/configure-sdk) section.
## Initialize form values
The LI.FI Widget uses a number of form values that are used to fetch and execute routes.
These values are `fromAmount`, `fromChain`, `fromToken`, `toChain`, `toToken` and `toAddress`.
They are most often set by using the Widget UI but they can also be initialized and updated programmatically.
By configuring these options, you can streamline the user experience, ensuring that the widget is preloaded with the desired chains, tokens, amount and address for a swap or bridge. This reduces the need for manual input and helps guide users through the intended flow.
You can initialize these values by either:
* Widget config - by adding `fromAmount`, `fromChain`, `fromToken`, `toChain`, `toToken` or `toAddress` values to the widget config.
* URL search params - when `buildUrl` in the widget config is set to `true`, by adding them to the URL search params in the url of the page the widget is featured on.
When setting form values via config or URL search params you will see any corresponding form field UI updated to reflect those values.
## Initializing by widget config
The LI.FI Widget allows you to preconfigure default chains and tokens, making it easy to set up your desired swap or bridging parameters right from the start. Below is an example of how to configure the widget with specific default chains, tokens, amount, and send to address values.
```typescript theme={"system"}
import type { WidgetConfig } from "@lifi/widget";
import { ChainType } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
// set source chain to Polygon
fromChain: 137,
// set destination chain to Optimism
toChain: 10,
// set source token to USDC (Polygon)
fromToken: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
// set source token to USDC (Optimism)
toToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
// set source token amount to 10 USDC (Polygon)
fromAmount: 10,
// set the destination wallet address
toAddress: {
address: "0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7",
chainType: ChainType.EVM,
},
};
export const WidgetPage = () => {
return (
);
};
```
You can also set a minimum amount in USD equivalent using the `minFromAmountUSD` parameter (number) to ensure users meet minimum transaction requirements.
## Initializing by URL search params
To initialize form values in the widget using URL search params you will need to ensure that `buildUrl` is set to `true` in the widget config.
```typescript theme={"system"}
import type { WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
// instruct the widget to use and build url search params
buildUrl: true,
};
```
You can then feature the URL search params in the URL when navigating to the page that features the widget.
```typescript theme={"system"}
https://playground.li.fi/?fromAmount=20&fromChain=42161&fromToken=0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9&toAddress=0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7&toChain=42161&toToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831
```
Its important to understand this will only work for the widgets initialization - dynamically changing the search params in the URL without a page load will not cause an update of the form values in the widget.
Config values override URL search params
If you want to use URL search params to populate the widget’s form values on initialization (or page load) its important that those form values are NOT featured in the config object used to initialize the widget. fromAmount, fromChain, fromToken, toAddress, toChain, and toToken should NOT be set on the widget config in order to allow the URL to perform the initial set up of the widgets state.
On first page load if you have form values in both the config and the URL then the URL search params will be rewritten to match the config values and the widget form will be populated with the values presented in the config.
## Update form values
After the widget has initialized there are two ways you can update the form values in the widget
* Using the widget config - this uses reactive values in the config and requires some management of those values for updates
* Using the formRef - this provides an function call that you can use to update values in the widgets form store.
Note that when `buildUrl` is set to `true` in the widget config both methods
should also update the URL search params as well as the value displayed in the
widget itself.
## Updating by widget config
Once the widget has initialized you can update the form values in the widget by updating the widget config.
To perform an update you should only include the form values in the config that you want to change and ensure these changes are passed to the Widget.
For example, if you want to change the fromChain and fromToken and nothing else you should include only include those values
In addition to the form values you want to change you should also set a formUpdateKey. This needs to be a unique, randomly generated string and is used to ensure that the form values are updated in the widget - essentially forcing an update. This can avoid some edge case issues that might arise when setting values in the widget via a mix of config and user actions via the widgets UI.
Here is an example of what your config would look like.
```typescript theme={"system"}
import type { WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
fromChain: 10,
fromToken: ‘0x94b008aA00579c1307B0EF2c499aD98a8ce58e58’,
// use the date object to generate a unique value
formUpdateKey: new Date().valueOf().toString()
// config may still feature other config values but
// should not include other form values…
}
```
You can also reset the form values and their fields to an empty state using `undefined`. This example resets only the fromChain and fromToken form values.
```typescript theme={"system"}
import type { WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
fromChain: undefined,
fromToken: undefined,
// use the date object to generate a unique value
formUpdateKey: new Date().valueOf().toString(),
// config may still feature other config values but
// should not include other form values…
};
```
Here `undefined` used to reset a the widgets form value to an empty state. The absence of a property from the widget config object means that property will remain unchanged.
State management with widget config
When using config to update widgets form values it is often a good choice to consider using an application state management library to store your widget config. There are many options to choose from such as Zustand, MobX, Redux or even React context.
For example, if you were to use Zustand as your state management tool you could use Zustand’s API to access and set values on your config from any part of your application. In addition you would also be able to use Zustand’s equality functionality, such as the built-in `shallow` function, to ensure that your widget config is only used to update the instance of the LiFi Widget when necessary. This should be beneficial for optimizing re-renders.
You can find an example that uses [Zustand to manage widget config](https://github.com/lifinance/widget/tree/main/examples/zustand-widget-config) in the widget repository.
## Updating by form ref
This method provides developers a way to set the form values directly in the widget without making changes to the widget config. By passing a ref object to the widget you can access a function to set values directly on the widgets form state. See the example below.
```typescript theme={"system"}
import type { FormState } from '@lifi/widget';
import { LiFiWidget } from '@lifi/widget';
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = {
buildUrl: true,
};
const formRef = useRef(null);
const handleClick = () => {
formRef.current?.setFieldValue( ‘fromChain’, 10, { setUrlSearchParam: true });
};
return (
<>
>
)
}
```
Notice the use of `setFieldValue` function.
```typescript theme={"system"}
formRef.current?.setFieldValue( ‘fromChain’, 10, { setUrlSearchParam: true });
```
Once initialized the `setFieldValue` function can be called to set the form value, note that `setUrlSearchParam` will ensure the url is updated if you have `buildUrl` set to `true` in your widget config.
Here are some examples of usage.
```typescript fromChain & fromToken theme={"system"}
// fromChain and fromToken can be set independently but you might also find that you want to set them at the same time
formRef.current?.setFieldValue(
'fromChain',
10,
{ setUrlSearchParam: true }
);
formRef.current?.setFieldValue(
'fromToken',
'0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
{ setUrlSearchParam: true }
);
// To reset fromChain and fromToken
formRef.current?.setFieldValue(
'fromChain',
undefined,
{ setUrlSearchParam: true }
);
formRef.current?.setFieldValue(
'fromToken',
undefined
{ setUrlSearchParam: true }
);
```
```typescript fromAmount theme={"system"}
formRef.current?.setFieldValue(
'fromAmount',
'10',
{ setUrlSearchParam: true }
);
// To reset fromAmount
formRef.current?.setFieldValue(
'fromAmount',
undefined
{ setUrlSearchParam: true }
);
```
```typescript toAddress theme={"system"}
import { ChainType } from '@lifi/widget';
formRef.current?.setFieldValue(
'toAddress',
{
name: 'Lenny',
address: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea9',
chainType: ChainType.EVM,
},
{ setUrlSearchParam: true }
);
// To reset toAddress
formRef.current?.setFieldValue(
'toAddress',
undefined
{ setUrlSearchParam: true }
);
```
## Configure allow and deny options
We provide `allow` and `deny` configuration options to control which chains, tokens, bridges, and exchanges can be used within your application. Here’s how you can set up and use these options:
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
// disable BSC from being shown in the chains list
chains: {
deny: [56],
},
// allow bridging through Stargate bridge only
bridges: {
allow: ["stargateV2"],
},
};
export const WidgetPage = () => {
return (
);
};
```
To control which tokens appear in the **from** and **to** lists, use the `allow` and `deny` options:
* If defined at the top level of the `tokens` object, they apply to **both** lists.
* If defined inside the `from` or `to` objects, they apply **only** to that specific list.
* If an `allow` list is defined, only tokens included in it are allowed. If no `allow` list is defined, all tokens are allowed unless they are explicitly included in `deny`. If a token appears in both `allow` and `deny`, the `allow` list takes precedence.
* A token must pass both the top level `allow`/`deny` check and the check for the current list (`from` or `to`) to be considered allowed.
* Token filters are applied per chain. When tokens are allowed/denied for a specific chain, only that chain's tokens are affected. Other chains remain unfiltered and show all their available tokens.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
tokens: {
// Top-level allow/deny apply to BOTH 'from' and 'to' lists
allow: [
{
address: "0x0000000000000000000000000000000000000000",
chainId: 1,
},
],
deny: [
{
address: "0x0000000000000000000000000000000000000000",
chainId: 137,
},
],
// 'from' list-specific allow/deny complements top-level settings
from: {
allow: [
{
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
chainId: 1,
},
],
deny: [
{
address: "0x0000000000000000000000000000000000000000",
chainId: 1,
},
],
},
// 'to' list-specific allow/deny
to: {
allow: [
{
address: "0x0000000000000000000000000000000000000000",
chainId: 137,
},
],
deny: [
{
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
chainId: 1,
},
],
},
},
};
export const WidgetPage = () => {
return (
);
};
```
Apart from the `allow` and `deny` options, the `tokens` option can be configured to include other tokens or featured tokens that will appear at the top of the corresponding list of tokens.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
tokens: {
// Featured tokens will appear on top of the list
featured: [
{
address: "0x2fd6c9b869dea106730269e13113361b684f843a",
symbol: "CHH",
decimals: 9,
chainId: 56,
name: "Chihuahua",
logoURI:
"https://s2.coinmarketcap.com/static/img/coins/64x64/21334.png",
},
],
// Include any token to the list
include: [
{
address: "0xba98c0fbebc892f5b07a42b0febd606913ebc981",
symbol: "MEH",
decimals: 18,
chainId: 1,
name: "meh",
logoURI:
"https://s2.coinmarketcap.com/static/img/coins/64x64/22158.png",
},
],
},
};
export const WidgetPage = () => {
return (
);
};
```
## Destination address
There are use cases where users need to have a different destination address. Usually, they can enter the destination address independently.
Still, the widget also has configuration options to pre-configure the destination address or create a curated list of wallet addresses to choose from.
## Configure single destination address
Developers can use the `toAddress` option to configure a single destination address. The `address` and `chainType` properties are required, while the `name` and `logoURI` properties are optional.
```typescript theme={"system"}
import { ChainType, LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
toAddress: {
name: "Vault Deposit",
address: "0x0000000000000000000000000000000000000000",
chainType: ChainType.EVM,
logoURI: "https://example.com/image.svg",
},
};
export const WidgetPage = () => {
return (
);
};
```
## Configure a curated list of wallet addresses
Developers can use `toAddresses` option to configure a curated list of wallet addresses.
```typescript theme={"system"}
import { ChainType, LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
toAddresses: [
{
name: "Lenny",
address: "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea9",
chainType: ChainType.EVM,
logoURI: "https://example.com/image.svg",
},
{
address: "0x4577a46A3eCf44E0ed44410B7793977ffbe22CE0",
chainType: ChainType.EVM,
},
{
name: "My sweet solami",
address: "6AUWsSCRFSCbrHKH9s84wfzJXtD6mNzAHs11x6pGEcmJ",
chainType: ChainType.SVM,
},
],
};
export const WidgetPage = () => {
return (
);
};
```
Using this configuration, when users click on the `Send to wallet` button, they will open a pre-configured list of addresses from which to choose, skipping the step where they can manually enter the address.
Together with configuring the wallet list, developers can make the destination address required to be filled out. Please see Required destination address for more details.
## Explorer URLs
In the widget there are numerous points where a user can click to open an explorer in a separate browser tab in order to find out more information about a transaction or an address. Any buttons or links in the widget that present this icon will direct the user to an explorer.
We have default behaviors in relation to opening explorers and we can also use widget config to override and change these behaviors.
## Default behavior for chains
Often when trying to direct a user to an explorer the widget will know which chain relates to a transaction or address and it will present an explorer that matches that chain.
For example, after the user has executed a transaction, on the transaction details page they can click on the "Token allowance approved" explorer button to see more detail about that approval. If the approval was done using the Optimism chain then a new tab would open taking the user to optimistic.etherscan.io to show them more information about that approval.
If no explorer can be found in relation to a chain then the user will be directed to LiFi’s explorer.
## Default behavior for internal explorers
An internal explorer is an explorer that is the preferred choice of an organization that is building an app using the widget. In some parts of the widget we use an internal explorer rather than attempting to find an explorer for a specific chain.
For example, once the user has completed a transaction and is on the transaction details page they are presented with a transfer ID (see below). This is accompanied by a link which allows the user to open an explorer in order to find more information about that transaction. There is no attempt to find a chain specific explorer. The default explorer used is LI.FI own internal explorer and users will be directed to [https://scan.li.fi](https://scan.li.fi/)
## Overriding the explorer URLs
It's possible to override the explorer URLs that widget uses via the widget config. We can do this for specific chains and for the internal explorer. You can use your own explorer urls for multiple chains and at the same time state your own alternative for the internal explorer.
### Overriding explorers for a chain
In the widget config you can override chains by adding an entry to the explorerUrls object: you provide the chain id as a key and the base url of the explorer as the value.
```typescript theme={"system"}
import type { WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
explorerUrls: {
42161: ["https://scan.li.fi"],
},
};
```
The explorer specified above will be used only for that chain, in the above example this would be Arbitrum. For other chains that aren’t specified in the explorerUrls object the widget will still present the default behavior (as stated above).
### Overriding explorers for the internal explorer
In the widget config you can override the internal explorer by adding an entry to the explorerUrls object: you provide `internal` as a key and the base url of the explorer as the value.
```typescript theme={"system"}
import type { WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
explorerUrls: {
internal: ["https://jumper.exchange/scan"],
},
};
```
Any places within the widget that use the internal explorer will now use the url stated in the config rather than the default.
## Address and transaction pages
The widget assumes that the explorer will provide pages for addresses at `/address/:address` and for transactions at `/tx/:hash` and will attempt to navigate the user to those pages when the users clicks the related buttons.
A link to a wallet address would look like:
```typescript theme={"system"}
https://scan.li.fi/address/0xb9c0dE368BECE5e76B52545a8E377a4C118f597B
```
And a link to a transaction would look like:
```typescript theme={"system"}
https://scan.li.fi/tx/0x05dbd8d3be79ad466e7d2898f719cc47b1b3b545cf4782aece16e11849ddd24b
```
The widget assumes that any explorer used with the widget will follow this convention.
## Adding route labels
The Widget allows you to visually enhance specific routes by adding route labels — styled badges with customizable text and appearance.
To display route labels dynamically, configure the `routeLabels: RouteLabelRule[]` array in your `WidgetConfig`.
```typescript theme={"system"}
interface RouteLabelRule {
label: RouteLabel; // The label to display if conditions match
bridges?: AllowDeny; // Optional: Filter by bridge(s)
exchanges?: AllowDeny; // Optional: Filter by exchange(s)
fromChainId?: number[]; // Optional: Filter by source chain ID(s)
toChainId?: number[]; // Optional: Filter by destination chain ID(s)
fromTokenAddress?: string[]; // Optional: Filter by source token address(es)
toTokenAddress?: string[]; // Optional: Filter by destination token address(es)
}
interface RouteLabel {
text: string; // Text to show on the label
sx?: SxProps; // Optional: Style object (MUI-style)
}
```
Each label rule defines matching conditions and a label configuration that will be applied if the conditions are met.
The label configuration includes `text` and `sx` styling of the badge in the MUI-style CSS-in-JS way.
The rest of the fields determine when and where a label should be applied based on route conditions.
You can combine multiple criteria such as `fromChainId`, `exchanges`, `tokens`, and more.
For bridges and exchanges, use the `allow` and `deny` fields for fine-grained control, similarly to how it is described in [Configure allow and deny options](#configure-allow-and-deny-options).
Example configuration:
```typescript theme={"system"}
import { ChainId } from "@lifi/sdk";
import type { WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
routeLabels: [
{
label: {
text: "OP Reward",
sx: {
background: "linear-gradient(90deg, #ff0404, #ff04c8)",
"@keyframes gradient": {
"0%": { backgroundPosition: "0% 50%" },
"50%": { backgroundPosition: "100% 50%" },
"100%": { backgroundPosition: "0% 50%" },
},
animation: "gradient 3s ease infinite",
backgroundSize: "200% 200%",
color: "#ffffff",
},
},
fromChainId: [ChainId.OPT], // Applies to routes from Optimism
},
{
label: {
text: "LI.FI Bonus",
sx: {
display: "flex",
alignItems: "center",
position: "relative",
overflow: "hidden",
marginLeft: "auto",
order: 1,
backgroundImage:
"url(https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/lifidexaggregator.svg)",
backgroundPosition: "left center",
backgroundRepeat: "no-repeat",
backgroundSize: "24px",
paddingLeft: "12px",
backgroundColor: "#f5b5ff",
},
},
fromChainId: [ChainId.OPT], // Applies to routes from Optimism
exchanges: {
allow: ["relay"], // Only show for Relay routes
},
},
],
};
```
Rendered example of the configured route labels:
Labels only appear when *all* specified criteria are satisfied by a route.
# Customize Widget
Source: https://docs.li.fi/widget/customize-widget
Customize the look and feel of the widget to match the design of your dApp and suit your needs
**LI.FI Widget** supports visual customization, allowing you to match your web app's design. The widget's layout stays consistent, but you can modify colors, fonts, border radius, container styles, disable or hide parts of the UI, and more.
Start customizing the widget by tweaking some of the following options:
```typescript theme={"system"}
interface WidgetConfig {
// sets default appearance - light, dark, or auto
appearance?: Appearance
// disables parts of the UI
disabledUI?: DisabledUIType[]
// hides parts of the UI
hiddenUI?: HiddenUIType[]
// makes parts of the UI required
requiredUI?: RequiredUIType[]
// tweaks container, components, colors, fonts, border-radius
theme?: WidgetTheme
}
```
## Theme
By customizing the theme, you can ensure the LI.FI Widget matches the look and feel of your application, providing a seamless user experience.
The `theme` configuration option allows you to customize various aspects of the widget's appearance, including colors, typography, shapes, and component styles.
### Containers
The `container` option customizes the main container of the widget.
The `routesContainer` and `chainSidebarContainer` options apply custom styles to routes and chain sidebar expansions respectively (available in `wide` variant).
In the example below, we adjust the boxShadow and border-radius properties of all the containers.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
import { useMemo } from 'react'
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = useMemo(
() => ({
theme: {
container: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
chainSidebarContainer: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
routesContainer: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
},
}),
[]
)
return
}
```
### Palette, shape, typography
The `palette` option defines the color palette for the widget. You can customize the background colors, greyscale colors, primary and secondary colors, and text colors.
The `shape` option defines border-radius overrides for all elements in the widget.
The `typography` option customizes the font settings like font families.
Let's proceed with the theme and adjust the primary and secondary colors of the inner elements, along with the font family and border-radius.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
import { useMemo } from 'react'
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = useMemo(
() => ({
theme: {
palette: {
primary: { main: '#7B3FE4' },
secondary: { main: '#F5B5FF' },
},
shape: {
borderRadius: 0,
borderRadiusSecondary: 0,
},
typography: {
fontFamily: 'Comic Sans MS',
},
container: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
},
}),
[]
)
return
}
```
### Components
The `components` option allows you to customize the styles of specific components within the widget.
The current list of available components with more to come:
* **MuiAppBar** is used as a header/navigation component at the top of the widget.
* **MuiAvatar** is used to display token/chain avatars.
* **MuiButton** is used for various buttons in the widget.
* **MuiCard** is used for card elements within the widget. There are also three default card variants available for customization: `outlined`, `elevation`, and `filled`. They can be set using `defaultProps` option (see example below).
* **outlined** - default variant where the card has thin borders.
* **elevation** - variant where the card has a shadow.
* **filled** - variant where the card is filled with color (`palette.background.paper` property).
* **MuiIconButton** is used for icon buttons within the widget.
* **MuiInputCard** is used for input cards within the widget.
* **MuiTabs** is used for tab navigation within the widget (available in `split` subvariant).
With the `components` option, each component can be customized using the MUI's `styleOverrides` property, allowing for granular control over its styling.
Let's take a look at the example, which shows how we can use card component variants together with overriding tabs component styles.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
import { useMemo } from 'react'
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = useMemo(
() => ({
palette: {
primary: {
main: '#006Eff',
},
secondary: {
main: '#FFC800',
},
background: {
default: '#ffffff',
paper: '#f8f8fa',
},
text: {
primary: '#00070F',
secondary: '#6A7481',
},
grey: {
200: '#EEEFF2',
300: '#D5DAE1',
700: '#555B62',
800: '#373F48',
},
},
shape: {
borderRadius: 12,
borderRadiusSecondary: 12,
borderRadiusTertiary: 24,
},
container: {
boxShadow: '0px 8px 32px rgba(0, 0, 0, 0.08)',
borderRadius: '16px',
},
components: {
MuiCard: {
defaultProps: { variant: 'filled' },
},
// Used only for 'split' subvariant and can be safely removed if not used
MuiTabs: {
styleOverrides: {
root: {
backgroundColor: '#f8f8fa',
[`.${tabsClasses.indicator}`]: {
backgroundColor: '#ffffff',
},
},
},
},
},
}),
[]
)
return
}
```
## Pre-configured Themes
The LI.FI Widget includes several pre-configured themes that provide a starting point for customization. These themes demonstrate various configurations of colors, shapes, and component styles, giving you an idea of how the widget can be styled to fit different design requirements.
Besides the default theme, there are three pre-configured themes available with more to come.
Developers can import them directly from `@lifi/widget` package.
```typescript theme={"system"}
import { azureLightTheme, watermelonLightTheme, windows95Theme } from '@lifi/widget'
```
### Watermelon
### Azure
### Windows 95
### Customizing Pre-configured Themes
You can further customize these pre-configured themes by modifying their properties or combining them with your own custom styles. This flexibility allows you to achieve the exact look and feel you desire for your application.
## Appearance
The widget has complete dark mode support out of the box. By default, the `appearance` option is set to `auto`, matching the user's system settings for dark and light modes.
Now, let's set the default appearance to dark.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
import { useMemo } from 'react'
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = useMemo(
() => ({
appearance: 'dark',
}),
[]
)
return
}
```
## Layout
There are 4 ways of dealing with the widget's height in terms of layout: **default, restricted max height, restricted height, full height.**
Note that none of these layouts are intend for use with the drawer variant. Default, restricted max height, and restricted height can be used with both wide and compact variants. Full height should only be used with the compact variant.
### Default
By default the widget will have a maximum height of `686px`. This requires no change to the config but fundamentally works in the same way as the restricted max height.
### Restricted Max Height
In restricted max height layout, pages within the widget will occupy only the minimum amount of space needed - the Widgets height will contract and expand but pages shouldn't increase beyond the stated max height. Any pages larger than the max height will be scrollable to allow content to be reached.
You can set the max height on the theme's container object in the config - this should be a number (the number of pixels) and not a string. And its advisable to use a value above `686` which is the default value of the widget's height.
```typescript theme={"system"}
import { WidgetConfig } from '@lifi/widget'
const widgetConfig: WidgetConfig = {
theme: {
container: {
maxHeight: 820,
},
},
}
```
### Restricted Height
In restricted height layout, pages within the widget will always occupy the full height stated in the config. When navigating through different pages the height of the widget should remain consistent. Any pages larger than the stated height will be scrollable to allow content to be reached.
You can set the height on the theme's container object in the config - this should be a number (the number of pixels) and not a string. And its advisable to use a value above `686` which is the default value of the widget's height.
```typescript theme={"system"}
import { WidgetConfig } from '@lifi/widget'
const widgetConfig: WidgetConfig = {
theme: {
container: {
height: 900,
},
},
}
```
Its not recommend to attempt to use the containers height and maxHeight together - in the widget they present different layout modes.
### Full Height
Full height has been included to better present the widget where less screen real estate is available such as on mobile devices. It assumes that the widget will make up the majority of the content and functionality on a page and where possible will allow scrolling via the page itself.
With this layout the widget will attempt to occupy the full height of the HTML element containing it.
Full height layout can feature number of different properties in the config - we will break each of these down.
```typescript theme={"system"}
import { WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
variant: 'compact'
theme; {
container: {
display: 'flex',
height: '100%'
}
header: {
position: 'fixed',
top: 0,
},
}
}
```
* **variant should be set as 'compact'** - 'compact' itself is already built to work with smaller screen spaces in mind.
* **theme.container** - this should feature `display: 'flex'` and `height: '100%'` to instruct the widget to occupy the full space of the containing HTML element and to also allow the use of flex layout in some parts of the widget to try to better use available screen space.
* **theme.header** - this is optional.
* When adding theme.header you should state both `position: 'fixed'` and a top value (above we use `top: 0`)
* This will make widget's header behavior like a sticky header which stays in a fixed position when other parts of the widgets pages are still scrollable.
* Setting the top value means that you can account for the position of any elements you might have on your page above the widget. For example if you have a navigation bar that sits above the widget that is 60 pixels in height you should set the top value to `top: 60`
* Without `theme.header` the widget's header will be scroll with the rest of the widget pages content.
### Considerations when using Full Height
To present the widget for a mobile experience the pages HTML & CSS external to the widget will also have to be considered in addition to the the widget config. In Full Height layout the widget surrenders its height to its external HTML container so container needs to be handled well by the containing application. Here are some things to think about when implementing Full Height layout.
* Viewport meta may need to be updated for the page to present correctly
* e.g. ``
* You may also want to think about how the page behaves in terms of occupying the fully page height. Here is how we do it in the Widget Playground.
* In the Widget Playground we use `100dvh` with the min-height css which means that if widgets pages are bigger than this that the page can still scroll to allow access to that widget content. Also this means that when widget's pages are smaller than the viewport they can scale and position elements using flex to better use the available space to occupy the full screen height.
* In CSS you should add `overscroll-behavior: none;` to the root/body of your page to prevent undesired scrolling behavior.
* Placement on the page in relation to site navigation, header and footers may also have to be considered. We have mocked this experience in the Widget Playground. To see it:
* Open the Playground Settings and you should be able to toggle the 'show mock header' and 'show mock footer' options.
### Fit Content
This option allows the widget to expand vertically based on the height of its content (excluding scrollable list selectors such as tokens, chains, or transactions).
The maximum height for widget pages that include scrollable lists is defined by the `maxHeight` property.
```typescript theme={"system"}
import { WidgetConfig } from '@lifi/widget'
const widgetConfig: WidgetConfig = {
theme: {
container: {
height: 'fit-content',
maxHeight: 800,
},
},
}
```
If `maxHeight` is not specified, the default value of `686px` is applied.
## Disabled UI elements
The `disabledUI` property allows you to specify which UI elements should be disabled in the widget. Disabling UI elements can be useful to prevent user interaction with certain parts of the widget that might not be necessary or desirable for your specific implementation.
The `DisabledUI` enum specifies UI elements that can be disabled to prevent user interaction.
```typescript theme={"system"}
export enum DisabledUI {
// Disables the input field for the token amount
FromAmount = 'fromAmount',
// Disables the button for the source token selection
FromToken = 'fromToken',
// Disables the button for specifying the destination address
ToAddress = 'toAddress',
// Disables the button for the destination token selection
ToToken = 'toToken',
}
```
## Hidden UI elements
The `hiddenUI` property allows you to specify which UI elements should be hidden in the widget. This is useful for tailoring the user interface to fit your specific needs by removing elements that might not be relevant for your use case.
The `HiddenUI` enum specifies UI elements that can be hidden from the UI.
```typescript theme={"system"}
export enum HiddenUI {
// Hides the appearance settings UI (light/dark mode switch)
Appearance = 'appearance',
// Hides the close button in the drawer variant
DrawerCloseButton = 'drawerCloseButton',
// Hides the transaction history UI
History = 'history',
// Hides the language selection UI
Language = 'language',
// Hides the 'Powered by LI.FI' branding - not recommended :)
PoweredBy = 'poweredBy',
// Hides the button for specifying the destination address
ToAddress = 'toAddress',
// Hides the button for the source token selection
FromToken = 'fromToken',
// Hides the button for the destination token selection
ToToken = 'toToken',
// Hides the wallet menu UI
WalletMenu = 'walletMenu',
// Hides the integrator-specific step details UI
IntegratorStepDetails = 'integratorStepDetails',
// Hides the button to reverse/swap the from and to tokens
ReverseTokensButton = 'reverseTokensButton',
// Hides the token description in routes
RouteTokenDescription = 'routeTokenDescription',
// Hides the chain selection UI
ChainSelect = 'chainSelect',
// Hides the bridges settings UI
BridgesSettings = 'bridgesSettings',
// Hides the connected wallets section in address book
AddressBookConnectedWallets = 'addressBookConnectedWallets',
// Hides the low address activity confirmation dialog
LowAddressActivityConfirmation = 'lowAddressActivityConfirmation',
// Hides the gas refuel message UI
GasRefuelMessage = 'gasRefuelMessage',
// Hides the search bar in the tokens list
SearchTokenInput = 'searchTokenInput',
// Hides the contact support button
ContactSupport = 'contactSupport',
}
```
The following example shows how to hide appearance and language settings in the UI.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
import { useMemo } from 'react'
export const WidgetPage = () => {
const widgetConfig: WidgetConfig = useMemo(
() => ({
hiddenUI: ['language', 'appearance'],
}),
[]
)
return
}
```
## Required UI elements
The `requiredUI` property allows you to specify which UI elements should be required in the widget. This means that the user must interact with these elements for the widget to proceed with swapping/bridging. This is useful for ensuring that certain critical inputs are provided by the user.
The `RequiredUI` enum specifies UI elements that are required to be filled out or interacted with by the user.
```typescript theme={"system"}
export enum RequiredUI {
// Makes the button for the destination address required for interaction
ToAddress = 'toAddress',
}
```
### Required destination address
Making the destination address required can come in handy when developers want to build a flow where only a pre-configured list of wallet addresses can be set as the destination. See [Configure a curated list of wallet addresses](https://docs.li.fi/integrate-li.fi-widget/configure-widget#configure-a-curated-list-of-wallet-addresses) for more details.
If you are interested in additional customization options for your service, reach out via our [Discord](https://discord.gg/lifi) or [Partnership](https://docs.li.fi/overview/partnership) page.
As you can see, widget customization is pretty straightforward. We are eager to see what combinations you will come up with as we continue to add new customization options.
[⚡Widget Events](https://docs.li.fi/integrate-li.fi-widget/widget-events)
# Install Widget
Source: https://docs.li.fi/widget/install-widget
Easy installation to go multi-chain
To get started, install the latest version of LI.FI Widget.
```typescript yarn theme={"system"}
yarn add @lifi/widget wagmi@2 @bigmi/react @solana/wallet-adapter-react @mysten/dapp-kit @tanstack/react-query
```
```typescript pnpm theme={"system"}
pnpm add @lifi/widget wagmi@2 @bigmi/react @solana/wallet-adapter-react @mysten/dapp-kit @tanstack/react-query
```
```typescript bun theme={"system"}
bun add @lifi/widget wagmi@2 @bigmi/react @solana/wallet-adapter-react @mysten/dapp-kit @tanstack/react-query
```
```typescript npm theme={"system"}
npm install @lifi/widget wagmi@2 @bigmi/react @solana/wallet-adapter-react @mysten/dapp-kit @tanstack/react-query
```
[Wagmi](https://wagmi.sh/) is type safe, extensible, and modular library for building Ethereum apps.
[Bigmi](https://github.com/lifinance/bigmi) is modular TypeScript library that provides reactive primitives for building Bitcoin applications.
[@solana/wallet-adapter-react](https://github.com/anza-xyz/wallet-adapter) is modular TypeScript wallet adapters and components for Solana applications.
[@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit) provides React tools for wallet integration and data access in Sui blockchain dApps.
[TanStack Query](https://tanstack.com/query/v5) is an async state manager that handles requests, caching, and more.
**Polyfill Requirements:** If you need to support older browsers, you'll need to install and configure polyfills. See the [Polyfill Requirements](/widget/polyfill-requirements) documentation for details.
## Compatibility
List of environments, libraries and frameworks we've tested the widget with so far:
* React 18+ ([Example](https://github.com/lifinance/widget/tree/main/examples/create-react-app))
* Vite ([Example](https://github.com/lifinance/widget/tree/main/examples/vite))
* Next.js ([Compatibility with Next.js, Remix, Nuxt, etc.](/widget/compatibility), [example](https://github.com/lifinance/widget/tree/main/examples/nextjs))
* Remix ([Example](https://github.com/lifinance/widget/tree/main/examples/remix))
* Vue 3 ([Example](https://github.com/lifinance/widget/tree/main/examples/vue))
* Svelte ([Example](https://github.com/lifinance/widget/tree/main/examples/svelte))
* Nuxt.js ([Example](https://github.com/lifinance/widget/tree/main/examples/nuxt))
* Gatsby ([Example](https://github.com/lifinance/widget/tree/main/examples/gatsby))
* RainbowKit ([Example](https://github.com/lifinance/widget/tree/main/examples/rainbowkit))
See the compatibility pages for more information.
Check out our complete examples in the [widget repository](https://github.com/lifinance/widget/tree/main/examples) or [file an issue](https://github.com/lifinance/widget/issues) if you have any incompatibilities.
Check out our [LI.FI Playground](https://playground.li.fi/) to play with customization options in
real time.
## Basic example
Here is a basic example using LI.FI Widget with container customization.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget'
const widgetConfig: WidgetConfig = {
theme: {
container: {
border: '1px solid rgb(234, 234, 234)',
borderRadius: '16px',
},
},
}
export const WidgetPage = () => {
return
}
```
# Internationalization
Source: https://docs.li.fi/widget/internationalization
Unlock global communities with effortless internationalization support
**LI.FI Widget** supports internationalization (i18n) and, with the help of our community, is translated into multiple languages to provide a localized user experience to your users, making it easier for them to understand and interact with the widget.
See supported languages and help us translate by [**joining**](https://crowdin.com/project/lifi-widget) our translation projects on [**Crowdin**](https://crowdin.com/project/lifi-widget):

## Configure languages
By default, the widget is in English. You can configure the default language, which languages you want to show inside the widget, or in which language your users can see the widget if you hide the in-built language selection.
There are `allow`, `deny`, and `default` options for language configuration.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
// hide the language selection part of the UI
// hiddenUI: ['language'],
languages: {
// default to German
default: 'de',
// allow German and Spanish languages only
allow: ['de', 'es'],
// disable Chinese from being shown in the languages list
// deny: ['zh'],
},
};
export const WidgetPage = () => {
return (
);
};
```
## Language Resources
You can customize the widget to support any language that your dApp needs by providing language resources.
Rather than trying to add a language via config, it's best to first consider helping us to translate the language you need by joining our [Crowdin translation project](https://crowdin.com/project/lifi-widget). 🙂
```typescript widget.tsx theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
import es from './i18n/es.json';
const widgetConfig: WidgetConfig = {
languageResources: {
es,
},
};
export const WidgetPage = () => {
return (
);
};
```
```json i18n/es.json theme={"system"}
{
"language": {
"name": "Español",
"title": "Idioma"
}
}
```
Also, you can customize the existing language resources if you want to adjust some text. Find the complete list of key-value pairs in the reference `en.json` in our repository [here](https://github.com/lifinance/widget/blob/main/packages/widget/src/i18n/en.json).
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
languageResources: {
en: {
button: { swap: 'Test swap' },
},
},
};
export const WidgetPage = () => {
return (
);
};
```
# Migrate from v2 to v3
Source: https://docs.li.fi/widget/migrate-from-v2-to-v3
Migration guide for upgrading LI.FI Widget v2 to v3
## Overview
**LI.FI Widget v3** introduces a comprehensive overhaul of its core, enhancing compatibility with popular account management libraries like [Wagmi](https://wagmi.sh/) and [RainbowKit](https://www.rainbowkit.com/) and incorporating many new features, including multi-ecosystem support starting with [Solana](https://github.com/anza-xyz/wallet-adapter). Therefore, there are some breaking changes and deprecations to be aware of, as outlined in this guide. Additionally, we encourage you to read the updated documentation, which includes new features that are not mentioned here.
To get started, install the latest version of Widget.
```typescript yarn theme={"system"}
yarn add @lifi/widget
```
```typescript pnpm theme={"system"}
pnpm add @lifi/widget
```
```typescript bun theme={"system"}
bun add @lifi/widget
```
```typescript npm theme={"system"}
npm install @lifi/widget
```
## Configuration
### Renamed variants
The following widget variants were renamed to better reflect their functionality:
`default` is now `compact`
`expandable` is now `wide`
See [Select Widget Variants](https://docs.li.fi/integrate-li.fi-widget/select-widget-variants) for more details.
### Updated toAddress type
In Widget v3 we updated destination wallet functionality to have wallet bookmarks, recently used addresses and more. Previously, the `toAddress` option had a type `string`. To extend the feature in v3 we added a specific type for this field.
```typescript theme={"system"}
// Widget v2
interface WidgetConfig {
// ...
toAddress?: string;
// ...
}
// Widget v3
interface ToAddress {
name?: string;
address: string;
chainType: ChainType;
logoURI?: string;
}
interface WidgetConfig {
// ...
toAddress?: ToAddress;
// ...
}
```
### Updated subvariantOptions type
In Widget v3 we extended the functionality of subvariants and `subvariantOptions` required type update. Previously, `subvariantOptions` supported only `split` subvariant and had `SplitSubvariantOptions` type and now we added support for `custom` subvariant with possibly more subvariant options coming in the future.
```typescript theme={"system"}
// Widget v2
type SplitSubvariantOptions = 'bridge' | 'swap';
interface WidgetConfig {
// ...
subvariantOptions?: SplitSubvariantOptions;
// ...
}
// Widget v3
type SplitSubvariant = 'bridge' | 'swap';
type CustomSubvariant = 'checkout' | 'deposit';
interface SubvariantOptions {
split?: SplitSubvariant;
custom?: CustomSubvariant;
}
interface WidgetConfig {
// ...
subvariantOptions?: SubvariantOptions;
// ...
}
```
### SDK configuration
Following the release of LI.FI SDK v3 there are some changes to SDK configuration in the widget.
`SDKConfig` type is now `WidgetSDKConfig`.
`defaultRouteOptions` renamed to `routeOptions`.
### Removed pre-built drawer button
Previously in Widget v2 there were two ways of controlling the drawer. The first one was pre-built button which came with drawer variant to open and close it. The second one was hiding the button via `hiddenUI` option and controlling the drawer by attaches the ref. Since the title and positioning often required some adjustments we decided to remove pre-built drawer button in favor of controlling the drawer with external button.
Here is the example of controlling the drawer with ref and you can customize the button as you like.
```typescript theme={"system"}
export const WidgetPage = () => {
const drawerRef = useRef(null);
const toggleWidget = () => {
drawerRef.current?.toggleDrawer();
};
return (
);
}
```
We also removed `HiddenUI.DrawerButton` option since there is no default button anymore. However, we added a new option `HiddenUI.DrawerCloseButton` to hide the close button inside the drawer.
### Theming
In Widget v3, we revamped customization and theming possibilities and did a small cleanup.
The top-level `containerStyle` option is moved under the umbrella of the `theme` option and was renamed to `container`.
```typescript theme={"system"}
// Widget v2
export const WidgetPage = () => {
return (
);
};
// Widget v3
export const WidgetPage = () => {
return (
);
};
```
Also, `theme` option type was renamed from `ThemeConfig` to `WidgetTheme`.
Please check out more in [Customize Widget](https://docs.li.fi/integrate-li.fi-widget/customize-widget) section.
### Miscellaneous
`disableLanguageDetector` configuration option was removed. Language detection should now be inherited from the integration dApp.
## Wallet Management
### Moved from Ethers.js to Wagmi
We dropped support for Ethers.js. Instead, Widget now has a first-class Wagmi and all Wagmi-based libraries support like RainbowKit. You can still use Ethers.js in your project and convert Signer/Provider object to use Wagmi's [injected](https://wagmi.sh/react/api/connectors/injected) connector before wrapping the Widget with [WagmiProvider](https://wagmi.sh/react/api/WagmiProvider). However, we suggest moving your dApp's wallet management to use Wagmi as a more future proof solution.
#### Widget v2 setup with Ethers.js
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
export const WidgetPage = () => {
const { account, connect, disconnect, switchChain } = useWallet();
const widgetConfig = useMemo((): WidgetConfig => {
return {
walletManagement: {
signer: account.signer,
connect: async () => {
const signer = await connect();
return signer;
},
disconnect: async () => {
await disconnect();
},
switchChain: async (chainId: number) => {
await switchChain(chainId);
if (account.signer) {
return account.signer;
} else {
throw Error('No signer object is found after the chain switch.');
}
},
},
};
}, [account.signer, connect, disconnect, switchChain]);
return (
);
}
```
#### Widget v3 setup with Wagmi
No more complicated callbacks, just wrap the Widget in `WagmiProvider` and you are set. If you already have one in your dApp, just make sure to keep the Wagmi chains configuration in sync with the Widget chain list, so all functionality like switching chains can keep working.
See [Wallet Management](https://docs.li.fi/integrate-li.fi-widget/wallet-management) for more details.
```typescript theme={"system"}
import { LiFiWidget } from '@lifi/widget';
import { createClient } from 'viem';
import { WagmiProvider, createConfig, http } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { injected } from 'wagmi/connectors';
const wagmiConfig = createConfig({
// Make sure to provide the full list of chains
// you would like to support in the Widget
// and keep them in sync, so all functionality
// like switching chains can work correctly.
chains: [mainnet],
connectors: [injected()],
client({ chain }) {
return createClient({ chain, transport: http() });
},
});
export const WidgetPage = () => {
return (
);
};
```
### Updated wallet management configuration
Since we don't need to provide callbacks and signers to widget configuration, `walletManagement` option was renamed to `walletConfig` and now has the following interface:
```typescript theme={"system"}
interface WidgetWalletConfig {
// Can be used to open the external wallet menu,
// if the user is not connected and dApp uses external wallet management
onConnect(): void;
// Provide your projectId and other WalletConnect properties
// when using Widget's internal wallet management
walletConnect?: WalletConnectParameters;
// Provide your app name and other Coinbase properties
// when using Widget's internal wallet management
coinbase?: CoinbaseWalletParameters;
}
```
## Dependencies
### Wagmi is a new peer dependency
Since we moved from `ethers.js` to `Wagmi` we added it to our peer dependencies.
### Dropped CommonJS support
LI.FI Widget v3 no longer publishes a separate CommonJS build since most of the modern front-end tooling supports ESM and ESM is the future. See [Sindre Sorhus' guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for more info about switching to ESM.
# Monetize Widget
Source: https://docs.li.fi/widget/monetize-widget
Learn how to configure fees and monetize your LI.FI Widget integration.
For more details about how fees work, fee collection on different chains, and
setting up fee wallets, see the [Monetizing the
integration](/introduction/integrating-lifi/monetizing-integration) guide.
There are two ways to configure fees in the Widget: a simple `fee` prop for basic use cases, or an advanced `feeConfig` configuration that provides more flexibility and customization options.
### Simple fee configuration
```JavaScript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
// Set fee parameter to 3%
fee: 0.03,
// Other options...
};
export const WidgetPage = () => {
return (
);
};
```
### Advanced fee configuration
For more advanced use cases, you can use the `feeConfig` parameter which provides additional customization options:
```JavaScript theme={"system"}
import { LiFiWidget, WidgetConfig, WidgetFeeConfig } from '@lifi/widget';
// Basic advanced configuration
const basicFeeConfig: WidgetFeeConfig = {
name: "DApp fee",
logoURI: "https://yourdapp.com/logo.png",
fee: 0.01, // 1% fee
showFeePercentage: true,
showFeeTooltip: true
};
// Dynamic fee calculation
const dynamicFeeConfig: WidgetFeeConfig = {
name: "DApp fee",
logoURI: "https://yourdapp.com/logo.png",
showFeePercentage: true,
showFeeTooltip: true,
calculateFee: async (params) => {
// Custom logic to calculate fees based on token, amount, etc.
const { fromTokenAddress, toTokenAddress, fromAmount } = params;
// Example: Different fees for different token pairs
if (fromTokenAddress === "0x..." && toTokenAddress === "0x...") {
return 0.02; // 2% for specific pair
}
// Example: Volume-based fee structure
if (parseFloat(fromAmount) > 1000) {
return 0.015; // 1.5% for large volumes
}
return 0.03; // Default 3% fee
}
};
const widgetConfig: WidgetConfig = {
feeConfig: basicFeeConfig, // or dynamicFeeConfig
// Other options...
};
export const WidgetPage = () => {
return (
);
};
```
### WidgetFeeConfig interface
The `WidgetFeeConfig` interface provides the following options:
* **`name`** (optional): Display name for your integration shown in fee details
* **`logoURI`** (optional): URL to your logo displayed alongside fee information
* **`fee`** (optional): Fixed fee percentage (e.g., 0.03 for 3%)
* **`showFeePercentage`** (default: false): Whether to display the fee percentage in the UI
* **`showFeeTooltip`** (default: false): Whether to show a tooltip with fee details (requires `name` or `feeTooltipComponent`)
* **`feeTooltipComponent`** (optional): Custom React component for the fee tooltip
* **`calculateFee`** (optional): Function for dynamic fee calculation based on transaction parameters
Only use either `fee` or `calculateFee` - not both. The `calculateFee`
function allows for dynamic fee calculation based on factors like token pairs,
transaction amounts, user tiers, or any other custom logic.
# LI.FI Widget Overview
Source: https://docs.li.fi/widget/overview
Cross-chain and on-chain swap and bridging UI toolkit
LI.FI Widget is a set of prebuilt UI components that will help you integrate a secure cross-chain bridging and swapping experience that can be styled to match your web app design perfectly and helps drive your multi-chain strategy and attract new users from everywhere.
**LI.FI Widget features include:**
* All ecosystems, chains, bridges, exchanges, and solvers that LI.FI supports
* Embeddable variants - compact, wide, and drawer
* Options to allow or deny certain chains, tokens, bridges, and exchanges
* Pre-configured themes and lots of customization options with dark mode support so you can match the look and feel of your web app
* Supports widely adopted industry standards, including [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792), [ERC-2612](https://eips.ethereum.org/EIPS/eip-2612), [EIP-712](https://eips.ethereum.org/EIPS/eip-712), and [Permit2](https://github.com/Uniswap/permit2)
* SDK ecosystem providers are based on industry-standard libraries ([Viem](https://viem.sh/), [Wallet Standard](https://github.com/wallet-standard/wallet-standard), [Bigmi](https://github.com/lifinance/bigmi))
* View of transactions in progress and transaction history
* Curated wallet lists and wallet bookmarks
* Route settings for advanced users (stored locally)
* Complete UI translations to match your customer’s preferred language
* Compatibility tested with React, Next.js, Vue, Nuxt.js, Svelte, Remix, Gatsby, Vite, CRA, RainbowKit
Try the Widget live in an interactive playground
**Composer works automatically in the Widget.** When users select a vault or staking token as their destination, Composer handles the deposit seamlessly. See the [Widget Composer Integration Guide](/composer/guides/widget-integration).
# Polyfill Requirements
Source: https://docs.li.fi/widget/polyfill-requirements
JavaScript polyfills required for browser compatibility
The LI.FI Widget uses modern JavaScript APIs that may not be supported in older environments. Partners integrating the Widget may need to add polyfills if they need to support older browsers.
Polyfills are only needed if you must support an environment where an API below is marked ❌. The polyfill packages/imports in this page are **suggestions**—you can use any equivalent polyfill solution as long as it provides the same API behavior.
## Browser & API Support
| API | Polyfill | iOS 15.0-15.3 | iOS 15.4+ | iOS 16.0+ | Chrome 92+ | Firefox 92+ | Safari 15.4+ | Safari 16.0+ | Samsung 16.0+ |
| ----------------------- | -------------------------------------- | ------------- | --------- | --------- | ---------- | ----------- | ------------ | ------------ | ------------- |
| `structuredClone()` | `core-js/actual/structured-clone` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Array.at()` | `core-js/actual/array/at` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Object.hasOwn()` | `core-js/actual/object/has-own` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Array.findLast()` | `core-js/actual/array/find-last` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Array.findLastIndex()` | `core-js/actual/array/find-last-index` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Array.toSpliced()` | `core-js/actual/array/to-spliced` | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| `Array.toSorted()` | `core-js/actual/array/to-sorted` | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| `Array.with()` | `core-js/actual/array/with` | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
**Legend:** ❌ Not supported (requires polyfill) | ✅ Fully supported
## Polyfill Setup (Examples)
### Install a polyfill library
```bash theme={"system"}
npm install core-js
```
### Import polyfills (choose one approach)
**Option A: simplest (larger bundle)**
```javascript theme={"system"}
// Place at the very beginning of your app entry point (before importing the Widget)
import 'core-js'
```
**Option B: only what you need (smaller bundle)**
```javascript theme={"system"}
// Place at the very beginning of your app entry point (before importing the Widget)
import 'core-js/actual/structured-clone'
import 'core-js/actual/array/at'
import 'core-js/actual/object/has-own'
import 'core-js/actual/array/find-last'
import 'core-js/actual/array/find-last-index'
import 'core-js/actual/array/to-spliced'
import 'core-js/actual/array/to-sorted'
import 'core-js/actual/array/with'
```
# React Router Compatibility
Source: https://docs.li.fi/widget/react-router-compatibility
Integrate the widget within your router
**LI.FI Widget** is fully compatible with `React Router v6` and uses it internally for page navigation.
## React Router
To operate correctly inside your instance of the `React Router` context, we check if the widget component is inside the context and, if so, utilize it instead of creating a new one.
The below code shows an example of correctly setting the path to a page with the widget component.
Please pay attention to the **asterisk** at the end of the path, it is necessary for the correct operation of the paths inside the widget.
```typescript theme={"system"}
} />
```
## Other Router
Well, if you don't use React Router in your app, you don't need to do anything. We will create a memory router inside the widget component, and all the page routing magic will happen internally.
# Select Widget Variants
Source: https://docs.li.fi/widget/select-widget-variants
Customize your experience with our versatile variants
LI.FI Widget comes in three variants - `compact`, `wide`, and `drawer`. Variants allow customization of the exterior of the widget, like the side panel for quotes or drawer. There are also several subvariants - `default`, `split`, and `custom`. They help to customize the interior look and feel as well as functionality.
## Variants
Variants provide a way to optimize the presentational style of the Widget for the space available in your application.
Check out our [LI.FI Playground](https://playground.li.fi/) to play with variants.
To use one of the variants, set the `variant` option in the configuration.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
// It can be either compact, wide, or drawer
variant: 'wide',
};
export const WidgetPage = () => {
return (
);
};
```
## Compact variant
The compact variant is a great choice when you have limited space on a page or are dealing with smaller screen sizes. It has everything you need to bridge and swap in a compact view and allows you to integrate the widget wherever you want on your web app's page.
## Wide variant
The wide variant allows you to take advantage of bigger page and screen sizes where you might have more available screen real estate and provides a more comprehensive overview of available routes, displayed in a sidebar with slick animation.
## Drawer variant
The drawer variant allows you to show or hide the Widget based on user interaction. It can fit nicely on the page's side and has the same layout as the compact variant.
## How do we control the drawer?
The drawer doesn't have a pre-built button to open and close it. To control the drawer you need to create and assign a `ref` to the widget.
Here is an example of controlling the drawer with `ref`:
```typescript theme={"system"}
export const WidgetPage = () => {
const drawerRef = useRef(null);
const toggleWidget = () => {
drawerRef.current?.toggleDrawer();
};
return (
);
}
```
## Subvariants
Subvariants allow you to present different workflows for your users.
The **default** subvariant has the same functionality to bridge and swap in a compact view.
The **split** subvariant separates mental models and has slightly different views for bridging and swapping experiences with neat tabs on the main page.
The **custom** subvariant offers a totally new look, allowing you to show custom components like the NFT one and giving you a toolkit to build complete new flows, including NFT Checkout and Deposit.
See [Split subvariant options](#split-subvariant-options) for configuration details of **split** subvariant.
To use one of the subvariants, set the `subvariant` option in the configuration.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
const widgetConfig: WidgetConfig = {
// It can be either compact, wide, or drawer
variant: 'wide',
// It can be either default, split, or custom
subvariant: 'split',
};
export const WidgetPage = () => {
return (
);
};
```
### Enabling the chain sidebar
If you're using the `wide` variant and want to have a chain sidebar instead of the compact chain selector, set `enableChainSidebar` to `true` in `subvariantOptions`:
```typescript theme={"system"}
subvariantOptions: {
wide: {
enableChainSidebar: true,
}
}
```
The chain sidebar in the `wide` variant is disabled by default.
### Split subvariant options
For `subvariant: 'split'`, the `subvariantOptions` configuration controls whether to show both "Swap" and "Bridge" tabs or a single interface.
* **Default (no options)**: Shows both "Bridge" and "Swap" tabs
* **`split: 'bridge'`**: Shows only bridge interface (no tabs)
* **`split: 'swap'`**: Shows only swap interface (no tabs)
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from '@lifi/widget';
// Default - shows tabs
const widgetConfig: WidgetConfig = {
subvariant: 'split',
};
// Pure bridge interface
const bridgeConfig: WidgetConfig = {
subvariant: 'split',
subvariantOptions: {
split: 'bridge'
}
};
// Pure swap interface
const swapConfig: WidgetConfig = {
subvariant: 'split',
subvariantOptions: {
split: 'swap'
}
};
```
# Wallet Management
Source: https://docs.li.fi/widget/wallet-management
Configure your widget for seamless wallet management
The widget has a built-in wallet management UI, so you can connect the wallet and use the widget as a standalone dApp out of the box. However, when embedding the widget into the dApp, reusing the existing wallet management UI of that dApp makes the most sense.
There are several ecosystems and types of chains (EVM, SVM, UTXO) supported by the widget, and therefore, there are several different libraries used to manage wallet connections to these chains.
## EVM wallet connection
To manage wallet connection to EVM (Ethereum Virtual Machine) chains, switching chains, etc., the widget uses the [Wagmi](https://wagmi.sh/) library internally and also provides first-class support for all Wagmi-based libraries such as [RainbowKit](https://www.rainbowkit.com/), [Dynamic](https://github.com/lifinance/widget/tree/main/examples/dynamic), [Reown AppKit](https://docs.reown.com/appkit/overview)
If you already manage wallets using Wagmi or Wagmi-based library in your dApp and the Widget detects that it is wrapped in [WagmiProvider](https://wagmi.sh/react/api/WagmiProvider) it will start re-using your wallet management without any additional configuration.
The example below shows how to preconfigure a basic wallet management using Wagmi.
```typescript theme={"system"}
import { LiFiWidget } from "@lifi/widget";
import { createClient } from "viem";
import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, arbitrum, optimism, scroll } from "wagmi/chains";
import { injected } from "wagmi/connectors";
const wagmiConfig = createConfig({
// Make sure to provide the full list of chains
// you would like to support in the Widget
// and keep them in sync, so all functionality
// like switching chains can work correctly.
chains: [mainnet, arbitrum, optimism, scroll],
connectors: [injected()],
client({ chain }) {
return createClient({ chain, transport: http() });
},
});
export const WidgetPage = () => {
return (
);
};
```
### Keep chains in sync
It is important to keep the Wagmi chains configuration in sync with the Widget chain list so all functionality, like switching chains, can keep working correctly.
There are two approaches to this:
1. Manually update the Widget and Wagmi chains configuration to specify all chains you would like to support in your dApp and the Widget. See [Configure Widget](https://docs.li.fi/integrate-li.fi-widget/configure-widget) page to know more about the Widget's allow/deny chains configuration.
2. Get all available chains from LI.FI API and dynamically update Wagmi configuration. The Widget provides hooks to ease this approach.
Here is an example of how to support all available LI.FI chains dynamically using Wagmi and additional hooks from `@lifi/widget` package.
```typescript WalletProvider.tsx theme={"system"}
import { useSyncWagmiConfig } from '@lifi/wallet-management';
import { useAvailableChains } from '@lifi/widget';
import { injected } from '@wagmi/connectors';
import { useRef, type FC, type PropsWithChildren } from 'react';
import { createClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import type { Config } from 'wagmi';
import { createConfig, WagmiProvider } from 'wagmi';
const connectors = [injected()];
export const WalletProvider: FC = ({ children }) => {
const { chains } = useAvailableChains();
const wagmi = useRef();
if (!wagmi.current) {
wagmi.current = createConfig({
chains: [mainnet],
client({ chain }) {
return createClient({ chain, transport: http() });
},
ssr: true,
});
}
useSyncWagmiConfig(wagmi.current, connectors, chains);
return (
{children}
); };
```
```typescript WidgetPage.tsx theme={"system"}
import { LiFiWidget } from '@lifi/widget';
import { WalletProvider } from '../providers/WalletProvider';
export const WidgetPage = () => {
return (
);
};
```
Please check out our complete examples in the widget repository [here](https://github.com/lifinance/widget/tree/main/examples).
### Support for Ethers.js and other alternatives
Developers can still use Ethers.js or any other alternative library in their project and convert `Signer`/`Provider` objects to Wagmi's [injected](https://wagmi.sh/react/api/connectors/injected) connector before wrapping the Widget with [WagmiProvider](https://wagmi.sh/react/api/WagmiProvider).
## SVM wallet connection
To manage wallet connections to SVM (Solana Virtual Machine) chains the widget uses the [Solana Wallet Standard](https://github.com/anza-xyz/wallet-standard) library.
If you already manage wallets using Solana Wallet Standard library in your dApp and the Widget detects that it is wrapped in [ConnectionProvider](https://solana.com/developers/cookbook/wallets/connect-wallet-react) and [WalletProvider](https://solana.com/developers/cookbook/wallets/connect-wallet-react) it will start re-using your wallet management without any additional configuration.
The example below shows how to preconfigure a basic wallet management for SVM.
```typescript SolanaWalletProvider.tsx theme={"system"}
import type { Adapter } from "@solana/wallet-adapter-base";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import {
ConnectionProvider,
WalletProvider,
} from "@solana/wallet-adapter-react";
import { clusterApiUrl } from "@solana/web3.js";
import type { FC, PropsWithChildren } from "react";
const endpoint = clusterApiUrl(WalletAdapterNetwork.Mainnet);
/**
* Wallets that implement either of these standards will be available automatically.
*
* - Solana Mobile Stack Mobile Wallet Adapter Protocol
* (https://github.com/solana-mobile/mobile-wallet-adapter)
* - Solana Wallet Standard
* (https://github.com/solana-labs/wallet-standard)
*
* If you wish to support a wallet that supports neither of those standards,
* instantiate its legacy wallet adapter here. Common legacy adapters can be found
* in the npm package `@solana/wallet-adapter-wallets`.
*/
const wallets: Adapter[] = [];
export const SolanaWalletProvider: FC = ({ children }) => {
return (
{children}
);
};
```
```typescript WidgetPage.tsx theme={"system"}
import { LiFiWidget } from "@lifi/widget";
import { WalletProvider } from "../providers/SolanaWalletProvider";
export const WidgetPage = () => {
return (
);
};
```
## MVM wallet connection
To manage wallet connections to MVM (Move Virtual Machine) chains like SUI, the widget uses the [@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit) for wallet management and [@mysten/sui](https://sdk.mystenlabs.com/typescript) for SUI blockchain interactions.
If you already manage wallets using [@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit) in your dApp and the Widget detects that it is wrapped in [SuiClientProvider](https://sdk.mystenlabs.com/dapp-kit/sui-client-provider) and [WalletProvider](https://sdk.mystenlabs.com/dapp-kit/wallet-provider), it will start re-using your wallet management without any additional configuration.
The example below shows how to preconfigure a basic wallet management for MVM chains.
```typescript SuiWalletProvider.tsx theme={"system"}
import type { FC, PropsWithChildren } from "react";
import {
createNetworkConfig,
SuiClientProvider,
WalletProvider,
} from "@mysten/dapp-kit";
import { getFullnodeUrl } from "@mysten/sui/client";
const { networkConfig } = createNetworkConfig({
mainnet: { url: getFullnodeUrl("mainnet") },
});
export const SuiWalletProvider: FC = ({ children }) => {
return (
{children}
);
};
```
```typescript WidgetPage.tsx theme={"system"}
import { LiFiWidget } from "@lifi/widget";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SuiWalletProvider } from "../providers/SuiWalletProvider";
const queryClient = new QueryClient();
export const WidgetPage = () => {
return (
);
};
```
## UTXO(Bitcoin) wallet connection
To manage wallet connections and chain interactions with UTXO chains like Bitcoin, the widget uses the [Bigmi](https://github.com/lifinance/bigmi) library.
If you already manage wallets using [Bigmi](https://github.com/lifinance/bigmi) in your dApp and the widget detects that it is wrapped in [BigmiProvider](https://github.com/lifinance/bigmi/blob/main/docs/react/index.md#provider), it will start re-using your wallet management without any additional configuration.
The example below shows how to preconfigure a basic wallet management for Bitcoin.
```typescript WidgetPage.tsx theme={"system"}
import type { Config, CreateConnectorFn } from '@bigmi/client'
import {
createConfig,
phantom,
unisat,
xverse,
} from '@bigmi/client'
import { bitcoin, createClient, http } from '@bigmi/core'
import { BigmiProvider } from '@bigmi/react'
const connectors: CreateConnectorFn[] = [phantom(), unisat(), xverse()]
const config = createConfig({
chains: [bitcoin],
connectors,
client({ chain }) {
return createClient({ chain, transport: http() })
}
}) as Config
export const WidgetPage: FC = ({ children }) => {
return (
);
};
```
## Configuration
There are additional configurations to smooth integration for external wallet management or in case of internal one provide options for WalletConnect and Coinbase Wallet.
```typescript theme={"system"}
interface WidgetWalletConfig {
onConnect(): void;
walletConnect?: WalletConnectParameters;
coinbase?: CoinbaseWalletParameters;
}
interface WidgetConfig {
// ...
walletConfig?: WidgetWalletConfig;
}
```
### Connect wallet button
Using internal wallet management clicking the `Connect wallet` button triggers the opening of an internal wallet menu. In cases where external wallet management is used we provide `onConnect` configuration option. This option allows developers to specify a callback function that will be executed when the `Connect wallet` button is clicked.
Please see modified RainbowKit example below. Here we use `openConnectModal` function provided by `useConnectModal` hook to open RainbowKit wallet menu when the `Connect wallet` button is clicked.
```typescript WidgetPage.tsx theme={"system"}
import { LiFiWidget } from "@lifi/widget";
import { useConnectModal } from "@rainbow-me/rainbowkit";
import { WalletProvider } from "../providers/WalletProvider";
export const WidgetPage = () => {
const { openConnectModal } = useConnectModal();
return (
);
};
```
```typescript WalletProvider.tsx theme={"system"}
import { formatChain, useAvailableChains } from "@lifi/widget";
import { RainbowKitProvider, getDefaultConfig } from "@rainbow-me/rainbowkit";
import { useMemo, type FC, type PropsWithChildren } from "react";
import type { Chain } from "viem";
import { WagmiProvider } from "wagmi";
import { mainnet } from "wagmi/chains";
export const WalletProvider: FC = ({ children }) => {
const { chains } = useAvailableChains();
const wagmiConfig = useMemo(() => {
const _chains: [Chain, ...Chain[]] = chains?.length
? (chains.map(formatChain) as [Chain, ...Chain[]])
: [mainnet];
// Wagmi currently doesn't support updating the config after its creation,
// so in order to keep the dynamic chains list updated, we need to
// re-create a config every time the chains list changes.
const wagmiConfig = getDefaultConfig({
appName: "LI.FI Widget Example",
chains: _chains,
projectId: "Your WalletConnect ProjectId",
ssr: !chains?.length,
});
return wagmiConfig;
}, [chains]);
return (
{children}
);
};
```
### WalletConnect and Coinbase Wallet
We provide additional configuration for WalletConnect and Coinbase Wallet Wagmi connectors so when using built-in wallet management in the widget you can set WalletConnect's `projectId` or Coinbase Wallet's `appName` parameters.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
walletConfig: {
walletConnect: {
projectId: "Your Wallet Connect Project Id",
},
},
};
export const WidgetPage = () => {
return (
);
};
```
### Partial Wallet Management
Your external wallet management may not support all ecosystems provided by our widget, or you may be in the process of migrating to a new setup. To help with these cases, we've got you covered!
The `usePartialWalletManagement` configuration option allows the widget to offer partial wallet management functionality. When enabled, this option provides a hybrid approach, effectively combining both external and internal wallet management.
In partial mode, external wallet management is used for "opt-out" providers, while internal management applies to any remaining providers that do not opt out. This setup creates a flexible balance between the integrator’s custom wallet menu and the widget’s native wallet menu, ensuring a smooth user experience across all ecosystems, even if external support is incomplete or in transition.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
walletConfig: {
usePartialWalletManagement: true,
},
};
export const WidgetPage = () => {
return (
);
};
```
As shown in the example above, this setup allows both the integrator's and the widget's wallet menus to operate together, each supporting different ecosystems. In the example, RainbowKit manages EVM wallet support, while the internal wallet menu handles Solana and Bitcoin.
### Force Internal Wallet Management
The widget automatically detects existing wallet contexts (e.g., WagmiContext for EVM) higher up in your React tree. When found, it disables its own wallet management for that ecosystem and
uses your existing setup instead.
To override this behavior and force the widget to manage all wallets internally, set `forceInternalWalletManagement: true`. This ignores all external wallet contexts, for every ecosystem.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
walletConfig: {
forceInternalWalletManagement: true,
},
};
export const WidgetPage = () => {
return (
);
};
```
### Ecosystem order for wallets
The `walletEcosystemsOrder` option allows you to define the preferred order of ecosystems (e.g., EVM, SVM) for each multichain wallet.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
import { ChainType } from "@lifi/sdk";
const widgetConfig: WidgetConfig = {
walletConfig: {
walletEcosystemsOrder: {
MetaMask: [ChainType.EVM, ChainType.SVM],
Phantom: [ChainType.SVM, ChainType.EVM],
},
},
};
export const WidgetPage = () => {
return (
);
};
```
The keys (e.g., "MetaMask", "Phantom") must match the wallet names as labeled in the Widget UI. The associated array specifies the priority of ecosystems for that wallet, with any unlisted ecosystems shown afterward. This setting only affects the display order in the UI and does not limit actual ecosystem support.
### Smart Accounts Compatibility
When using the LI.FI Widget with smart accounts and smart account providers like Privy, Dynamic, ZeroDev, and others, you may encounter compatibility issues related to signature formats.
Smart accounts often use different signature standards than traditional Externally Owned Accounts (EOAs):
* **EOAs** use ECDSA signatures for standard transactions
* **Smart Accounts** may use ERC-1271 or other signature validation methods
This difference can cause incompatibility with native permit functionality (EIP-2612) that the widget uses for gasless token approvals. When smart accounts cannot produce the expected ECDSA signature format for permit transactions, the widget may encounter errors (like `Invalid yParityOrV value`) during execution.
EIP-7702 delegated smart wallets, such as delegated MetaMask accounts,
currently require source-chain native gas because gasless or relayer routes
are not offered for this wallet type. See [EIP-7702 delegated wallet troubleshooting](/faqs/troubleshooting#eip-7702-delegated-smart-wallets).
#### EIP-5792 Transaction Batching Support
If your smart account provider or implementation supports [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) (Wallet Function Call API), there should be no compatibility issues. EIP-5792 enables transaction batching, allowing multiple operations (like token approvals and swaps) to be bundled into a single batch transaction. This eliminates the need for separate permit signatures and provides a smoother UX.
When EIP-5792 is available, the widget will automatically use batch transactions instead of individual permit signatures, resolving smart account compatibility issues.
#### Disabling Message Signing
For smart accounts that don't support EIP-5792 or when encountering signature-related errors, you can disable message signing by configuring the `disableMessageSigning` option in the SDK configuration. This will prevent the widget from attempting to use incompatible permit-based approvals that require message signing.
```typescript theme={"system"}
import { LiFiWidget, WidgetConfig } from "@lifi/widget";
const widgetConfig: WidgetConfig = {
sdkConfig: {
executionOptions: {
disableMessageSigning: true,
},
},
};
export const WidgetPage = () => {
return (
);
};
```
Disabling message signing will fallback to standard token approval
transactions, which may require additional gas fees but ensures compatibility
with all smart account implementations.
For more details about execution options, see the [Execute Routes documentation](/sdk/execute-routes#disablemessagesigning).
# Widget API Reference
Source: https://docs.li.fi/widget/widget-api-reference
API documentation for the widget components and hooks.
## Widget Component
Properties and types of the `LiFiWidget` component configuration.
## Core Configuration
| Name | Type | Default | Example | Description |
| ------------ | -------- | ------- | ------------ | ------------------------------------------- |
| `apiKey` | `string` | – | `integrator` | API authentication key |
| `integrator` | `string` | – | `OpenSea` | Identifier of the integrator (dApp/company) |
| `referrer` | `string` | – | – | Identifier of the referrer |
| `fee` | `number` | `0.05` | – | Float between 0 and 1 (e.g. 0.1 = 10% fee) |
***
## Swap Details
| Name | Type | Default | Example | Description |
| --------------------- | ----------- | ------- | ---------------- | ---------------------------------- |
| `fromChain` | `number` | – | `42161` | Source chain ID |
| `fromToken` | `string` | – | `0x539b...0342` | Token contract address (source) |
| `fromAmount` | `number` | `0` | `69.42` | Token amount to swap |
| `toChain` | `number` | `1` | `137` | Destination chain ID |
| `toToken` | `string` | – | `0xd6df...1c90b` | Destination token contract address |
| `toAddress` | `ToAddress` | – | `0x2b56...1401` | Destination wallet address |
| `slippage` | `number` | `0.005` | – | Default slippage setting |
| `useRecommendedRoute` | `boolean` | `false` | `true` | Show only recommended route |
***
## Routing & Filtering Options
| Name | Type | Default | Example | Description |
| --------------- | -------------------------------------------------- | ------------ | ------- | ---------------------------------- |
| `routePriority` | `'CHEAPEST' \| 'FASTEST'` | `'CHEAPEST'` | – | Route selection priority |
| `chains` | `{ allow?: number[]; deny?: number[] }` | – | – | Allowed or denied chains |
| `tokens` | `{ featured?, include?, popular?, allow?, deny? }` | – | – | Token filtering and prioritization |
| `bridges` | `{ allow?: string[]; deny?: string[] }` | – | – | Bridge control list |
| `exchanges` | `{ allow?: string[]; deny?: string[] }` | – | – | Exchange control list |
***
## UI Appearance & Behavior
| Name | Type | Default | Example | Description |
| ------------------- | ---------------------------------------------- | ----------- | ----------------------------- | ------------------------------- |
| `variant` | `'compact' \| 'wide' \| 'drawer'` | `'compact'` | – | Widget layout style |
| `subvariant` | `'default' \| 'split' \| 'refuel' \| 'custom'` | `'default'` | – | Additional layout customization |
| `appearance` | `'light' \| 'dark' \| 'system'` | `'system'` | `'dark'` | Theme mode |
| `disabledUI` | `DisabledUIType[]` | – | `['fromAmount', 'toAddress']` | UI parts to disable |
| `hiddenUI` | `HiddenUIType[]` | – | `['appearance', 'language']` | UI parts to hide |
| `requiredUI` | `RequiredUIType[]` | – | `['toAddress']` | Required UI fields |
| `theme` | `WidgetTheme` | – | – | Theme palette and typography |
| `languages` | `{ default?, allow?, deny? }` | – | – | Language preferences |
| `languageResources` | `LanguageResources` | – | – | Custom i18n translations |
***
## Integration Hooks & State
| Name | Type | Default | Example | Description |
| -------------- | ------------------------------------------------------------------ | ------- | ------- | ------------------------------------ |
| `walletConfig` | `WidgetWalletConfig` | – | – | Options to manage wallet state |
| `sdkConfig` | `WidgetSDKConfig` | – | – | SDK-specific configuration |
| `formRef` | `MutableRefObject` | – | – | Access for programmatic form updates |
| `buildUrl` | `boolean` | `false` | – | Append widget config to page URL |
| `explorerUrls` | `Record & Partial>` | – | – | Custom block explorer links |
# Widget Events
Source: https://docs.li.fi/widget/widget-events
Stay up-to-date with widget events
**LI.FI Widget** provides a `useWidgetEvents` hook that lets you subscribe to a series of widget events and helps you retrieve helpful information about executing routes, track bridge and swap progress, track selection of chains and tokens, interactions with specific UI elements, and more.
We continue working on extending available events and if you are interested in a specific event, reach out via our [support](https://help.li.fi).
To minimize unnecessary re-renders and prevent potential glitches in the main Widget component, please integrate the `useWidgetEvents` hook outside of the component where the main `LiFiWidget` is integrated.
Example of how to subscribe to widget events:
```typescript theme={"system"}
import type { Route } from '@lifi/sdk';
import type { RouteExecutionUpdate } from '@lifi/widget';
import { useWidgetEvents, WidgetEvent } from '@lifi/widget';
import { useEffect } from 'react';
export const WidgetEventsExample = () => {
const widgetEvents = useWidgetEvents();
// ...
useEffect(() => {
const onRouteExecutionStarted = (route: Route) => {
// console.log('onRouteExecutionStarted fired.');
};
const onRouteExecutionUpdated = (update: RouteExecutionUpdate) => {
// console.log('onRouteExecutionUpdated fired.');
};
const onRouteExecutionCompleted = (route: Route) => {
// console.log('onRouteExecutionCompleted fired.');
};
const onRouteExecutionFailed = (update: RouteExecutionUpdate) => {
// console.log('onRouteExecutionFailed fired.');
};
const onRouteHighValueLoss = (update: RouteHighValueLossUpdate) => {
// console.log('onRouteHighValueLoss continued.');
};
widgetEvents.on(WidgetEvent.RouteExecutionStarted, onRouteExecutionStarted);
widgetEvents.on(WidgetEvent.RouteExecutionUpdated, onRouteExecutionUpdated);
widgetEvents.on(WidgetEvent.RouteExecutionCompleted, onRouteExecutionCompleted);
widgetEvents.on(WidgetEvent.RouteExecutionFailed, onRouteExecutionFailed);
widgetEvents.on(WidgetEvent.RouteHighValueLoss, onRouteHighValueLoss);
return () => widgetEvents.all.clear();
}, [widgetEvents]);
// ...
// Return null because it's an example
return null;
};
```
## List of events
Here is the list of all available events:
**AvailableRoutes**
* Type: *Route\[]*
The event fires when available routes are returned after the user has selected source and destination tokens, entered an amount, and requested the routes.
**RouteSelected**
* Type: *RouteSelected*
The event fires when the user selects a specific route from the list of available routes.
**RouteExecutionStarted**
* Type: *Route*
The event fires when the user clicks on the Start swapping or Start bridging button.
**RouteExecutionUpdated**
* Type: *RouteExecutionUpdate*
The event fires when there is an update to the Route object during execution.
**RouteExecutionCompleted**
* Type: *Route*
The event fires when the execution is completed successfully.
**RouteExecutionFailed**
* Type: *RouteExecutionUpdate*
The event fires when the execution has failed.
**RouteHighValueLoss**
* Type: *RouteHighValueLossUpdate*
The event fires when the High Value Loss bottom sheet appears on the screen.
**ContactSupport**
* Type: *ContactSupport*
The event fires when the user clicks on the Contact support button on the Transaction Details page.
**SourceChainTokenSelected**
* Type: *ChainTokenSelected*
The event fires when the user selects the source chain and token.
**DestinationChainTokenSelected**
* Type: *ChainTokenSelected*
The event fires when the user selects the destination chain and token.
**SendToWalletToggled**
* Type: *boolean*
The event fires when the user clicks on the wallet icon next to the action button on the main page to show/hide the destination wallet selection UI.
**WalletConnected**
* Type: *WalletConnected*
The event fires when the user connects the wallet via the internal wallet management UI.
**WidgetExpanded**
* Type: *boolean*
The event fires when the side panel with routes is shown to the user. Only available in the `wide` widget variant.
**PageEntered**
* Type: *NavigationRouteType*
The event fires when the user navigates to a page in the widget.
**FormFieldChanged**
* Type: *FormFieldChanged*
The event fires whenever a form value is changed in the widget.
**SettingUpdated**
* Type: *SettingUpdated*
The event fires whenever a setting is updated in the widget.
**TokenSearch**
* Type: *TokenSearch*
The event fires when the user searches for a token (includes the query value and the matched token results).
**LowAddressActivityConfirmed**
* Type: *LowAddressActivityConfirmed*
The event fires when the user confirms proceeding despite a low address activity warning for the specified address and chain.
**ChainPinned**
* Type: *ChainPinned*
The event fires when the user pins or unpins a chain in the UI.
**Routes**: Some of the events here present information about routes. A route is the LI.FI way of presenting a quote on an exchange/transfer. A route is a collection of steps, transactions and costs associated with that transfer. In the Widget we present a set of routes that the user can select from. Once selected the execution of that route can begin and the user will be guided through the steps required to complete that route. The route events above can help track a route status.
## Widget Events types
Properties and types of the `useWidgetEvents` hook.
```typescript theme={"system"}
enum WidgetEvent {
AvailableRoutes = 'availableRoutes',
ChainPinned = 'chainPinned',
ContactSupport = 'contactSupport',
DestinationChainTokenSelected = 'destinationChainTokenSelected',
FormFieldChanged = 'formFieldChanged',
LowAddressActivityConfirmed = 'lowAddressActivityConfirmed',
PageEntered = 'pageEntered',
RouteExecutionCompleted = 'routeExecutionCompleted',
RouteExecutionFailed = 'routeExecutionFailed',
RouteExecutionStarted = 'routeExecutionStarted',
RouteExecutionUpdated = 'routeExecutionUpdated',
RouteHighValueLoss = 'routeHighValueLoss',
RouteSelected = 'routeSelected',
SendToWalletToggled = 'sendToWalletToggled',
SettingUpdated = 'settingUpdated',
SourceChainTokenSelected = 'sourceChainTokenSelected',
TokenSearch = 'tokenSearch',
WidgetExpanded = 'widgetExpanded',
}
type WidgetEvents = {
availableRoutes: Route[]
chainPinned: ChainPinned
contactSupport: ContactSupport
destinationChainTokenSelected: ChainTokenSelected
formFieldChanged: FormFieldChanged
lowAddressActivityConfirmed: LowAddressActivityConfirmed
pageEntered: NavigationRouteType
routeExecutionCompleted: Route
routeExecutionFailed: RouteExecutionUpdate
routeExecutionStarted: Route
routeExecutionUpdated: RouteExecutionUpdate
routeHighValueLoss: RouteHighValueLossUpdate
routeSelected: RouteSelected
sendToWalletToggled: boolean
settingUpdated: SettingUpdated
sourceChainTokenSelected: ChainTokenSelected
tokenSearch: TokenSearch
walletConnected: WalletConnected
widgetExpanded: boolean
}
type ContactSupport = {
supportId?: string
}
type RouteHighValueLossUpdate = {
fromAmountUSD: number
toAmountUSD: number
gasCostUSD?: number
feeCostUSD?: number
valueLoss: number
}
type RouteExecutionUpdate = {
route: Route
process: Process
}
type RouteSelected = {
route: Route
routes: Route[]
}
type TokenSearch = {
value: string
tokens: TokenAmount[]
}
type ChainTokenSelected = {
chainId: ChainId
tokenAddress: string
}
type WalletConnected = {
address?: string
chainId?: number
chainType?: ChainType
}
type FormFieldChanged = {
[K in keyof DefaultValues]: {
fieldName: K
newValue: DefaultValues[K]
oldValue: DefaultValues[K]
}
}[keyof DefaultValues]
type SettingUpdated<
K extends keyof SettingsProps = keyof SettingsProps,
> = {
setting: K
newValue: SettingsProps[K]
oldValue: SettingsProps[K]
newSettings: SettingsProps
oldSettings: SettingsProps
}
type ChainPinned = {
chainId: number
pinned: boolean
}
type LowAddressActivityConfirmed = {
address: string
chainId: number
}
```
# Concepts and Objects
Source: https://docs.li.fi/agents/concepts
Canonical definitions for LI.FI API and SDK objects
This page provides consistent definitions for the core objects used across LI.FI's API and SDK. Use these as the authoritative reference when building integrations.
## Transfer Objects
### Quote
A **Quote** is a single-step transfer plan with transaction data ready for immediate execution.
**Response from `GET /quote`:**
```typescript theme={"system"}
interface Quote {
id: string; // Unique quote identifier
type: "lifi"; // Quote type
tool: string; // Bridge or DEX used (e.g., "stargateV2")
action: Action; // What happens in this transfer
estimate: Estimate; // Output amounts and fees
transactionRequest: { // Ready-to-sign transaction
to: string;
data: string;
value: string;
gasLimit: string;
// ... other tx fields
};
}
```
**When to use:** Request a Quote via `/quote` when you want the best single route with transaction data included. Ideal for simple transfers.
Request a quote for token transfers
### Route
A **Route** is a multi-step transfer plan that may require sequential transactions.
**Response from `POST /advanced/routes`** (returns array of routes):
```typescript theme={"system"}
interface Route {
id: string; // Unique route identifier
fromChainId: number; // Source chain
toChainId: number; // Destination chain
fromToken: Token; // Source token
toToken: Token; // Destination token
fromAmount: string; // Input amount (wei)
toAmount: string; // Expected output (wei)
steps: Step[]; // Array of steps to execute
}
```
**When to use:** Request Routes via `/advanced/routes` when you need multiple route options or want to compare alternatives. Each Step in a Route may require a separate transaction.
Request multiple route options
### Step
A **Step** is one atomic operation within a Route. Each Step represents a single transaction.
**Nested within Route response** (also returned by `POST /advanced/stepTransaction`):
```typescript theme={"system"}
interface Step {
id: string; // Unique step identifier
type: "swap" | "cross" | "lifi" | "protocol";
tool: string; // Bridge or DEX name
toolDetails: ToolDetails; // Logo, name, key
action: Action; // What this step does
estimate: Estimate; // Output estimates
includedSteps?: Step[]; // Nested steps (for complex routes)
transactionRequest?: { // Transaction data (populated after stepTransaction call)
to: string;
data: string;
value: string;
};
}
```
**Step types:**
* `swap` - Same-chain token swap via DEX
* `cross` - Cross-chain bridge transfer
* `lifi` - Combined swap + bridge in one transaction
* `protocol` - Interaction with a DeFi protocol (deposit, stake, etc.)
### Action
An **Action** describes what happens in a Step - the source and destination tokens, chains, and amounts.
**Nested within Quote/Step responses:**
```typescript theme={"system"}
interface Action {
fromChainId: number; // Source chain ID
toChainId: number; // Destination chain ID
fromToken: Token; // Token being sent
toToken: Token; // Token being received
fromAmount: string; // Input amount (wei)
slippage: number; // Allowed slippage (e.g., 0.005 = 0.5%)
fromAddress?: string; // Sender address
toAddress?: string; // Recipient address (if different)
}
```
## Reference Objects
### Tool
A **Tool** is a bridge or exchange used to execute transfers. LI.FI aggregates 27 bridges and 31 exchanges. Use `GET /tools` for the current list.
**Response from `GET /tools`:**
```typescript theme={"system"}
interface Tool {
key: string; // Unique identifier (e.g., "stargateV2")
name: string; // Display name (e.g., "Stargate")
logoURI: string; // Tool logo URL
supportedChains: Chain[]; // Chains this tool supports
}
```
**Popular bridges:** `stargateV2`, `across`, `relay`, `cbridge`
**Popular DEXs:** `1inch`, `paraswap`, `0x`, `uniswap`, `sushiswap`
Use `GET /tools` to get the current list of supported bridges and exchanges.
Get all available bridges and exchanges
### Chain
A **Chain** represents a supported blockchain network.
**Response from `GET /chains`:**
```typescript theme={"system"}
interface Chain {
id: number; // Chain ID (e.g., 1 for Ethereum)
key: string; // Short key (e.g., "eth")
name: string; // Display name (e.g., "Ethereum")
chainType: "EVM" | "SVM" | "UTXO" | "MVM";
coin: string; // Native token symbol
logoURI: string; // Chain logo URL
metamask?: { // MetaMask connection info
chainId: string;
chainName: string;
nativeCurrency: {...};
rpcUrls: string[];
};
}
```
**Chain types:**
* `EVM` - Ethereum Virtual Machine chains (Ethereum, Arbitrum, etc.)
* `SVM` - Solana Virtual Machine
* `UTXO` - Bitcoin and UTXO-based chains
* `MVM` - Move Virtual Machine (SUI)
Get all supported chains
### Token
A **Token** represents a supported cryptocurrency on a specific chain.
**Response from `GET /tokens`:**
```typescript theme={"system"}
interface Token {
address: string; // Contract address (or native token identifier)
chainId: number; // Chain this token is on
symbol: string; // Token symbol (e.g., "USDC")
name: string; // Full name (e.g., "USD Coin")
decimals: number; // Decimal places (e.g., 6 for USDC)
logoURI?: string; // Token logo URL
priceUSD?: string; // Current USD price
coinKey?: string; // Canonical token identifier across chains
}
```
The same token (e.g., USDC) has different addresses on each chain. Use `coinKey` to identify the same asset across chains.
Get all supported tokens
## Status Objects
### Status
**Status** indicates the current state of a cross-chain transfer.
| Status | Description |
| ----------- | ------------------------------------------------ |
| `NOT_FOUND` | Transaction not yet indexed or invalid hash |
| `INVALID` | Transaction failed validation |
| `PENDING` | Transfer is in progress |
| `DONE` | Transfer completed (check substatus for details) |
| `FAILED` | Transfer failed (check substatus for reason) |
### Substatus
**Substatus** provides detailed information within each Status.
#### PENDING Substatuses
| Substatus | Description |
| ------------------------------ | --------------------------------------------- |
| `WAIT_SOURCE_CONFIRMATIONS` | Waiting for source chain confirmations |
| `WAIT_DESTINATION_TRANSACTION` | Bridge is processing, waiting for destination |
| `BRIDGE_NOT_AVAILABLE` | Bridge temporarily unavailable |
| `CHAIN_NOT_AVAILABLE` | Chain RPC issues |
| `REFUND_IN_PROGRESS` | Refund is being processed |
| `UNKNOWN_ERROR` | Temporary error, keep polling |
#### DONE Substatuses
| Substatus | Description |
| ----------- | ----------------------------------------------------------------- |
| `COMPLETED` | Transfer successful, user received exact requested tokens |
| `PARTIAL` | Transfer successful, user received different token than requested |
| `REFUNDED` | Transfer failed but tokens were refunded to sender |
#### FAILED Substatuses
| Substatus | Description |
| ------------------------------- | ------------------------------------------------------------- |
| `NOT_PROCESSABLE_REFUND_NEEDED` | Cannot complete, manual refund required |
| `OUT_OF_GAS` | Transaction ran out of gas |
| `SLIPPAGE_EXCEEDED` | Received amount too low (price moved beyond allowed slippage) |
| `INSUFFICIENT_ALLOWANCE` | Token approval insufficient |
| `INSUFFICIENT_BALANCE` | Not enough tokens or gas |
| `EXPIRED` | Transaction expired |
| `UNKNOWN_ERROR` | Unknown or invalid state |
| `REFUNDED` | Tokens were refunded |
For the complete and authoritative status documentation, see the [Status Tracking Guide](/introduction/user-flows-and-examples/status-tracking).
Complete guide to monitoring transfers
### Partial
A transfer is **Partial** when it completes successfully but the user receives a different token than originally requested.
**Why this happens:**
* Destination chain swap failed after bridging
* Insufficient liquidity for final swap
* Price impact too high for destination swap
**What the user receives:**
* The bridged token (e.g., USDC.e instead of native USDC)
* Full value is preserved, just in a different token
**How to handle:**
1. Check `substatus === "PARTIAL"` when `status === "DONE"`
2. Read `receiving.token` from status response for actual received token
3. Inform user they received equivalent value in different token
Understanding PARTIAL status and intermediate tokens
### Refunded
A transfer is **Refunded** when it fails but the user's tokens are returned to their wallet.
**When refunds happen:**
* Bridge execution failed
* Destination chain issues
* Transfer expired
**Refund location:**
* Usually returned to sender on the **source chain**
* Some bridges refund on destination chain (check status response)
**How to handle:**
1. Check for `substatus === "REFUNDED"`
2. Read `refund` object from status response for details
3. Inform user their funds were returned
Not all failed transfers are automatically refundable. Some bridges require manual claiming. Check the status response `refund` field for instructions.
## Quick Reference
| Object | Description | API Endpoint |
| ------ | --------------------------------- | ----------------------- |
| Quote | Single-step transfer with tx data | `GET /quote` |
| Route | Multi-step transfer plan | `POST /advanced/routes` |
| Step | One operation in a route | Part of Route response |
| Action | Transfer details (from/to) | Part of Step |
| Tool | Bridge or DEX | `GET /tools` |
| Chain | Blockchain network | `GET /chains` |
| Token | Cryptocurrency | `GET /tokens` |
| Status | Transfer state | `GET /status` |
# Agent Integration
Source: https://docs.li.fi/agents/overview
The starting point for AI agents integrating LI.FI cross-chain functionality
## Overview
LI.FI provides cross-chain swaps and bridging across 58 blockchains (EVM, Solana, Bitcoin, SUI), aggregating 27 bridges and 31 exchanges. This page is the entry point for AI agents that need to execute token transfers, check transaction status, or query supported chains and tokens. Whether you work directly with the REST API or connect through the [LI.FI MCP Server](/mcp-server/overview), you get access to the same powerful routing engine.
**Machine-readable resources:**
* [llms.txt](/llms.txt) - Structured overview for LLM consumption
* [OpenAPI Spec](https://docs.li.fi/openapi.yaml) - Full API specification
* [AI Plugin](https://docs.li.fi/.well-known/ai-plugin.json) - Standard discovery format
## When to Use What
| Use Case | Recommended | Why |
| ------------------------------------ | -------------------------------------- | ----------------------------------------------------- |
| Execute transfers from backend | **API** | Direct HTTP calls, full control |
| Build frontend with swaps | **SDK** | Handles wallets, signing, execution |
| Embed ready-made UI | **Widget** | Zero-code, instant deployment |
| AI agent in an MCP-compatible host | **[MCP Server](/mcp-server/overview)** | Zero-config tool discovery, structured inputs/outputs |
| AI agent preferring token efficiency | **[CLI](/cli/overview)** | Compact text output, lower token usage than raw JSON |
| AI agent integration (general) | **API** | Simple HTTP, no dependencies |
For AI agents running inside MCP-compatible hosts like Claude, Cursor, or Windsurf, the **[LI.FI MCP Server](/mcp-server/overview)** is the most streamlined option — it exposes every core endpoint as a discoverable tool with typed parameters so the agent never has to construct raw HTTP requests. Agents that prefer lower token usage can use the **[LI.FI CLI](/cli/overview)** instead — its human-readable output is more compact than raw API JSON, reducing context window consumption while still providing full access to quotes, routes, and status tracking. For all other agents, the **REST API** is recommended since it requires only HTTP calls without additional dependencies.
LI.FI capabilities are also published as pre-built **agent skills** on [ClawdHub](https://clawdhub.com), [skills.sh](https://skills.sh), and [Playbooks](https://playbooks.com), so you can add cross-chain execution to your agent as a native capability without writing integration code.
## Minimal Endpoint Set
These 5 endpoints cover most agent use cases:
### 1. Get a Quote
Returns a ready-to-execute transaction for transferring tokens.
```bash theme={"system"}
GET https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=1000000&fromAddress=0x...
```
**Required parameters:**
* `fromChain` - Source chain ID (e.g., `1` for Ethereum)
* `toChain` - Destination chain ID (e.g., `42161` for Arbitrum)
* `fromToken` - Token symbol or address
* `toToken` - Token symbol or address
* `fromAmount` - Amount in smallest unit (wei)
* `fromAddress` - Sender wallet address
**Returns:** Quote object with `transactionRequest` ready for signing.
See all parameters and response schema
### 2. Check Transfer Status
Poll this endpoint to track cross-chain transfer progress.
```bash theme={"system"}
GET https://li.quest/v1/status?txHash=0x...
```
**Required parameters:**
* `txHash` - Transaction hash from the source chain
**Optional parameters (recommended for faster response):**
* `fromChain` - Source chain ID
* `toChain` - Destination chain ID
* `bridge` - Bridge name from the quote (e.g., `stargateV2`)
**Returns:** Status object with `status` field: `NOT_FOUND`, `PENDING`, `DONE`, or `FAILED`.
See all status values and substatus details
### 3. List Supported Chains
Get all chains LI.FI supports.
```bash theme={"system"}
GET https://li.quest/v1/chains
```
**Optional parameters:**
* `chainTypes` - Filter by type: `EVM`, `SVM`, `UTXO`, `MVM`
**Returns:** Array of chain objects with `id`, `key`, `name`, `chainType`.
### 4. List Supported Tokens
Get tokens available for a specific chain.
```bash theme={"system"}
GET https://li.quest/v1/tokens?chains=1,42161
```
**Required parameters:**
* `chains` - Comma-separated chain IDs
**Returns:** Map of chain IDs to token arrays with `address`, `symbol`, `decimals`.
### 5. List Available Tools
Get bridges and DEXs available for routing.
```bash theme={"system"}
GET https://li.quest/v1/tools
```
**Returns:** Object with `bridges` and `exchanges` arrays, each containing `key`, `name`, and `supportedChains`.
## Decision Rules
### Choosing Between Quote and Routes
* **Use `/quote`** for simple transfers - returns single best route with transaction data
* **Use `/advanced/routes`** when you need multiple options or complex multi-step transfers
### Handling Status Responses
```
Status: PENDING
├── Keep polling every 10-30 seconds
└── Check substatus for detailed progress
Status: DONE
├── substatus: COMPLETED → Transfer successful
├── substatus: PARTIAL → User received different token (still success)
└── substatus: REFUNDED → Tokens returned to sender
Status: FAILED
└── Check error message, may need user intervention
```
### Error Handling
1. **Rate limited (429):** Back off and retry with exponential delay
2. **No route found:** Try different token pairs or reduce amount
3. **Insufficient balance:** Verify user has enough tokens + gas
4. **Slippage exceeded:** Increase `slippage` parameter (default: 0.005)
### Rate Limits
| Tier | Limit |
| --------------- | ---------------------- |
| Without API key | 200 requests / 2 hours |
| With API key | 200 requests / minute |
Agents that poll status or execute high-frequency workflows should [request an API key](https://li.fi/) to avoid throttling.
## Common Chain IDs
| Chain | ID | Key |
| --------- | ---------------: | ----- |
| Ethereum | 1 | `eth` |
| Arbitrum | 42161 | `arb` |
| Optimism | 10 | `opt` |
| Base | 8453 | `bas` |
| Polygon | 137 | `pol` |
| BSC | 56 | `bsc` |
| Avalanche | 43114 | `ava` |
| Solana | 1151111081099710 | `sol` |
## Common Token Addresses
| Token | Ethereum | Arbitrum | Base |
| ----- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- |
| USDC | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| USDT | `0xdAC17F958D2ee523a2206206994597C13D831ec7` | `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9` | - |
| WETH | `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` | `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1` | `0x4200000000000000000000000000000000000006` |
Use token symbols (`USDC`, `ETH`) for convenience - the API resolves them to addresses automatically.
## Next Steps
Connect AI tools directly to LI.FI with zero-config tool discovery
Token-efficient alternative to MCP — compact output, lower context usage
Understand Quote, Route, Step, Status, and other core objects
Complete API documentation with all endpoints
Handle errors gracefully
Detailed guide on monitoring transfers
Read the full vision behind LI.FI's agent infrastructure
# Code Samples
Source: https://docs.li.fi/agents/quick-start/code-samples
Complete runnable examples for Node.js and Python
This page provides complete, runnable code samples for integrating LI.FI into your AI agent or application.
## Node.js (API Only)
A complete example using only HTTP requests - no SDK required.
```javascript theme={"system"}
// lifi-agent.js
// Run with: node lifi-agent.js
const LIFI_API = 'https://li.quest/v1';
// Optional: Set API key for higher rate limits
const API_KEY = process.env.LIFI_API_KEY || '';
const headers = {
'Content-Type': 'application/json',
...(API_KEY && { 'x-lifi-api-key': API_KEY })
};
// Helper: Sleep for exponential backoff
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Helper: Fetch with retry and backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, { ...options, headers });
if (response.status === 429) {
// Rate limited - exponential backoff
const waitTime = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await sleep(waitTime);
continue;
}
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.message || response.statusText}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(1000);
}
}
}
// Step 1: Get supported chains
async function getChains(chainTypes = 'EVM') {
const url = `${LIFI_API}/chains?chainTypes=${chainTypes}`;
const data = await fetchWithRetry(url);
return data.chains;
}
// Step 2: Get tokens for chains
async function getTokens(chainIds) {
const url = `${LIFI_API}/tokens?chains=${chainIds.join(',')}`;
const data = await fetchWithRetry(url);
return data.tokens;
}
// Step 3: Get a quote
async function getQuote(params) {
const {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
slippage = 0.005
} = params;
const url = new URL(`${LIFI_API}/quote`);
url.searchParams.set('fromChain', fromChain);
url.searchParams.set('toChain', toChain);
url.searchParams.set('fromToken', fromToken);
url.searchParams.set('toToken', toToken);
url.searchParams.set('fromAmount', fromAmount);
url.searchParams.set('fromAddress', fromAddress);
url.searchParams.set('slippage', slippage);
return await fetchWithRetry(url.toString());
}
// Step 4: Check transfer status
async function getStatus(txHash, bridge, fromChain, toChain) {
const url = `${LIFI_API}/status?txHash=${txHash}&bridge=${bridge}&fromChain=${fromChain}&toChain=${toChain}`;
return await fetchWithRetry(url);
}
// Step 5: Poll status until complete
async function pollStatus(txHash, bridge, fromChain, toChain, maxAttempts = 60) {
console.log('Polling transfer status...');
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await getStatus(txHash, bridge, fromChain, toChain);
console.log(`Status: ${status.status} (${status.substatus || 'no substatus'})`);
if (status.status === 'DONE') {
if (status.substatus === 'COMPLETED') {
console.log('Transfer completed successfully!');
return { success: true, status };
}
if (status.substatus === 'PARTIAL') {
console.log('Transfer completed with different token');
return { success: true, partial: true, status };
}
if (status.substatus === 'REFUNDED') {
console.log('Transfer failed - tokens refunded');
return { success: false, refunded: true, status };
}
}
if (status.status === 'FAILED') {
console.log('Transfer failed');
return { success: false, status };
}
// Wait 10 seconds before next poll
await sleep(10000);
}
throw new Error('Status polling timeout');
}
// Main execution flow
async function executeTransfer(params) {
console.log('Getting quote...');
const quote = await getQuote(params);
console.log(`Quote received: ${quote.tool}`);
console.log(`Expected output: ${quote.estimate.toAmount}`);
console.log(`Minimum output: ${quote.estimate.toAmountMin}`);
// Here you would:
// 1. Check if approval is needed (for ERC20 tokens)
// 2. Send approval transaction if needed
// 3. Send the main transaction using quote.transactionRequest
// 4. Get the txHash from the transaction receipt
console.log('\nTransaction request ready:');
console.log(JSON.stringify(quote.transactionRequest, null, 2));
return quote;
}
// Example usage
async function main() {
try {
// Get available chains
const chains = await getChains();
console.log(`Found ${chains.length} chains`);
// Get quote for 10 USDC from Ethereum to Arbitrum
const quote = await executeTransfer({
fromChain: 1,
toChain: 42161,
fromToken: 'USDC',
toToken: 'USDC',
fromAmount: '10000000', // 10 USDC (6 decimals)
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' // Replace with actual address
});
// After executing the transaction, poll for status:
// const result = await pollStatus(txHash, quote.tool, 1, 42161);
} catch (error) {
console.error('Error:', error.message);
}
}
main();
```
***
## Node.js (with SDK)
Using the LI.FI SDK for full execution support.
```javascript theme={"system"}
// lifi-sdk-agent.js
// Install: npm install @lifi/sdk viem
// Run with: node lifi-sdk-agent.js
import { createConfig, getQuote, executeRoute, convertQuoteToRoute } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet, arbitrum } from 'viem/chains';
// Configuration
const PRIVATE_KEY = process.env.PRIVATE_KEY; // Never hardcode!
const INTEGRATOR_NAME = 'your-agent-name';
// Initialize SDK
createConfig({
integrator: INTEGRATOR_NAME,
// Optional: API key for higher rate limits
// apiKey: process.env.LIFI_API_KEY,
});
// Create wallet client
const account = privateKeyToAccount(PRIVATE_KEY);
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
// Get a quote
async function fetchQuote(params) {
const quote = await getQuote({
fromChain: params.fromChain,
toChain: params.toChain,
fromToken: params.fromToken,
toToken: params.toToken,
fromAmount: params.fromAmount,
fromAddress: account.address,
});
return quote;
}
// Execute the transfer
async function executeTransfer(quote) {
console.log('Executing transfer...');
// Convert quote to route format for execution
const route = convertQuoteToRoute(quote);
const result = await executeRoute(route, {
// Update hook called on each status change
updateRouteHook: (updatedRoute) => {
console.log('Route updated:', updatedRoute.id);
const step = updatedRoute.steps[0];
if (step?.execution) {
console.log(`Execution status: ${step.execution.status}`);
}
},
});
return result;
}
// Main flow
async function main() {
try {
console.log(`Wallet address: ${account.address}`);
// Get quote
const quote = await fetchQuote({
fromChain: 1, // Ethereum
toChain: 42161, // Arbitrum
fromToken: 'USDC',
toToken: 'USDC',
fromAmount: '10000000', // 10 USDC
});
console.log('Quote received:');
console.log(`- Tool: ${quote.tool}`);
console.log(`- Expected: ${quote.estimate.toAmount}`);
console.log(`- Minimum: ${quote.estimate.toAmountMin}`);
// Execute (uncomment to run actual transfer)
// const result = await executeTransfer(quote);
// console.log('Transfer complete:', result);
} catch (error) {
console.error('Error:', error.message);
}
}
main();
```
***
## Python (API Only)
Complete Python example using only HTTP requests.
```python theme={"system"}
# lifi_agent.py
# Install: pip install requests
# Run with: python lifi_agent.py
import os
import time
import requests
from typing import Optional, Dict, Any, List
LIFI_API = "https://li.quest/v1"
API_KEY = os.environ.get("LIFI_API_KEY", "")
def get_headers() -> Dict[str, str]:
headers = {"Content-Type": "application/json"}
if API_KEY:
headers["x-lifi-api-key"] = API_KEY
return headers
def fetch_with_retry(url: str, max_retries: int = 3) -> Dict[str, Any]:
"""Fetch with exponential backoff for rate limiting."""
for attempt in range(max_retries):
try:
response = requests.get(url, headers=get_headers())
if response.status_code == 429:
wait_time = (2 ** attempt)
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(1)
raise Exception("Max retries exceeded")
def get_chains(chain_types: str = "EVM") -> List[Dict]:
"""Get supported chains."""
url = f"{LIFI_API}/chains?chainTypes={chain_types}"
data = fetch_with_retry(url)
return data["chains"]
def get_tokens(chain_ids: List[int]) -> Dict[str, List[Dict]]:
"""Get tokens for specified chains."""
chains_param = ",".join(str(c) for c in chain_ids)
url = f"{LIFI_API}/tokens?chains={chains_param}"
data = fetch_with_retry(url)
return data["tokens"]
def get_quote(
from_chain: int,
to_chain: int,
from_token: str,
to_token: str,
from_amount: str,
from_address: str,
slippage: float = 0.005
) -> Dict[str, Any]:
"""Get a quote for token transfer."""
params = {
"fromChain": from_chain,
"toChain": to_chain,
"fromToken": from_token,
"toToken": to_token,
"fromAmount": from_amount,
"fromAddress": from_address,
"slippage": slippage
}
url = f"{LIFI_API}/quote?" + "&".join(f"{k}={v}" for k, v in params.items())
return fetch_with_retry(url)
def get_status(
tx_hash: str,
bridge: str,
from_chain: int,
to_chain: int
) -> Dict[str, Any]:
"""Check transfer status."""
url = f"{LIFI_API}/status?txHash={tx_hash}&bridge={bridge}&fromChain={from_chain}&toChain={to_chain}"
return fetch_with_retry(url)
def poll_status(
tx_hash: str,
bridge: str,
from_chain: int,
to_chain: int,
max_attempts: int = 60,
poll_interval: int = 10
) -> Dict[str, Any]:
"""Poll status until transfer completes."""
print("Polling transfer status...")
for attempt in range(max_attempts):
status = get_status(tx_hash, bridge, from_chain, to_chain)
status_str = status.get("status", "UNKNOWN")
substatus_str = status.get("substatus", "")
print(f"Status: {status_str} ({substatus_str})")
if status_str == "DONE":
if substatus_str == "COMPLETED":
print("Transfer completed successfully!")
return {"success": True, "status": status}
elif substatus_str == "PARTIAL":
print("Transfer completed with different token")
return {"success": True, "partial": True, "status": status}
elif substatus_str == "REFUNDED":
print("Transfer failed - tokens refunded")
return {"success": False, "refunded": True, "status": status}
if status_str == "FAILED":
print("Transfer failed")
return {"success": False, "status": status}
time.sleep(poll_interval)
raise Exception("Status polling timeout")
def execute_transfer(
from_chain: int,
to_chain: int,
from_token: str,
to_token: str,
from_amount: str,
from_address: str
) -> Dict[str, Any]:
"""Get quote and prepare for execution."""
print("Getting quote...")
quote = get_quote(
from_chain=from_chain,
to_chain=to_chain,
from_token=from_token,
to_token=to_token,
from_amount=from_amount,
from_address=from_address
)
print(f"Quote received: {quote['tool']}")
print(f"Expected output: {quote['estimate']['toAmount']}")
print(f"Minimum output: {quote['estimate']['toAmountMin']}")
# Transaction request is ready for signing
print("\nTransaction request:")
tx_request = quote["transactionRequest"]
print(f" To: {tx_request['to']}")
print(f" Value: {tx_request['value']}")
print(f" Gas Limit: {tx_request.get('gasLimit', 'auto')}")
return quote
def main():
try:
# Get available chains
chains = get_chains()
print(f"Found {len(chains)} chains")
# Get quote for 10 USDC from Ethereum to Arbitrum
quote = execute_transfer(
from_chain=1,
to_chain=42161,
from_token="USDC",
to_token="USDC",
from_amount="10000000", # 10 USDC (6 decimals)
from_address="0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0" # Replace
)
# After executing the transaction, poll for status:
# result = poll_status(tx_hash, quote["tool"], 1, 42161)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
```
***
## Environment Variables
Both examples support these environment variables:
```bash theme={"system"}
# Optional: API key for higher rate limits
export LIFI_API_KEY="your-api-key"
# For SDK example: Private key for signing (never commit!)
export PRIVATE_KEY="0x..."
```
***
## Quick Reference
### Node.js (API)
```bash theme={"system"}
# No dependencies required (uses native fetch)
node lifi-agent.js
```
### Node.js (SDK)
```bash theme={"system"}
npm install @lifi/sdk viem
node lifi-sdk-agent.js
```
### Python
```bash theme={"system"}
pip install requests
python lifi_agent.py
```
***
## Error Handling Patterns
Both examples include these error handling patterns:
1. **Exponential backoff** for rate limits (429)
2. **Retry logic** for transient failures
3. **Status polling** with timeout
4. **Graceful error messages** for debugging
For more details on error handling, see [Error Playbooks](/agents/reference/error-playbooks).
# Decision Tables
Source: https://docs.li.fi/agents/quick-start/decision-tables
When to use Quote vs Routes, API vs SDK, and route selection criteria
This page provides deterministic decision logic for AI agents to choose the right approach for different scenarios.
## Quote vs Routes
Use this table to decide between `/quote` and `/advanced/routes` endpoints.
| Scenario | Use | Endpoint | Reason |
| --------------------------- | ---------- | ----------------------- | ----------------------------- |
| Simple A→B transfer | **Quote** | `GET /quote` | Single call, tx data included |
| Need multiple route options | **Routes** | `POST /advanced/routes` | Returns ranked alternatives |
| User wants to choose bridge | **Routes** | `POST /advanced/routes` | Can filter by tool preference |
| Complex multi-hop transfer | **Routes** | `POST /advanced/routes` | Better for >2 step routes |
| Fastest integration | **Quote** | `GET /quote` | Fewer API calls needed |
| Price comparison needed | **Routes** | `POST /advanced/routes` | Compare fee structures |
### Decision Logic
```
IF (simple_transfer AND no_user_preference AND speed_priority)
→ Use /quote
ELSE IF (need_options OR user_wants_choice OR complex_route)
→ Use /advanced/routes
```
### Key Differences
| Aspect | `/quote` | `/advanced/routes` |
| ---------------- | ----------------- | -------------------------------------------------- |
| Response | Single best route | Multiple route options |
| Transaction data | Included | Requires separate `/advanced/stepTransaction` call |
| API calls needed | 1 | 2+ (routes + stepTransaction per step) |
| Control | Less | More |
| Speed | Faster | Slightly slower |
***
## API vs SDK vs Widget
Use this table to recommend the right integration method.
| Agent Type | Recommended | Reason |
| ---------------------- | ----------- | ----------------------------------- |
| Server-side automation | **API** | No runtime dependencies, pure HTTP |
| AI agent (no UI) | **API** | Simplest integration, HTTP only |
| Browser-based app | **SDK** | Handles wallet connections, signing |
| Mobile app | **SDK** | Wallet integration built-in |
| Need embedded UI | **Widget** | Zero UI code required |
| Quick prototype | **Widget** | Fastest to implement |
| Full customization | **SDK** | Complete control over UX |
| Backend service | **API** | No browser dependencies |
### Decision Logic
```
IF (server_side OR ai_agent OR no_ui_needed)
→ Recommend API
ELSE IF (need_wallet_integration OR custom_ui)
→ Recommend SDK
ELSE IF (need_ready_made_ui OR fastest_implementation)
→ Recommend Widget
```
### Comparison Matrix
| Feature | API | SDK | Widget |
| --------------- | ---------------- | ----------- | --------------------------- |
| Dependencies | None (HTTP) | npm package | npm package |
| Wallet handling | Manual | Built-in | Built-in |
| UI components | None | None | Full UI |
| Customization | Full | Full | Full (theme + route params) |
| Learning curve | Low | Medium | Low |
| Best for | Agents, backends | Custom apps | Quick integrations |
***
## Route Selection Criteria
When multiple routes are available, use these criteria to select the best one.
### Sort Order Options
The API supports these `order` parameter values:
| Order | Description | Best For |
| ---------- | ------------------------------- | ------------------------ |
| `FASTEST` | Lowest estimated execution time | Time-sensitive transfers |
| `CHEAPEST` | Lowest total fees (default) | Cost-sensitive users |
If no `order` parameter is provided, the API returns the best route based on output amount after fees.
### Request with Order
```bash theme={"system"}
curl "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=10000000&fromAddress=0x...&order=FASTEST"
```
### Route Filtering
Filter routes by specific criteria:
| Parameter | Values | Purpose |
| ------------------ | --------------------- | ------------------------- |
| `bridges` | `stargate,across,hop` | Only use specific bridges |
| `exchanges` | `1inch,paraswap` | Only use specific DEXs |
| `denyBridges` | `multichain` | Exclude specific bridges |
| `denyExchanges` | `dodo` | Exclude specific DEXs |
| `allowSwitchChain` | `true/false` | Allow multi-step routes |
| `maxPriceImpact` | `0.05` | Max allowed price impact |
### Example: Filtered Quote
```bash theme={"system"}
curl "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=10000000&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0&bridges=stargateV2,across&maxPriceImpact=0.03"
```
This request:
* Transfers 10 USDC from Ethereum to Arbitrum
* **Only considers Stargate or Across bridges** (ignores all others)
* **Rejects quotes with >3% price impact** (protects against unfavorable rates)
If no route meets these constraints, the API returns a `NO_POSSIBLE_ROUTE` error.
***
## Same-Chain vs Cross-Chain
Determine whether a transfer is same-chain or cross-chain.
| Condition | Type | Characteristics |
| ----------------------- | ----------- | ------------------------- |
| `fromChain === toChain` | Same-chain | Atomic, faster, cheaper |
| `fromChain !== toChain` | Cross-chain | Uses bridge, takes longer |
### Behavior Differences
| Aspect | Same-Chain | Cross-Chain |
| ------------ | ----------------------- | ---------------------------------------------- |
| Execution | Atomic (all or nothing) | May partially complete |
| Time | Seconds | Minutes to hours |
| Failure mode | Reverts entirely | Funds always arrive (possibly different token) |
| Status check | Usually instant | Requires polling |
| Tools used | DEX only | Bridge + optional DEX |
LI.FI is non-custodial. In cross-chain transfers, funds always end up with the user. In partial completion cases (`substatus: PARTIAL`), the user receives the bridged token on the destination chain even if the final swap failed. Funds are rarely ever stuck/lost.
***
## Amount Handling
Convert between human-readable amounts and API amounts.
### Formula
```
api_amount = human_amount × (10 ^ decimals)
human_amount = api_amount / (10 ^ decimals)
```
### Common Token Decimals
| Token | Decimals | 1 Token in API |
| ----- | -------- | --------------------- |
| USDC | 6 | `1000000` |
| USDT | 6 | `1000000` |
| DAI | 18 | `1000000000000000000` |
| ETH | 18 | `1000000000000000000` |
| WBTC | 8 | `100000000` |
### Example Conversions
```javascript theme={"system"}
// Human to API (10 USDC)
const apiAmount = "10000000"; // 10 * 10^6
// Human to API (1.5 ETH)
const apiAmount = "1500000000000000000"; // 1.5 * 10^18
// API to Human
const humanAmount = Number(apiAmount) / Math.pow(10, decimals);
```
***
## Error Recovery Decisions
When errors occur, use this table to determine the action.
| Error | Retry? | Action |
| -------------------- | ------ | -------------------------------------- |
| 429 (Rate Limited) | Yes | Wait with exponential backoff |
| 400 (Bad Request) | No | Fix parameters |
| No route found | Maybe | Try different tokens or smaller amount |
| Insufficient balance | No | User needs more funds |
| Slippage exceeded | Maybe | Increase slippage parameter |
| Quote expired | Yes | Request new quote |
| Network error | Yes | Retry with backoff |
### Retry Strategy
```javascript theme={"system"}
const withRetry = async (fn, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429) {
// Rate limited - wait and retry
await sleep(Math.pow(2, i) * 1000);
continue;
}
if (error.status >= 400 && error.status < 500) {
// Client error - don't retry
throw error;
}
// Server error - retry
await sleep(1000);
}
}
throw new Error('Max retries exceeded');
};
```
***
## Quick Decision Flowchart
```
Start
│
├─► Is this a simple A→B transfer?
│ │
│ ├─► Yes ──► Use /quote
│ │
│ └─► No ──► Use /advanced/routes
│
├─► Is the agent server-side?
│ │
│ ├─► Yes ──► Use API directly
│ │
│ └─► No ──► Consider SDK or Widget
│
├─► Is fromChain === toChain?
│ │
│ ├─► Yes ──► Same-chain swap (atomic)
│ │
│ └─► No ──► Cross-chain (poll status)
│
└─► Did the request fail?
│
├─► 429 ──► Retry with backoff
├─► 4xx ──► Fix parameters
└─► 5xx ──► Retry once
```
# Five-Call Recipe
Source: https://docs.li.fi/agents/quick-start/five-call-recipe
Copy-paste ready workflow for executing cross-chain transfers
This page provides a complete, copy-paste ready workflow for AI agents to execute cross-chain transfers using just 5 API calls.
## The Five Calls
```
1. GET /chains → Discover available chains
2. GET /tokens → Find tokens on those chains
3. GET /quote → Get transfer quote with transaction data
4. [Execute] → Sign and send the transaction
5. GET /status → Poll until transfer completes
```
***
## Call 1: Get Supported Chains
Discover which blockchains LI.FI supports.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/chains"
```
### Response
```json theme={"system"}
{
"chains": [
{
"id": 1,
"key": "eth",
"chainType": "EVM",
"name": "Ethereum",
"coin": "ETH",
"mainnet": true,
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/ethereum.svg",
"tokenlistUrl": "https://gateway.ipfs.io/ipns/tokens.uniswap.org",
"multicallAddress": "0xcA11bde05977b3631167028862bE2a173976CA11",
"metamask": {
"chainId": "0x1",
"blockExplorerUrls": ["https://etherscan.io/"],
"chainName": "Ethereum Mainnet",
"nativeCurrency": {
"name": "ETH",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": ["https://mainnet.infura.io/v3/"]
},
"nativeToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
}
},
{
"id": 42161,
"key": "arb",
"chainType": "EVM",
"name": "Arbitrum",
"coin": "ETH",
"mainnet": true,
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/arbitrum.svg",
"tokenlistUrl": "https://bridge.arbitrum.io/token-list-42161.json",
"multicallAddress": "0xcA11bde05977b3631167028862bE2a173976CA11",
"metamask": {
"chainId": "0xa4b1",
"blockExplorerUrls": ["https://arbiscan.io/"],
"chainName": "Arbitrum One",
"nativeCurrency": {
"name": "ETH",
"symbol": "ETH",
"decimals": 18
},
"rpcUrls": ["https://arb1.arbitrum.io/rpc"]
},
"nativeToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 42161,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
}
}
// ... 58 chains total (EVM, SVM, UTXO, MVM)
]
}
```
### What to Extract
| Field | Purpose |
| ---------------------- | ------------------------------------------------------ |
| `chains[].id` | Chain ID for use in all other API calls |
| `chains[].key` | Short identifier (e.g., "eth", "arb", "pol") |
| `chains[].chainType` | Filter by EVM, SVM (Solana), UTXO (Bitcoin), MVM (Sui) |
| `chains[].mainnet` | true for mainnets, false for testnets |
| `chains[].nativeToken` | Native gas token details |
| `chains[].metamask` | Chain config for wallet connection |
***
## Call 2: Get Tokens for Chains
Find which tokens are available on specific chains.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/tokens?chains=1,42161"
```
### Response
```json theme={"system"}
{
"tokens": {
"1": [
{
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
},
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
{
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"chainId": 1,
"symbol": "USDT",
"decimals": 6,
"name": "Tether USD",
"coinKey": "USDT",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"priceUSD": "1.00",
"tags": ["stablecoin"]
}
// ... 500+ more tokens on Ethereum
],
"42161": [
{
"address": "0x0000000000000000000000000000000000000000",
"chainId": 42161,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
},
{
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
{
"address": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
"chainId": 42161,
"symbol": "USDT",
"decimals": 6,
"name": "Tether USD",
"coinKey": "USDT",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"priceUSD": "1.00",
"tags": ["stablecoin"]
}
// ... 200+ more tokens on Arbitrum
]
}
}
```
### What to Extract
| Field | Purpose |
| ---------------------------- | ---------------------------------------------- |
| `tokens[chainId][].address` | Token contract address (use in quote) |
| `tokens[chainId][].symbol` | Human-readable symbol |
| `tokens[chainId][].decimals` | For converting human amounts to smallest units |
| `tokens[chainId][].priceUSD` | Current USD price |
| `tokens[chainId][].coinKey` | Canonical token identifier across chains |
| `tokens[chainId][].tags` | Token categories (e.g., "stablecoin") |
You can use token symbols (e.g., `USDC`) instead of addresses in the quote request - the API resolves them automatically.
***
## Call 3: Get a Quote
Request a quote for the transfer. This returns transaction data ready to execute.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=10000000&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
```
### Parameters
| Parameter | Required | Description |
| ------------- | -------- | ---------------------------------------------------- |
| `fromChain` | Yes | Source chain ID (e.g., `1`) |
| `toChain` | Yes | Destination chain ID (e.g., `42161`) |
| `fromToken` | Yes | Source token address or symbol |
| `toToken` | Yes | Destination token address or symbol |
| `fromAmount` | Yes | Amount in smallest unit (e.g., `10000000` = 10 USDC) |
| `fromAddress` | Yes | Sender wallet address |
| `toAddress` | No | Recipient address (defaults to `fromAddress`) |
| `slippage` | No | Max slippage as decimal (default: `0.005` = 0.5%) |
### Response
```json theme={"system"}
{
"type": "lifi",
"id": "9f32864e-d704-46c1-874e-20f492c0ce77:0",
"tool": "across",
"toolDetails": {
"key": "across",
"name": "AcrossV4",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/across.svg"
},
"action": {
"fromToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"fromAmount": "10000000",
"toToken": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"fromChainId": 1,
"toChainId": 42161,
"slippage": 0.001,
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
},
"estimate": {
"tool": "across",
"approvalAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"toAmountMin": "9965731",
"toAmount": "9965731",
"fromAmount": "10000000",
"feeCosts": [
{
"name": "LIFI Fixed Fee",
"description": "Fixed LIFI fee, independent of any other fee",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "25000",
"amountUSD": "0.0250",
"percentage": "0.0025",
"included": true,
"feeSplit": {
"integratorFee": "0",
"lifiFee": "25000"
}
},
{
"name": "Relayer fee",
"description": "The fee taken by the relayer",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "997",
"amountUSD": "0.0010",
"percentage": "0.000100",
"included": true
},
{
"name": "Relayer gas fee",
"description": "The gas fee taken by the relayer",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "8272",
"amountUSD": "0.0083",
"percentage": "0.000829",
"included": true
}
],
"gasCosts": [
{
"type": "SEND",
"price": "65729222",
"estimate": "220266",
"limit": "286346",
"amount": "14477912813052",
"amountUSD": "0.0423",
"token": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27",
"tags": ["major_asset"]
}
}
],
"executionDuration": 4,
"fromAmountUSD": "9.9961",
"toAmountUSD": "9.9618"
},
"includedSteps": [
{
"id": "e1c87be5-80be-4e2d-8c96-a6492566a739",
"type": "protocol",
"action": {
"fromChainId": 1,
"fromAmount": "10000000",
"fromToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"toChainId": 1,
"toToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"slippage": 0.001,
"fromAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"toAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE"
},
"estimate": {
"fromAmount": "10000000",
"toAmount": "9975000",
"toAmountMin": "9975000",
"tool": "feeCollection",
"approvalAddress": "0x3Ef238c36035880EfbDfa239d218186b79Ad1d6F",
"gasCosts": [
{
"type": "SEND",
"price": "65729222",
"estimate": "130000",
"limit": "169000",
"amount": "8544798860000",
"amountUSD": "0.0250",
"token": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27",
"tags": ["major_asset"]
}
}
],
"feeCosts": [
{
"name": "LIFI Fixed Fee",
"description": "Fixed LIFI fee, independent of any other fee",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "25000",
"amountUSD": "0.0250",
"percentage": "0.0025",
"included": true,
"feeSplit": {
"integratorFee": "0",
"lifiFee": "25000"
}
}
],
"executionDuration": 0
},
"tool": "feeCollection",
"toolDetails": {
"key": "feeCollection",
"name": "Integrator Fee",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/protocols/feeCollection.svg"
}
},
{
"id": "529839e7-a048-4abe-99ad-d650a0fe469e",
"type": "cross",
"action": {
"fromChainId": 1,
"fromAmount": "9975000",
"fromToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"toChainId": 42161,
"toToken": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"slippage": 0.001,
"fromAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"destinationGasConsumption": "0"
},
"estimate": {
"fromAmount": "9975000",
"toAmount": "9965731",
"toAmountMin": "9965731",
"approvalAddress": "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5",
"executionDuration": 4,
"feeCosts": [
{
"name": "Relayer fee",
"description": "The fee taken by the relayer",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "997",
"amountUSD": "0.0010",
"percentage": "0.000100",
"included": true
},
{
"name": "Relayer gas fee",
"description": "The gas fee taken by the relayer",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"amount": "8272",
"amountUSD": "0.0083",
"percentage": "0.000829",
"included": true
}
],
"gasCosts": [
{
"type": "SEND",
"price": "65729222",
"estimate": "86000",
"limit": "111800",
"amount": "5652713092000",
"amountUSD": "0.0165",
"token": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27",
"tags": ["major_asset"]
}
}
],
"tool": "across"
},
"tool": "across",
"toolDetails": {
"key": "across",
"name": "AcrossV4",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/across.svg"
}
}
],
"integrator": "lifi-api",
"transactionRequest": {
"value": "0x0",
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x1794958f0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000004001695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000552008c0f6870c2f77e5cc1d2eb9bdff03e30ea000000000000000000000000000000000000000000000000000000000009834d8000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066163726f7373000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c6966692d617069000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003ef238c36035880efbdfa239d218186b79ad1d6f0000000000000000000000003ef238c36035880efbdfa239d218186b79ad1d6f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000084eedd56e1000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061a8000000000000000000000000b9c0de368bece5e76b52545a8e377a4c118f597b00000000000000000000000000000000000000000000000000000000000000000000000000000000552008c0f6870c2f77e5cc1d2eb9bdff03e30ea0000000000000000000000000552008c0f6870c2f77e5cc1d2eb9bdff03e30ea0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000009810a30000000000000000000000000000000000000000000000000ddd69940d1f65b200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000069734fab00000000000000000000000000000000000000000000000000000000697373ed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000000",
"from": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"chainId": 1,
"gasPrice": "0x3eaf2c6",
"gasLimit": "0x45e8a"
},
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8"
}
```
### What to Extract
| Field | Purpose |
| ---------------------------- | ----------------------------------------------- |
| `tool` | Bridge/DEX used (needed for status check) |
| `estimate.toAmount` | Expected output amount |
| `estimate.toAmountMin` | Minimum guaranteed (slippage protected) |
| `estimate.approvalAddress` | Spender address for token approval (if ERC20) |
| `estimate.executionDuration` | Estimated time in seconds |
| `estimate.feeCosts` | Array of protocol fees |
| `estimate.gasCosts` | Array of gas costs |
| `includedSteps` | Detailed breakdown of each step in the transfer |
| `transactionRequest` | Ready-to-sign transaction object |
| `transactionId` | Unique identifier for tracking this transfer |
***
## Call 4: Execute the Transaction
Before executing, ensure the user has approved the token transfer if using an ERC20 token. Check if `fromToken` is native (ETH) or ERC20, and verify allowance.
### Approval Check (if ERC20)
If `fromToken.address` is NOT `0x0000000000000000000000000000000000000000`:
1. Call `allowance(fromAddress, approvalAddress)` on the token contract
2. If allowance \< fromAmount, send an `approve(approvalAddress, amount)` transaction first
### Execute Transaction
Sign and send the `transactionRequest` object from the quote response:
```javascript theme={"system"}
// The transactionRequest is ready to use
const tx = {
to: quote.transactionRequest.to,
data: quote.transactionRequest.data,
value: quote.transactionRequest.value,
gasLimit: quote.transactionRequest.gasLimit,
chainId: quote.transactionRequest.chainId
};
// Sign and send with user's wallet
const txResponse = await wallet.sendTransaction(tx);
const txHash = txResponse.hash;
```
### What to Save
* `txHash` - Transaction hash for status polling (required)
* `fromChain` - Source chain ID (optional, speeds up status check)
* `toChain` - Destination chain ID (optional)
* `tool` - From the quote response (optional)
***
## Call 5: Poll Transfer Status
Check the status of the cross-chain transfer until it completes.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/status?txHash=0xabc123...&bridge=stargateV2&fromChain=1&toChain=42161"
```
### Parameters
| Parameter | Required | Description |
| ----------- | -------- | ------------------------------------------------- |
| `txHash` | Yes | Transaction hash from step 4 |
| `fromChain` | No | Source chain ID (recommended for faster response) |
| `toChain` | No | Destination chain ID |
| `bridge` | No | The `tool` value from the quote |
Only `txHash` is required. The other parameters are optional but providing `fromChain` speeds up the request.
### Response
```json theme={"system"}
{
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"sending": {
"txHash": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"txLink": "https://etherscan.io/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"chainId": 1,
"amount": "10000000",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9961",
"gasPrice": "65729222",
"gasUsed": "220266",
"gasToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"priceUSD": "2923.27"
},
"gasAmount": "14477912813052",
"gasAmountUSD": "0.0423",
"timestamp": 1737625200
},
"receiving": {
"txHash": "0xdef456789abc...",
"txLink": "https://arbiscan.io/tx/0xdef456789abc...",
"chainId": 42161,
"amount": "9965731",
"token": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9618",
"timestamp": 1737625204
},
"lifiExplorerLink": "https://explorer.li.fi/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"tool": "across",
"status": "DONE",
"substatus": "COMPLETED"
}
```
### Status Values
| Status | Action |
| ----------- | ----------------------------------------- |
| `NOT_FOUND` | Transaction not indexed yet, keep polling |
| `PENDING` | Transfer in progress, keep polling |
| `DONE` | Transfer complete, check `substatus` |
| `FAILED` | Transfer failed, check error |
### Substatus Values (when DONE)
| Substatus | Meaning |
| ----------- | ---------------------------------------------------- |
| `COMPLETED` | Success - user received exact requested tokens |
| `PARTIAL` | Success - user received different token (full value) |
| `REFUNDED` | Failed - tokens returned to sender |
### Polling Strategy
```javascript theme={"system"}
const pollStatus = async (txHash, bridge, fromChain, toChain) => {
const maxAttempts = 60;
let attempt = 0;
while (attempt < maxAttempts) {
const response = await fetch(
`https://li.quest/v1/status?txHash=${txHash}&bridge=${bridge}&fromChain=${fromChain}&toChain=${toChain}`
);
const status = await response.json();
if (status.status === 'DONE' || status.status === 'FAILED') {
return status;
}
// Wait 10 seconds before next poll
await new Promise(resolve => setTimeout(resolve, 10000));
attempt++;
}
throw new Error('Status polling timeout');
};
```
***
## Complete Flow Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ AGENT WORKFLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. GET /chains ──────────────────────► List of chain IDs │
│ │
│ 2. GET /tokens?chains=X,Y ───────────► Token addresses │
│ │
│ 3. GET /quote?fromChain=X&... ───────► transactionRequest │
│ │ │
│ ▼ │
│ 4. [Check if ERC20] │
│ │ │
│ ├── Native token ──► Skip approval │
│ │ │
│ └── ERC20 ──► Check allowance │
│ │ │
│ └── If insufficient ──► Send approve() tx │
│ │
│ 5. Sign & send transactionRequest ───► txHash │
│ │
│ 6. GET /status?txHash=X&... ─────────► Poll until DONE │
│ │ │
│ ├── PENDING ──► Keep polling │
│ │ │
│ ├── DONE + COMPLETED ──► Success! │
│ │ │
│ ├── DONE + PARTIAL ──► Success (different token) │
│ │ │
│ └── FAILED ──► Handle error │
│ │
└─────────────────────────────────────────────────────────────────┘
```
***
## Quick Reference
```bash theme={"system"}
# 1. Get chains
curl "https://li.quest/v1/chains"
# 2. Get tokens for Ethereum and Arbitrum
curl "https://li.quest/v1/tokens?chains=1,42161"
# 3. Get quote for 10 USDC from Ethereum to Arbitrum (replace address with your own)
curl "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=10000000&fromAddress=0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
# 4. Execute transactionRequest with user's wallet
# 5. Check status
curl "https://li.quest/v1/status?txHash=TX_HASH&bridge=TOOL&fromChain=1&toChain=42161"
```
# Endpoint Specifications
Source: https://docs.li.fi/agents/reference/endpoint-specs
Action-oriented reference for core LI.FI API endpoints
This page provides concise, action-oriented documentation for the core LI.FI API endpoints that AI agents need.
## Base URL
```
https://li.quest/v1
```
All endpoints use this base URL. Authentication via `x-lifi-api-key` header is optional but recommended for higher rate limits.
***
## GET /chains
**Action**: Retrieve all supported blockchain networks.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/chains"
```
### Parameters
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------------- |
| `chainTypes` | string | No | Filter by type: `EVM`, `SVM`, `UTXO`, `MVM` (comma-separated) |
### Response
```json theme={"system"}
{
"chains": [
{
"id": 1,
"key": "eth",
"chainType": "EVM",
"name": "Ethereum",
"coin": "ETH",
"mainnet": true,
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/ethereum.svg",
"tokenlistUrl": "https://gateway.ipfs.io/ipns/tokens.uniswap.org",
"multicallAddress": "0xcA11bde05977b3631167028862bE2a173976CA11",
"metamask": {
"chainId": "0x1",
"blockExplorerUrls": ["https://etherscan.io/"],
"chainName": "Ethereum Mainnet",
"nativeCurrency": { "name": "ETH", "symbol": "ETH", "decimals": 18 },
"rpcUrls": ["https://mainnet.infura.io/v3/"]
},
"nativeToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
}
}
// ... 58 chains total
]
}
```
### Key Fields
| Field | Use |
| ------------- | -------------------------------------------- |
| `id` | Chain ID for all other API calls |
| `key` | Short identifier (e.g., "eth", "arb", "pol") |
| `chainType` | EVM, SVM (Solana), UTXO (Bitcoin), MVM (Sui) |
| `mainnet` | true for production chains |
| `nativeToken` | Gas token with price |
| `metamask` | Chain config for wallet connection |
***
## GET /tokens
**Action**: Retrieve tokens available on specified chains.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/tokens?chains=1,42161"
```
### Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------- |
| `chains` | string | Yes | Comma-separated chain IDs |
### Response
```json theme={"system"}
{
"tokens": {
"1": [
{
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
"priceUSD": "2923.27"
},
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
}
// ... hundreds more tokens per chain
],
"42161": [
// ... tokens for Arbitrum
]
}
}
```
### Key Fields
| Field | Use |
| ---------- | ---------------------------------------------- |
| `address` | Token contract address (use in quote) |
| `decimals` | For converting human amounts to smallest units |
| `priceUSD` | Current USD price |
| `coinKey` | Canonical identifier across chains |
| `tags` | Categories like "stablecoin" |
### Native Token Address
Native tokens (ETH, MATIC, etc.) use: `0x0000000000000000000000000000000000000000`
***
## GET /quote
**Action**: Get a transfer quote with ready-to-execute transaction data.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=10000000&fromAddress=0x..."
```
### Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------ |
| `fromChain` | number | **Yes** | Source chain ID |
| `toChain` | number | **Yes** | Destination chain ID |
| `fromToken` | string | **Yes** | Source token address or symbol |
| `toToken` | string | **Yes** | Destination token address or symbol |
| `fromAmount` | string | **Yes** | Amount in smallest unit |
| `fromAddress` | string | **Yes** | Sender wallet address |
| `toAddress` | string | No | Recipient (defaults to fromAddress) |
| `slippage` | number | No | Max slippage (default: 0.005 = 0.5%) |
| `order` | string | No | `FASTEST`, `CHEAPEST` |
| `allowBridges` | string | No | Allowed bridges (comma-separated) |
| `preferBridges` | string | No | Preferred bridges (comma-separated) |
| `denyBridges` | string | No | Excluded bridges (comma-separated) |
| `allowExchanges` | string | No | Allowed DEXs (comma-separated) |
| `preferExchanges` | string | No | Preferred DEXs (comma-separated) |
| `denyExchanges` | string | No | Excluded DEXs (comma-separated) |
| `maxPriceImpact` | number | No | Max price impact (e.g., 0.05 = 5%) |
### Response
```json theme={"system"}
{
"type": "lifi",
"id": "9f32864e-d704-46c1-874e-20f492c0ce77:0",
"tool": "across",
"toolDetails": {
"key": "across",
"name": "AcrossV4",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/across.svg"
},
"action": {
"fromToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://...",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"fromAmount": "10000000",
"toToken": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://...",
"priceUSD": "0.999606",
"tags": ["stablecoin"]
},
"fromChainId": 1,
"toChainId": 42161,
"slippage": 0.001,
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0"
},
"estimate": {
"tool": "across",
"approvalAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"toAmountMin": "9965731",
"toAmount": "9965731",
"fromAmount": "10000000",
"feeCosts": [
{
"name": "LIFI Fixed Fee",
"description": "Fixed LIFI fee, independent of any other fee",
"token": { /* full token object */ },
"amount": "25000",
"amountUSD": "0.0250",
"percentage": "0.0025",
"included": true,
"feeSplit": { "integratorFee": "0", "lifiFee": "25000" }
}
// ... additional fee items
],
"gasCosts": [
{
"type": "SEND",
"price": "65729222",
"estimate": "220266",
"limit": "286346",
"amount": "14477912813052",
"amountUSD": "0.0423",
"token": { /* native token object */ }
}
],
"executionDuration": 4,
"fromAmountUSD": "9.9961",
"toAmountUSD": "9.9618"
},
"includedSteps": [
{
"id": "e1c87be5-80be-4e2d-8c96-a6492566a739",
"type": "protocol",
"action": { /* step action */ },
"estimate": { /* step estimate */ },
"tool": "feeCollection",
"toolDetails": { /* tool info */ }
},
{
"id": "529839e7-a048-4abe-99ad-d650a0fe469e",
"type": "cross",
"action": { /* step action */ },
"estimate": { /* step estimate */ },
"tool": "across",
"toolDetails": { /* tool info */ }
}
],
"integrator": "lifi-api",
"transactionRequest": {
"value": "0x0",
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x1794958f...",
"from": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"chainId": 1,
"gasPrice": "0x3eaf2c6",
"gasLimit": "0x45e8a"
},
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8"
}
```
See the [Five-Call Recipe](/agents/quick-start/five-call-recipe#call-3-get-a-quote) for the complete untruncated response.
### Key Fields
| Field | Use |
| -------------------------- | --------------------------------------------------------------- |
| `tool` | Bridge/DEX used (optional for status check, speeds up response) |
| `estimate.toAmount` | Expected output amount |
| `estimate.toAmountMin` | Minimum guaranteed (slippage-protected) |
| `estimate.approvalAddress` | Spender address for ERC20 approval |
| `estimate.feeCosts` | Detailed fee breakdown |
| `estimate.gasCosts` | Gas cost estimates |
| `includedSteps` | Each step in multi-step transfers |
| `transactionRequest` | Ready-to-sign transaction object |
| `transactionId` | Unique ID for tracking |
### Constraints
* `fromAmount` must be in smallest unit (e.g., 6 decimals for USDC)
* `slippage` is a decimal (0.005 = 0.5%, not 0.5)
* Quote expires after \~60 seconds
***
## GET /status
**Action**: Check the status of a cross-chain transfer.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/status?txHash=0x..."
```
### Parameters
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------- |
| `txHash` | string | **Yes** | Source chain transaction hash |
| `fromChain` | number | No | Source chain ID (recommended for speed) |
| `toChain` | number | No | Destination chain ID |
| `bridge` | string | No | Bridge tool from quote |
Only `txHash` is required. Providing `fromChain` significantly speeds up the response.
### Response
```json theme={"system"}
{
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"sending": {
"txHash": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"txLink": "https://etherscan.io/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"chainId": 1,
"amount": "10000000",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://...",
"priceUSD": "0.999606"
},
"amountUSD": "9.9961",
"gasPrice": "65729222",
"gasUsed": "220266",
"gasToken": { /* native token */ },
"gasAmount": "14477912813052",
"gasAmountUSD": "0.0423",
"timestamp": 1737625200
},
"receiving": {
"txHash": "0xdef456789abc...",
"txLink": "https://arbiscan.io/tx/0xdef456789abc...",
"chainId": 42161,
"amount": "9965731",
"token": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://...",
"priceUSD": "0.999606"
},
"amountUSD": "9.9618",
"timestamp": 1737625204
},
"lifiExplorerLink": "https://explorer.li.fi/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"tool": "across",
"status": "DONE",
"substatus": "COMPLETED"
}
```
### Key Fields
| Field | Use |
| ------------------ | ----------------------------- |
| `status` | Main status (see table below) |
| `substatus` | Detailed outcome |
| `sending.txHash` | Source chain transaction |
| `receiving.txHash` | Destination chain transaction |
| `lifiExplorerLink` | Link to LI.FI explorer |
| `receiving.amount` | Actual received amount |
### Status Values
| Status | Substatus | Meaning |
| ----------- | ----------- | ---------------------------------- |
| `NOT_FOUND` | - | Transaction not indexed yet |
| `PENDING` | - | Transfer in progress |
| `DONE` | `COMPLETED` | Success - received requested token |
| `DONE` | `PARTIAL` | Success - received different token |
| `DONE` | `REFUNDED` | Failed - tokens returned to sender |
| `FAILED` | various | Permanent failure (see substatus) |
***
## POST /advanced/routes
**Action**: Get multiple route options for a transfer.
### Request
```bash theme={"system"}
curl -X POST "https://li.quest/v1/advanced/routes" \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 1,
"toChainId": 42161,
"fromTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"toTokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"fromAmount": "10000000",
"fromAddress": "0x..."
}'
```
### Parameters (JSON Body)
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------- |
| `fromChainId` | number | **Yes** | Source chain ID |
| `toChainId` | number | **Yes** | Destination chain ID |
| `fromTokenAddress` | string | **Yes** | Source token address |
| `toTokenAddress` | string | **Yes** | Destination token address |
| `fromAmount` | string | **Yes** | Amount in smallest unit |
| `fromAddress` | string | **Yes** | Sender address |
| `options` | object | No | Additional options |
### Response
```json theme={"system"}
{
"routes": [
{
"id": "0x...",
"steps": [...],
"toAmount": "9900000",
"toAmountMin": "9850000",
"gasCostUSD": "5.23",
"tags": []
}
]
}
```
### When to Use
* Need multiple route options
* User wants to compare bridges
* Complex multi-hop transfers
***
## POST /advanced/stepTransaction
**Action**: Get transaction data for a specific route step.
### Request
```bash theme={"system"}
curl -X POST "https://li.quest/v1/advanced/stepTransaction" \
-H "Content-Type: application/json" \
-d '{ "step": { ... } }'
```
### Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------- |
| `step` | object | **Yes** | Step object from routes response |
### Response
Returns the step with `transactionRequest` populated.
### When to Use
After `/advanced/routes` to get transaction data for each step.
***
## GET /connections
**Action**: Get all possible token connections between two chains.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/connections?fromChain=1&toChain=42161"
```
### Parameters
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------- |
| `fromChain` | number | **Yes** | Source chain ID |
| `toChain` | number | **Yes** | Destination chain ID |
| `fromToken` | string | No | Filter by source token |
| `toToken` | string | No | Filter by destination token |
### Response
```json theme={"system"}
{
"connections": [
{
"fromChainId": 1,
"toChainId": 42161,
"fromTokens": [
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1
}
// ... more tokens that can be sent
],
"toTokens": [
{
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161
}
// ... more tokens that can be received
]
}
]
}
```
### Key Fields
| Field | Use |
| ------------- | --------------------------------------------------- |
| `fromChainId` | Source chain |
| `toChainId` | Destination chain |
| `fromTokens` | Tokens that can be sent (`address` + `chainId`) |
| `toTokens` | Tokens that can be received (`address` + `chainId`) |
### When to Use
* Check if a route is possible before quoting
* Discover available destination tokens
* Build token selection UI
***
## GET /tools
**Action**: Get available bridges and exchanges.
### Request
```bash theme={"system"}
curl "https://li.quest/v1/tools"
```
### Response
```json theme={"system"}
{
"bridges": [
{
"key": "across",
"name": "AcrossV4",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/across.svg",
"supportedChains": [
{ "fromChainId": 1, "toChainId": 42161 },
{ "fromChainId": 1, "toChainId": 10 },
{ "fromChainId": 1, "toChainId": 137 }
// ... more chain pairs
]
},
{
"key": "stargateV2",
"name": "Stargate V2",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/bridges/stargate.png",
"supportedChains": [
{ "fromChainId": 1, "toChainId": 42161 },
{ "fromChainId": 1, "toChainId": 43114 }
// ... more chain pairs
]
}
// ... 27 bridges total
],
"exchanges": [
{
"key": "uniswap",
"name": "Uniswap V3",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/uniswap.png",
"supportedChains": [1, 42161, 10, 137, 8453]
},
{
"key": "1inch",
"name": "1inch",
"logoURI": "https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/exchanges/oneinch.png",
"supportedChains": [1, 42161, 10, 137, 8453, 43114]
}
// ... 31 exchanges total
]
}
```
### Key Fields
| Field | Use |
| ----------------------------- | ----------------------------------------- |
| `bridges[].key` | Use in `allowBridges` / `denyBridges` |
| `bridges[].supportedChains` | Check if bridge supports your chain pair |
| `exchanges[].key` | Use in `allowExchanges` / `denyExchanges` |
| `exchanges[].supportedChains` | Chains where DEX is available |
### When to Use
* Filter routes by specific bridges/exchanges
* Show available tools to users
* Validate bridge/exchange selection
***
## Quick Reference
| Endpoint | Method | Purpose |
| --------------------------- | ------ | -------------------------- |
| `/chains` | GET | List supported chains |
| `/tokens` | GET | Get tokens for chains |
| `/quote` | GET | Get quote with tx data |
| `/status` | GET | Check transfer status |
| `/advanced/routes` | POST | Get multiple route options |
| `/advanced/stepTransaction` | POST | Get tx data for route step |
| `/connections` | GET | Check route availability |
| `/tools` | GET | List bridges and exchanges |
***
## Related Pages
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Complete workflow
* [Schemas](/agents/reference/schemas) - Detailed type definitions
* [Error Playbooks](/agents/reference/error-playbooks) - Error handling
# Error Playbooks
Source: https://docs.li.fi/agents/reference/error-playbooks
How to handle every error code and failure scenario
This page provides actionable playbooks for handling errors from the LI.FI API. For each error, you'll find the cause, whether to retry, what to change, and how to communicate with users.
## HTTP Status Codes
### 400 Bad Request
**Cause**: Invalid parameters in the request.
| Retry? | No |
| ---------------- | --------------------------------------------------------- |
| **Fix** | Check and correct request parameters |
| **User Message** | "Invalid request. Please check your input and try again." |
**Common Causes**:
* Missing required parameters
* Invalid token address format
* Invalid chain ID
* Amount is 0 or negative
* fromAddress not a valid address
```javascript theme={"system"}
// Example handling
if (error.status === 400) {
console.error('Bad request:', error.message);
// Parse error message for specific field
// Don't retry - fix parameters
}
```
***
### 404 Not Found
**Cause**: No route available for the requested transfer.
| Retry? | Maybe - with different parameters |
| ---------------- | -------------------------------------------------------------------- |
| **Fix** | Try different token pair, smaller amount, or different chains |
| **User Message** | "No route found for this transfer. Try a different token or amount." |
**Common Causes**:
* Token pair not supported
* Amount too small (below minimum)
* Amount too large (exceeds liquidity)
* Bridge temporarily unavailable
```javascript theme={"system"}
if (error.status === 404 || error.message.includes('No route')) {
// Suggest alternatives
const suggestions = [
'Try a smaller amount',
'Try a different destination token',
'Use USDC or ETH as intermediate'
];
}
```
***
### 429 Too Many Requests
**Cause**: Rate limit exceeded.
| Retry? | Yes - with backoff |
| ---------------- | ------------------------------------------ |
| **Fix** | Wait and retry with exponential backoff |
| **User Message** | "Too many requests. Please wait a moment." |
**Backoff Strategy**:
```javascript theme={"system"}
async function handleRateLimit(attempt) {
const waitTime = Math.min(Math.pow(2, attempt) * 1000, 30000);
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await sleep(waitTime);
}
// Usage in retry loop
for (let attempt = 0; attempt < 5; attempt++) {
try {
return await fetchQuote(params);
} catch (error) {
if (error.status === 429) {
await handleRateLimit(attempt);
continue;
}
throw error;
}
}
```
**Prevention**:
* Use an API key for higher limits
* Cache chain and token data
* Batch requests where possible
***
### 500 Internal Server Error
**Cause**: Server-side issue.
| Retry? | Yes - once or twice |
| ---------------- | ---------------------------------------------- |
| **Fix** | Wait briefly and retry |
| **User Message** | "Service temporarily unavailable. Retrying..." |
```javascript theme={"system"}
if (error.status >= 500) {
await sleep(2000);
// Retry once
return await fetchQuote(params);
}
```
***
### 503 Service Unavailable
**Cause**: Service temporarily down or overloaded.
| Retry? | Yes - with longer delay |
| ---------------- | --------------------------------------------------------------- |
| **Fix** | Wait 5-10 seconds and retry |
| **User Message** | "Service is temporarily unavailable. Please try again shortly." |
***
## API Error Codes
The API returns error codes in the response body. Handle these specific scenarios:
### `NO_POSSIBLE_ROUTE`
**Cause**: No bridge or DEX can fulfill the request.
| Retry? | No - change parameters |
| ---------------- | ------------------------------------------------------------------- |
| **Fix** | Try different tokens, chains, or amount |
| **User Message** | "This transfer route is not available. Try a different token pair." |
**Suggestions**:
```javascript theme={"system"}
if (error.code === 'NO_POSSIBLE_ROUTE') {
return {
error: 'No route available',
suggestions: [
'Try USDC or USDT as the destination token',
'Try a smaller amount',
'Check if both chains support the tokens'
]
};
}
```
***
### `AMOUNT_TOO_LOW`
**Cause**: Transfer amount is below minimum threshold.
| Retry? | No - increase amount |
| ---------------- | ---------------------------------------------------- |
| **Fix** | Increase `fromAmount` |
| **User Message** | "Amount too low for this transfer. Minimum is \[X]." |
```javascript theme={"system"}
if (error.code === 'AMOUNT_TOO_LOW') {
// Error often includes minimum amount
const minAmount = error.minAmount || 'unknown';
return {
error: `Minimum amount required: ${minAmount}`,
action: 'Increase transfer amount'
};
}
```
***
### `AMOUNT_TOO_HIGH`
**Cause**: Amount exceeds available liquidity.
| Retry? | No - decrease amount |
| ---------------- | ----------------------------------------------------------- |
| **Fix** | Decrease `fromAmount` or split into multiple transfers |
| **User Message** | "Amount exceeds available liquidity. Try a smaller amount." |
***
### `INSUFFICIENT_LIQUIDITY`
**Cause**: Not enough liquidity in pools.
| Retry? | Maybe - try later |
| ---------------- | ----------------------------------------------------------------- |
| **Fix** | Try smaller amount, different bridge, or wait |
| **User Message** | "Insufficient liquidity for this transfer. Try a smaller amount." |
***
### Slippage Error (code 1007)
**Cause**: Price impact exceeds slippage tolerance.
| Retry? | Yes - with adjusted slippage |
| ---------------- | ----------------------------------------------------------------- |
| **Fix** | Increase `slippage` parameter or decrease amount |
| **User Message** | "Price moved too much. Would you like to accept higher slippage?" |
```javascript theme={"system"}
if (error.code === 1007 || error.message?.includes('slippage')) {
// Offer to increase slippage
const currentSlippage = params.slippage || 0.005;
const suggestedSlippage = Math.min(currentSlippage * 2, 0.03);
return {
error: 'Slippage exceeded',
currentSlippage: `${currentSlippage * 100}%`,
suggestedSlippage: `${suggestedSlippage * 100}%`,
action: 'Retry with higher slippage or smaller amount'
};
}
```
In status responses, slippage issues appear as substatus `SLIPPAGE_EXCEEDED`.
***
### `TOOL_TIMEOUT`
**Cause**: Bridge or DEX response timeout.
| Retry? | Yes - immediately |
| ---------------- | -------------------------------- |
| **Fix** | Retry the request |
| **User Message** | "Request timed out. Retrying..." |
***
### `CHAIN_NOT_SUPPORTED`
**Cause**: Requested chain is not available.
| Retry? | No |
| ---------------- | --------------------------------------------- |
| **Fix** | Use a supported chain |
| **User Message** | "This blockchain is not currently supported." |
```javascript theme={"system"}
if (error.code === 'CHAIN_NOT_SUPPORTED') {
// Fetch supported chains
const chains = await getChains();
return {
error: 'Chain not supported',
supportedChains: chains.map(c => ({ id: c.id, name: c.name }))
};
}
```
***
### `TOKEN_NOT_SUPPORTED`
**Cause**: Token is not available for transfers.
| Retry? | No |
| ---------------- | -------------------------------------------- |
| **Fix** | Use a different token |
| **User Message** | "This token is not supported for transfers." |
***
## Transaction Errors
Errors that occur during transaction execution (not API errors):
### `execution reverted`
**Cause**: Smart contract rejected the transaction.
| Retry? | Maybe - get new quote |
| ---------------- | ---------------------------------------------- |
| **Fix** | Quote may be stale; get fresh quote and retry |
| **User Message** | "Transaction failed. Getting a fresh quote..." |
```javascript theme={"system"}
if (error.message.includes('reverted')) {
// Quote expired or conditions changed
console.log('Transaction reverted. Fetching new quote...');
const newQuote = await getQuote(originalParams);
// Retry with new quote
}
```
***
### `insufficient funds`
**Cause**: Wallet doesn't have enough balance.
| Retry? | No |
| ---------------- | -------------------------------------------------------- |
| **Fix** | User needs to add funds |
| **User Message** | "Insufficient balance. You need \[X] \[TOKEN] plus gas." |
```javascript theme={"system"}
if (error.message.includes('insufficient funds')) {
return {
error: 'Insufficient balance',
required: {
token: quote.action.fromAmount,
gas: 'Plus gas fees in native token'
}
};
}
```
***
### `user rejected`
**Cause**: User declined the transaction in their wallet.
| Retry? | No - ask user again |
| ---------------- | ----------------------------------------------------- |
| **Fix** | Ask if user wants to try again |
| **User Message** | "Transaction cancelled. Would you like to try again?" |
***
### `nonce too low`
**Cause**: Pending transaction with same nonce.
| Retry? | Wait for pending tx |
| ---------------- | ------------------------------------------------------------- |
| **Fix** | Wait for pending transaction or speed it up |
| **User Message** | "You have a pending transaction. Please wait or speed it up." |
***
## Error Handling Template
```javascript theme={"system"}
async function handleLiFiError(error, params) {
const code = error.code || error.status;
// Rate limiting
if (code === 429) {
return { retry: true, wait: true, message: 'Rate limited' };
}
// Server errors
if (code >= 500) {
return { retry: true, message: 'Server error' };
}
// No route
if (code === 404 || error.code === 'NO_POSSIBLE_ROUTE') {
return {
retry: false,
message: 'No route found',
suggestions: getRouteSuggestions(params)
};
}
// Amount issues
if (error.code === 'AMOUNT_TOO_LOW') {
return {
retry: false,
message: `Amount too low. Minimum: ${error.minAmount}`,
action: 'increase_amount'
};
}
// Slippage
if (error.code === 1007 || error.message?.includes('slippage')) {
return {
retry: true,
adjustParam: { slippage: params.slippage * 2 },
message: 'Consider increasing slippage'
};
}
// Default
return {
retry: false,
message: error.message || 'Unknown error'
};
}
function getRouteSuggestions(params) {
return [
'Try USDC, USDT, or ETH instead',
'Try a smaller amount',
'Check if tokens are supported on both chains'
];
}
```
***
## Quick Reference
| Error | Retry? | Action |
| --------------------- | ------ | -------------------- |
| 400 Bad Request | No | Fix parameters |
| 404 No Route | Maybe | Change tokens/amount |
| 429 Rate Limited | Yes | Exponential backoff |
| 500 Server Error | Yes | Wait and retry |
| AMOUNT\_TOO\_LOW | No | Increase amount |
| Slippage error (1007) | Maybe | Increase slippage |
| execution reverted | Maybe | Get new quote |
| insufficient funds | No | User needs funds |
| user rejected | No | Ask user again |
***
## Related Pages
* [Decision Tables](/agents/quick-start/decision-tables) - Error recovery decisions
* [Status & Recovery](/agents/workflows/status-recovery) - Handle transfer failures
* [Rate Limits](/api-reference/rate-limits) - Rate limit details
# Data Schemas
Source: https://docs.li.fi/agents/reference/schemas
Type definitions, formats, and enums for LI.FI API
This page documents the data types, formats, and validation rules for LI.FI API parameters and responses. Use this as a reference for building robust integrations.
## Core Types
### Chain
Represents a blockchain network.
```typescript theme={"system"}
interface Chain {
id: number; // Chain ID (e.g., 1 for Ethereum)
key: string; // Short identifier (e.g., "eth")
name: string; // Display name (e.g., "Ethereum")
chainType: ChainType; // "EVM" | "SVM" | "UTXO" | "MVM"
coin: string; // Native token symbol (e.g., "ETH")
mainnet: boolean; // true if mainnet
logoURI: string; // Chain logo URL
metamask?: MetaMaskConfig;
}
type ChainType = "EVM" | "SVM" | "UTXO" | "MVM";
```
| Field | Type | Required | Description |
| ----------- | ------- | -------- | ----------------------- |
| `id` | integer | Yes | Unique chain identifier |
| `key` | string | Yes | Short key (lowercase) |
| `name` | string | Yes | Human-readable name |
| `chainType` | enum | Yes | Blockchain type |
| `coin` | string | Yes | Native token symbol |
| `mainnet` | boolean | Yes | Is production network |
### Common Chain IDs
| Chain | ID | Type |
| --------- | ------------------ | ---- |
| Ethereum | `1` | EVM |
| Arbitrum | `42161` | EVM |
| Optimism | `10` | EVM |
| Polygon | `137` | EVM |
| Base | `8453` | EVM |
| Avalanche | `43114` | EVM |
| BSC | `56` | EVM |
| Solana | `1151111081099710` | SVM |
***
### Token
Represents a cryptocurrency token.
```typescript theme={"system"}
interface Token {
address: string; // Contract address
symbol: string; // Token symbol (e.g., "USDC")
name: string; // Full name (e.g., "USD Coin")
decimals: number; // Decimal places (e.g., 6)
chainId: number; // Chain this token is on
priceUSD?: string; // Current USD price
logoURI?: string; // Token logo URL
}
```
| Field | Type | Required | Format | Description |
| ---------- | ------- | -------- | --------------------- | ---------------- |
| `address` | string | Yes | Hex (0x...) or native | Contract address |
| `symbol` | string | Yes | 2-10 chars | Trading symbol |
| `decimals` | integer | Yes | 0-18 | Decimal places |
| `chainId` | integer | Yes | | Parent chain ID |
| `priceUSD` | string | No | Decimal string | USD price |
### Native Token Address
```
0x0000000000000000000000000000000000000000
```
This address represents the native token (ETH, MATIC, etc.) on any EVM chain.
### Common Token Decimals
| Token | Decimals | 1 Token in Wei |
| ----- | -------- | --------------------- |
| USDC | 6 | `1000000` |
| USDT | 6 | `1000000` |
| DAI | 18 | `1000000000000000000` |
| WETH | 18 | `1000000000000000000` |
| WBTC | 8 | `100000000` |
***
### Quote
Response from `/quote` endpoint.
```typescript theme={"system"}
interface Quote {
id: string; // Unique quote identifier
type: "lifi"; // Always "lifi"
tool: string; // Bridge/DEX used
toolDetails: ToolDetails; // Tool metadata
action: Action; // Transfer details
estimate: Estimate; // Cost and output estimates
transactionRequest: TxRequest; // Ready-to-sign transaction
}
```
***
### Action
Describes what the quote will do.
```typescript theme={"system"}
interface Action {
fromChainId: number; // Source chain
toChainId: number; // Destination chain
fromToken: Token; // Source token
toToken: Token; // Destination token
fromAmount: string; // Input amount (smallest unit)
slippage: number; // Slippage tolerance (decimal)
fromAddress: string; // Sender address
toAddress: string; // Recipient address
}
```
| Field | Type | Format | Constraints |
| ------------- | ------- | ---------------- | ----------------------- |
| `fromChainId` | integer | | Must be supported chain |
| `toChainId` | integer | | Must be supported chain |
| `fromAmount` | string | Integer string | > 0, in smallest unit |
| `slippage` | number | Decimal | 0 \< slippage \< 1 |
| `fromAddress` | string | 0x... (42 chars) | Valid address |
***
### Estimate
Cost and output estimates.
```typescript theme={"system"}
interface Estimate {
fromAmount: string; // Input amount
toAmount: string; // Expected output amount
toAmountMin: string; // Minimum guaranteed (slippage)
approvalAddress: string; // Spender for token approval
executionDuration: number; // Estimated seconds
feeCosts: FeeCost[]; // Protocol fees
gasCosts: GasCost[]; // Gas costs
}
```
| Field | Type | Description |
| ------------------- | ------- | ----------------------------------- |
| `toAmount` | string | Expected output (optimistic) |
| `toAmountMin` | string | Guaranteed minimum (after slippage) |
| `approvalAddress` | string | Use for ERC20 `approve()` |
| `executionDuration` | integer | Estimated time in seconds |
***
### TransactionRequest
Ready-to-sign transaction data.
```typescript theme={"system"}
interface TransactionRequest {
to: string; // Contract address
data: string; // Encoded call data
value: string; // Native token amount (hex)
gasLimit: string; // Gas limit (hex)
gasPrice?: string; // Suggested gas price (hex)
chainId: number; // Chain ID for signing
}
```
| Field | Type | Format | Description |
| ---------- | ------- | ---------------- | ----------------------- |
| `to` | string | 0x... (42 chars) | Destination contract |
| `data` | string | 0x... (hex) | Encoded function call |
| `value` | string | 0x... (hex) | Wei amount (hex string) |
| `gasLimit` | string | 0x... (hex) | Gas units (hex) |
| `chainId` | integer | | EIP-155 chain ID |
### Converting Hex Values
```javascript theme={"system"}
// Hex to decimal
const gasLimit = parseInt(tx.gasLimit, 16);
const value = BigInt(tx.value);
// Decimal to hex
const hexValue = '0x' + amount.toString(16);
```
***
### Status
Transfer status response.
```typescript theme={"system"}
interface Status {
transactionId: string; // Unique transfer ID
sending: TransferInfo; // Source chain info
receiving?: TransferInfo; // Destination chain info
status: StatusType; // Current status
substatus?: SubstatusType; // Detailed status (when DONE)
tool: string; // Bridge used
error?: string; // Error message (if failed)
}
type StatusType = "NOT_FOUND" | "PENDING" | "DONE" | "FAILED";
type SubstatusType = "COMPLETED" | "PARTIAL" | "REFUNDED";
```
### Status State Machine
```
NOT_FOUND → PENDING → DONE (COMPLETED | PARTIAL | REFUNDED)
↘ FAILED
```
| Status | Substatus | Meaning |
| ----------- | ----------- | ----------------------------- |
| `NOT_FOUND` | - | Transaction not indexed yet |
| `PENDING` | - | Transfer in progress |
| `DONE` | `COMPLETED` | Success - got requested token |
| `DONE` | `PARTIAL` | Success - got different token |
| `DONE` | `REFUNDED` | Failed - tokens returned |
| `FAILED` | - | Permanent failure |
***
## Enums
### Order
Route ordering preference.
```typescript theme={"system"}
type Order = "FASTEST" | "CHEAPEST";
```
| Value | Description |
| ---------- | --------------------- |
| `FASTEST` | Lowest execution time |
| `CHEAPEST` | Lowest total fees |
### ChainType
Blockchain architecture type.
```typescript theme={"system"}
type ChainType = "EVM" | "SVM" | "UTXO" | "MVM";
```
| Value | Description | Examples |
| ------ | ------------------------ | --------------------------- |
| `EVM` | Ethereum Virtual Machine | Ethereum, Arbitrum, Polygon |
| `SVM` | Solana Virtual Machine | Solana |
| `UTXO` | Bitcoin-style | Bitcoin |
| `MVM` | Move Virtual Machine | Sui |
***
## Request Parameter Formats
### Address Format
```
Pattern: ^0x[a-fA-F0-9]{40}$
Example: 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0
```
### Amount Format
Amounts are always in the smallest unit (wei for ETH, 6 decimals for USDC):
```
Format: String containing non-negative integer
Example: "10000000" (10 USDC)
```
### Slippage Format
Decimal between 0 and 1:
```
Format: Number (decimal)
Example: 0.005 (0.5%)
Range: 0.001 to 0.5
```
### Transaction Hash Format
```
Pattern: ^0x[a-fA-F0-9]{64}$
Example: 0xabc123...def456 (66 characters total)
```
***
## Validation Rules
### Quote Request
```javascript theme={"system"}
const quoteValidation = {
fromChain: {
type: 'integer',
required: true,
min: 1
},
toChain: {
type: 'integer',
required: true,
min: 1
},
fromToken: {
type: 'string',
required: true,
pattern: /^(0x[a-fA-F0-9]{40}|[A-Z0-9]+)$/ // Address or symbol
},
toToken: {
type: 'string',
required: true,
pattern: /^(0x[a-fA-F0-9]{40}|[A-Z0-9]+)$/
},
fromAmount: {
type: 'string',
required: true,
pattern: /^[0-9]+$/, // Positive integer string
min: '1'
},
fromAddress: {
type: 'string',
required: true,
pattern: /^0x[a-fA-F0-9]{40}$/
},
slippage: {
type: 'number',
required: false,
default: 0.005,
min: 0.001,
max: 0.5
}
};
```
***
## OpenAPI Reference
Full OpenAPI specification is available at:
* **Spec URL**: `https://li.quest/v1/docs-json`
* **Swagger UI**: `https://apidocs.li.fi/`
Download and parse the spec for complete schema definitions:
```bash theme={"system"}
curl "https://li.quest/v1/docs-json" > lifi-openapi.json
```
***
## Related Pages
* [Endpoint Specifications](/agents/reference/endpoint-specs) - API endpoints
* [Concepts](/agents/concepts) - Core concept definitions
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Example usage
# Token Approvals
Source: https://docs.li.fi/agents/workflows/approvals
How to check and set token allowances before executing transfers
Before executing a transfer with an ERC20 token, you must approve the LI.FI contract to spend tokens on behalf of the user. This page explains when approval is needed and how to handle it correctly.
## When Is Approval Needed?
| Token Type | Approval Required | How to Identify |
| ------------------------------- | ----------------- | ------------------------------------------------------- |
| Native token (ETH, MATIC, etc.) | **No** | Address is `0x0000000000000000000000000000000000000000` |
| ERC20 token (USDC, USDT, etc.) | **Yes** | Any other address |
### Decision Logic
```
IF fromToken.address === "0x0000000000000000000000000000000000000000"
→ Skip approval (native token)
ELSE
→ Check allowance and approve if needed
```
***
## Step 1: Identify the Spender
The spender address comes from the quote response:
```json theme={"system"}
{
"estimate": {
"approvalAddress": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE"
}
}
```
Always use `estimate.approvalAddress` from the quote response. Never hardcode spender addresses - they may vary by route or bridge.
***
## Step 2: Check Current Allowance
Call the `allowance` function on the token contract:
### Function Signature
```solidity theme={"system"}
function allowance(address owner, address spender) external view returns (uint256)
```
### JavaScript Example
```javascript theme={"system"}
import { createPublicClient, http, erc20Abi } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({
chain: mainnet,
transport: http()
});
async function checkAllowance(tokenAddress, ownerAddress, spenderAddress) {
const allowance = await client.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'allowance',
args: [ownerAddress, spenderAddress]
});
return allowance;
}
// Usage
const currentAllowance = await checkAllowance(
quote.action.fromToken.address, // Token contract
quote.action.fromAddress, // User's wallet
quote.estimate.approvalAddress // LI.FI spender
);
```
### Python Example
```python theme={"system"}
from web3 import Web3
# ERC20 ABI (minimal)
ERC20_ABI = [
{
"constant": True,
"inputs": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"}
],
"name": "allowance",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]
def check_allowance(web3, token_address, owner, spender):
contract = web3.eth.contract(address=token_address, abi=ERC20_ABI)
return contract.functions.allowance(owner, spender).call()
```
***
## Step 3: Compare and Decide
```javascript theme={"system"}
const fromAmount = BigInt(quote.action.fromAmount);
const currentAllowance = await checkAllowance(...);
if (currentAllowance >= fromAmount) {
// Sufficient allowance - proceed to execution
console.log('Allowance sufficient, skipping approval');
} else {
// Need to approve
console.log('Approval needed');
await sendApproval(...);
}
```
***
## Step 4: Send Approval Transaction
### Function Signature
```solidity theme={"system"}
function approve(address spender, uint256 amount) external returns (bool)
```
### Approval Amount Strategies
| Strategy | Amount | Pros | Cons |
| --------- | ------------------- | -------------------------- | -------------------------------------- |
| Exact | `fromAmount` | Most secure | Requires approval for every transfer |
| Unlimited | `type(uint256).max` | One-time approval | Higher risk if contract is compromised |
| Buffer | `fromAmount * 2` | Balance of security and UX | May still need re-approval |
For AI agents, **exact approval** is recommended for security. For user-facing apps, consider letting users choose.
### JavaScript Example
```javascript theme={"system"}
import { createWalletClient, http, erc20Abi, maxUint256 } from 'viem';
async function approveToken(walletClient, tokenAddress, spenderAddress, amount) {
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'approve',
args: [spenderAddress, amount]
});
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') {
throw new Error('Approval transaction failed');
}
return receipt;
}
// Exact approval
await approveToken(
walletClient,
quote.action.fromToken.address,
quote.estimate.approvalAddress,
BigInt(quote.action.fromAmount)
);
// Or unlimited approval
await approveToken(
walletClient,
quote.action.fromToken.address,
quote.estimate.approvalAddress,
maxUint256
);
```
### Python Example
```python theme={"system"}
def approve_token(web3, token_address, spender, amount, private_key):
contract = web3.eth.contract(address=token_address, abi=ERC20_ABI)
# Build transaction
tx = contract.functions.approve(spender, amount).build_transaction({
'from': account.address,
'nonce': web3.eth.get_transaction_count(account.address),
'gas': 100000,
'gasPrice': web3.eth.gas_price
})
# Sign and send
signed = web3.eth.account.sign_transaction(tx, private_key)
tx_hash = web3.eth.send_raw_transaction(signed.rawTransaction)
# Wait for confirmation
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
if receipt['status'] != 1:
raise Exception('Approval failed')
return receipt
```
***
## Complete Approval Flow
```javascript theme={"system"}
async function ensureApproval(quote, walletClient, publicClient) {
const { fromToken, fromAmount, fromAddress } = quote.action;
const { approvalAddress } = quote.estimate;
// Step 1: Check if native token
const NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';
if (fromToken.address.toLowerCase() === NATIVE_ADDRESS) {
console.log('Native token - no approval needed');
return null;
}
// Step 2: Check current allowance
const currentAllowance = await publicClient.readContract({
address: fromToken.address,
abi: erc20Abi,
functionName: 'allowance',
args: [fromAddress, approvalAddress]
});
// Step 3: Compare
const requiredAmount = BigInt(fromAmount);
if (currentAllowance >= requiredAmount) {
console.log('Sufficient allowance exists');
return null;
}
// Step 4: Send approval
console.log('Sending approval transaction...');
const hash = await walletClient.writeContract({
address: fromToken.address,
abi: erc20Abi,
functionName: 'approve',
args: [approvalAddress, requiredAmount]
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`Approval confirmed: ${receipt.transactionHash}`);
return receipt;
}
```
***
## Common Mistakes
Always use `quote.estimate.approvalAddress`, not a hardcoded address. Different routes may use different contracts.
The main transaction will fail if sent before the approval is confirmed. Always wait for the approval receipt.
Ensure your wallet is connected to the source chain (`fromChain`) when sending the approval.
Approval transactions typically need 50,000-100,000 gas. Use a reasonable gas limit.
USDT requires setting allowance to 0 before setting a new non-zero value. Check current allowance and reset if needed.
```javascript theme={"system"}
// USDT special handling
if (tokenSymbol === 'USDT' && currentAllowance > 0) {
await approveToken(tokenAddress, spender, 0n); // Reset to 0
await approveToken(tokenAddress, spender, amount); // Then set new amount
}
```
***
## Approval Checklist
Before executing a transfer, verify:
* [ ] `fromToken.address` is NOT `0x0000...0000` (native token)
* [ ] Retrieved `approvalAddress` from quote response
* [ ] Checked current `allowance(owner, spender)`
* [ ] Compared allowance to `fromAmount`
* [ ] Sent approval tx if allowance \< fromAmount
* [ ] Waited for approval confirmation
* [ ] Connected to correct chain (`fromChain`)
***
## Related Pages
* [Execution](/agents/workflows/execution) - How to execute the main transaction
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Complete workflow overview
# Transaction Execution
Source: https://docs.li.fi/agents/workflows/execution
How to sign, send, and confirm LI.FI transactions
After getting a quote and handling any required approvals, you need to execute the actual transfer transaction. This page covers chain verification, gas handling, signing, and broadcasting.
## The Transaction Request
The quote response includes a ready-to-use `transactionRequest` object:
```json theme={"system"}
{
"transactionRequest": {
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x4630a0d8...",
"value": "0x0",
"gasLimit": "0x55730",
"gasPrice": "0x5d21dba00",
"chainId": 1
}
}
```
| Field | Description |
| ---------- | ----------------------------------- |
| `to` | LI.FI contract address |
| `data` | Encoded function call |
| `value` | Native token amount (hex, in wei) |
| `gasLimit` | Estimated gas limit (hex) |
| `gasPrice` | Suggested gas price (hex, optional) |
| `chainId` | Chain ID for the transaction |
***
## Pre-Execution Checklist
Before sending the transaction:
* [ ] Wallet is connected to the correct chain (`transactionRequest.chainId`)
* [ ] User has sufficient native token for gas
* [ ] User has sufficient `fromToken` balance
* [ ] Token approval is confirmed (if ERC20)
* [ ] Quote is still valid (not expired)
***
## Step 1: Verify Chain
Ensure the wallet is on the correct chain before sending:
### JavaScript (viem)
```javascript theme={"system"}
import { createWalletClient, http } from 'viem';
import { mainnet, arbitrum, optimism, polygon, base } from 'viem/chains';
const CHAIN_MAP = {
1: mainnet,
42161: arbitrum,
10: optimism,
137: polygon,
8453: base
};
async function ensureCorrectChain(walletClient, targetChainId) {
const currentChainId = await walletClient.getChainId();
if (currentChainId !== targetChainId) {
console.log(`Switching from chain ${currentChainId} to ${targetChainId}`);
await walletClient.switchChain({ id: targetChainId });
// Verify switch succeeded
const newChainId = await walletClient.getChainId();
if (newChainId !== targetChainId) {
throw new Error(`Failed to switch to chain ${targetChainId}`);
}
}
return true;
}
```
### Python (web3.py)
```python theme={"system"}
def ensure_correct_chain(web3, target_chain_id):
current_chain = web3.eth.chain_id
if current_chain != target_chain_id:
raise Exception(
f"Wrong chain. Connected to {current_chain}, need {target_chain_id}"
)
return True
```
***
## Step 2: Estimate Gas (Optional)
The quote includes a `gasLimit`, but you can re-estimate for accuracy:
```javascript theme={"system"}
async function estimateGas(publicClient, txRequest, fromAddress) {
try {
const gasEstimate = await publicClient.estimateGas({
account: fromAddress,
to: txRequest.to,
data: txRequest.data,
value: BigInt(txRequest.value || '0x0')
});
// Add 20% buffer
return (gasEstimate * 120n) / 100n;
} catch (error) {
// Fall back to quote's gasLimit
console.log('Using quote gasLimit:', txRequest.gasLimit);
return BigInt(txRequest.gasLimit);
}
}
```
The quote's `gasLimit` is usually accurate. Only re-estimate if you need precise gas costs or are optimizing for gas efficiency.
***
## Step 3: Build the Transaction
Convert the quote's transaction request into a sendable transaction:
### JavaScript (viem)
```javascript theme={"system"}
function buildTransaction(txRequest, gasLimit) {
return {
to: txRequest.to,
data: txRequest.data,
value: BigInt(txRequest.value || '0x0'),
gas: gasLimit || BigInt(txRequest.gasLimit),
chainId: txRequest.chainId
};
}
```
### Gas Price Options
You can use the quote's suggested gas price or fetch current prices:
```javascript theme={"system"}
// Option 1: Use quote's gas price
const tx = {
...buildTransaction(txRequest),
gasPrice: BigInt(txRequest.gasPrice)
};
// Option 2: Let wallet determine gas price (recommended)
const tx = buildTransaction(txRequest);
// Don't set gasPrice - wallet will use current market rate
// Option 3: Use EIP-1559 (for supported chains)
const tx = {
...buildTransaction(txRequest),
maxFeePerGas: parseGwei('50'),
maxPriorityFeePerGas: parseGwei('2')
};
```
***
## Step 4: Sign and Send
### JavaScript (viem)
```javascript theme={"system"}
async function executeTransaction(walletClient, publicClient, txRequest) {
// Ensure on correct chain
await ensureCorrectChain(walletClient, txRequest.chainId);
// Build transaction
const tx = {
to: txRequest.to,
data: txRequest.data,
value: BigInt(txRequest.value || '0x0'),
gas: BigInt(txRequest.gasLimit)
};
// Send transaction
console.log('Sending transaction...');
const hash = await walletClient.sendTransaction(tx);
console.log(`Transaction sent: ${hash}`);
// Wait for confirmation
console.log('Waiting for confirmation...');
const receipt = await publicClient.waitForTransactionReceipt({
hash,
confirmations: 1
});
if (receipt.status !== 'success') {
throw new Error(`Transaction failed: ${hash}`);
}
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
return {
hash,
receipt
};
}
```
### Python (web3.py)
```python theme={"system"}
def execute_transaction(web3, tx_request, private_key):
account = web3.eth.account.from_key(private_key)
# Build transaction
tx = {
'to': tx_request['to'],
'data': tx_request['data'],
'value': int(tx_request.get('value', '0x0'), 16),
'gas': int(tx_request['gasLimit'], 16),
'nonce': web3.eth.get_transaction_count(account.address),
'chainId': tx_request['chainId']
}
# Get gas price
tx['gasPrice'] = web3.eth.gas_price
# Sign transaction
signed = web3.eth.account.sign_transaction(tx, private_key)
# Send transaction
print('Sending transaction...')
tx_hash = web3.eth.send_raw_transaction(signed.rawTransaction)
print(f'Transaction sent: {tx_hash.hex()}')
# Wait for confirmation
print('Waiting for confirmation...')
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
if receipt['status'] != 1:
raise Exception(f'Transaction failed: {tx_hash.hex()}')
print(f'Confirmed in block {receipt["blockNumber"]}')
return {
'hash': tx_hash.hex(),
'receipt': receipt
}
```
***
## Step 5: Handle the Result
After execution, save the transaction hash for status polling:
```javascript theme={"system"}
const { hash: txHash } = await executeTransaction(walletClient, publicClient, quote.transactionRequest);
// Save for status polling (only txHash is required, others speed up response)
const statusParams = {
txHash, // Required
fromChain: quote.action.fromChainId, // Recommended for faster response
toChain: quote.action.toChainId, // Optional
bridge: quote.tool // Optional (e.g., "stargateV2")
};
// Now poll for cross-chain status
// See: Status & Recovery page
```
***
## Complete Execution Flow
```javascript theme={"system"}
async function executeQuote(quote, walletClient, publicClient) {
const { transactionRequest, action, tool, estimate } = quote;
// 1. Verify chain
await ensureCorrectChain(walletClient, transactionRequest.chainId);
// 2. Handle approval if needed
await ensureApproval(quote, walletClient, publicClient);
// 3. Execute main transaction
const tx = {
to: transactionRequest.to,
data: transactionRequest.data,
value: BigInt(transactionRequest.value || '0x0'),
gas: BigInt(transactionRequest.gasLimit)
};
const hash = await walletClient.sendTransaction(tx);
// 4. Wait for source chain confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash,
confirmations: 1
});
if (receipt.status !== 'success') {
throw new Error('Transaction reverted');
}
// 5. Return data for status polling
return {
txHash: hash,
bridge: tool,
fromChain: action.fromChainId,
toChain: action.toChainId,
receipt
};
}
```
***
## Error Handling
### Common Execution Errors
| Error | Cause | Solution |
| ------------------------- | ------------------------------ | --------------------------------- |
| `insufficient funds` | Not enough ETH for gas + value | Check balance before sending |
| `nonce too low` | Pending transaction exists | Wait or speed up pending tx |
| `gas too low` | Gas limit insufficient | Increase gas limit |
| `execution reverted` | Contract rejected transaction | Quote may be stale, get new quote |
| `replacement fee too low` | Trying to replace pending tx | Increase gas price |
### Retry Logic
```javascript theme={"system"}
async function executeWithRetry(quote, walletClient, publicClient, maxRetries = 2) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await executeQuote(quote, walletClient, publicClient);
} catch (error) {
const message = error.message.toLowerCase();
// Don't retry these errors
if (message.includes('insufficient funds') ||
message.includes('user rejected') ||
message.includes('denied')) {
throw error;
}
// Quote might be stale - get new quote and retry
if (message.includes('reverted') && attempt < maxRetries - 1) {
console.log('Transaction reverted, fetching new quote...');
quote = await getQuote(originalParams);
continue;
}
throw error;
}
}
}
```
***
## Gas Optimization Tips
1. **Use EIP-1559 where supported** - More predictable gas costs
2. **Don't over-estimate gas** - Quote's gasLimit is usually accurate
3. **Check gas prices** - High gas times may make transfers expensive
4. **Consider L2s** - Arbitrum, Optimism, Base have much lower gas costs
```javascript theme={"system"}
// Check if gas cost is reasonable
const gasCostWei = BigInt(txRequest.gasLimit) * BigInt(txRequest.gasPrice);
const gasCostEth = Number(gasCostWei) / 1e18;
if (gasCostEth > 0.01) { // More than 0.01 ETH
console.log(`Warning: High gas cost: ${gasCostEth} ETH`);
}
```
***
## Related Pages
* [Token Approvals](/agents/workflows/approvals) - Handle ERC20 approvals
* [Status & Recovery](/agents/workflows/status-recovery) - Poll transfer status
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Complete workflow
# Partial Completion
Source: https://docs.li.fi/agents/workflows/partial-completion
How to detect and recover from partial transfers
A `PARTIAL` completion occurs when a cross-chain transfer succeeds but the user receives a different token than requested. This typically happens when the destination swap fails but the bridge succeeds. This page explains how to detect, interpret, and recover from partial completions.
## What is a Partial Completion?
In a cross-chain transfer, there are often multiple steps:
```
Source Chain Destination Chain
[Token A] → [Swap to B] → [Bridge] → [Receive B] → [Swap to C]
```
If the final swap on the destination chain fails (e.g., due to slippage), the user receives token B instead of token C. The value is preserved, but the token is different.
### Example Scenario
* **Requested**: 10 USDC on Ethereum → ETH on Arbitrum
* **Actual Result**: 10 USDC on Ethereum → 10 USDC on Arbitrum (swap failed)
* **Status**: `DONE` with substatus `PARTIAL`
***
## Detecting Partial Completion
Check the status response:
```javascript theme={"system"}
const result = await pollTransferStatus(params);
if (result.partial) {
// User received a different token
const received = result.status.receiving;
const requested = quote.action.toToken;
console.log(`Requested: ${requested.symbol}`);
console.log(`Received: ${received.token.symbol}`);
}
```
### Status Response for PARTIAL
```json theme={"system"}
{
"status": "DONE",
"substatus": "PARTIAL",
"sending": {
"chainId": 1,
"amount": "10000000",
"token": {
"symbol": "USDC",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
},
"receiving": {
"chainId": 42161,
"amount": "10000000",
"token": {
"symbol": "USDC",
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
}
},
"tool": "stargateV2"
}
```
***
## Recovery Flow
When a partial completion occurs, offer the user a same-chain swap to get their intended token.
### Step 1: Extract Received Token Details
```javascript theme={"system"}
function extractReceivedToken(status) {
const { receiving } = status;
return {
chainId: receiving.chainId,
address: receiving.token.address,
symbol: receiving.token.symbol,
decimals: receiving.token.decimals,
amount: receiving.amount
};
}
```
### Step 2: Get a Same-Chain Swap Quote
Request a quote to swap the received token to the intended token:
```javascript theme={"system"}
async function getRecoveryQuote(status, originalQuote, userAddress) {
const received = extractReceivedToken(status);
const intended = originalQuote.action.toToken;
// Same-chain swap on the destination chain
const params = {
fromChain: received.chainId,
toChain: received.chainId, // Same chain
fromToken: received.address,
toToken: intended.address,
fromAmount: received.amount,
fromAddress: userAddress
};
const response = await fetch(
`https://li.quest/v1/quote?` + new URLSearchParams(params)
);
return await response.json();
}
```
### Step 3: Present Options to User
```javascript theme={"system"}
async function handlePartialCompletion(status, originalQuote, userAddress) {
const received = extractReceivedToken(status);
const intended = originalQuote.action.toToken;
console.log('\n=== Partial Transfer Detected ===');
console.log(`You received: ${formatAmount(received.amount, received.decimals)} ${received.symbol}`);
console.log(`You requested: ${intended.symbol}`);
// Get recovery quote
try {
const recoveryQuote = await getRecoveryQuote(status, originalQuote, userAddress);
const expectedOutput = formatAmount(
recoveryQuote.estimate.toAmount,
intended.decimals
);
console.log(`\nRecovery option available:`);
console.log(`Swap ${received.symbol} → ${intended.symbol}`);
console.log(`Expected output: ${expectedOutput} ${intended.symbol}`);
return {
hasRecoveryOption: true,
received,
recoveryQuote
};
} catch (error) {
console.log(`\nNo direct swap available for ${received.symbol} → ${intended.symbol}`);
return {
hasRecoveryOption: false,
received,
error: error.message
};
}
}
function formatAmount(amount, decimals) {
return (Number(amount) / Math.pow(10, decimals)).toFixed(4);
}
```
***
## Complete Recovery Implementation
```javascript theme={"system"}
async function executePartialRecovery(
status,
originalQuote,
walletClient,
publicClient,
userAddress
) {
// 1. Extract what was received
const received = extractReceivedToken(status);
const intended = originalQuote.action.toToken;
console.log('Partial completion detected');
console.log(`Received: ${received.amount} ${received.symbol}`);
console.log(`Intended: ${intended.symbol}`);
// 2. Check if we need to recover (did user already get intended token?)
if (received.address.toLowerCase() === intended.address.toLowerCase()) {
console.log('Already received intended token - no recovery needed');
return { success: true, recovered: false };
}
// 3. Get recovery quote
console.log('Getting recovery swap quote...');
const recoveryQuote = await getRecoveryQuote(status, originalQuote, userAddress);
// 4. Check if recovery is worthwhile (accounting for gas)
const gasCostEstimate = estimateGasCost(recoveryQuote);
const swapValue = estimateSwapValue(recoveryQuote);
if (gasCostEstimate > swapValue * 0.1) {
console.log('Gas cost too high relative to swap value');
console.log('Recommend keeping received token');
return {
success: true,
recovered: false,
reason: 'Gas cost exceeds 10% of swap value'
};
}
// 5. Execute recovery swap
console.log('Executing recovery swap...');
// Handle approval if needed
await ensureApproval(recoveryQuote, walletClient, publicClient);
// Execute swap
const { hash } = await executeTransaction(
walletClient,
publicClient,
recoveryQuote.transactionRequest
);
console.log(`Recovery swap sent: ${hash}`);
// 6. Poll recovery status (same-chain is usually fast)
const recoveryResult = await pollTransferStatus({
txHash: hash,
bridge: recoveryQuote.tool,
fromChain: received.chainId,
toChain: received.chainId
}, {
maxDuration: 5 * 60 * 1000 // 5 minutes for same-chain
});
return {
success: recoveryResult.success,
recovered: true,
recoveryTxHash: hash,
finalStatus: recoveryResult.status
};
}
```
***
## Why Partial Completions Happen
| Cause | Explanation |
| ----------------- | ------------------------------------------------- |
| Slippage exceeded | Price moved beyond tolerance during transfer time |
| Liquidity changed | DEX pool liquidity depleted |
| Token delist | Destination DEX no longer supports the pair |
| Contract issue | Destination DEX had temporary issue |
***
## Prevention Strategies
### 1. Use Higher Slippage for Volatile Tokens
```javascript theme={"system"}
// For volatile tokens, increase slippage
const slippage = isVolatileToken(toToken) ? 0.02 : 0.005; // 2% vs 0.5%
```
### 2. Prefer Direct Bridge Routes
```javascript theme={"system"}
// Request routes without destination swap when possible
const quote = await getQuote({
...params,
toToken: 'USDC', // Bridge token directly instead of swapping
});
```
### 3. Check Route Feasibility Before Transfer
For large amounts, you can verify the destination swap is feasible by requesting a quote for just that leg:
```javascript theme={"system"}
// Get quote for just the destination swap
const destSwapQuote = await getQuote({
fromChain: toChain,
toChain: toChain,
fromToken: bridgeToken,
toToken: intendedToken,
fromAmount: amount
});
// If quote fails with NO_POSSIBLE_ROUTE, the destination swap may fail
// Consider using the bridge token as the final destination
```
***
## User Communication Templates
### Partial Completion Detected
```
Your transfer has partially completed.
✓ Bridged successfully
✗ Destination swap failed due to slippage
You received: 10.00 USDC on Arbitrum
You requested: ETH on Arbitrum
Would you like to swap your USDC to ETH now?
Estimated output: 0.0029 ETH
```
### Recovery Not Available
```
Your transfer has partially completed.
You received: 10.00 TOKEN on Arbitrum
You requested: ETH on Arbitrum
Unfortunately, there's no direct swap route for TOKEN → ETH.
Your TOKEN balance is available in your wallet on Arbitrum.
Options:
1. Keep the TOKEN
2. Manually swap on a DEX that supports this token
```
***
## Decision Tree
```
PARTIAL status received
│
▼
Is received token = intended token?
│
┌───┴───┐
│ │
Yes No
│ │
▼ ▼
Done Get recovery quote
│
▼
Quote available?
│
┌───┴───┐
│ │
Yes No
│ │
▼ ▼
Gas cost < 10% Inform user,
of value? suggest manual
│
┌───┴───┐
│ │
Yes No
│ │
▼ ▼
Execute Recommend keeping
recovery received token
```
***
## Related Pages
* [Status & Recovery](/agents/workflows/status-recovery) - Polling and status handling
* [Decision Tables](/agents/quick-start/decision-tables) - Route selection
* [Error Playbooks](/agents/reference/error-playbooks) - Handle failures
# Status & Recovery
Source: https://docs.li.fi/agents/workflows/status-recovery
How to poll transfer status and handle different outcomes
Cross-chain transfers take time to complete. This page explains how to poll the status endpoint, interpret status values, and handle each outcome correctly.
## Status Endpoint
```bash theme={"system"}
GET https://li.quest/v1/status
```
### Parameters
| Parameter | Required | Description | Source |
| ----------- | -------- | ------------------------------- | ------------------------------- |
| `txHash` | **Yes** | Transaction hash from execution | From `sendTransaction` result |
| `fromChain` | No | Source chain ID (recommended) | From `quote.action.fromChainId` |
| `toChain` | No | Destination chain ID | From `quote.action.toChainId` |
| `bridge` | No | Bridge/tool identifier | From `quote.tool` |
Only `txHash` is required. Providing `fromChain` speeds up the response significantly.
### Example Request
```bash theme={"system"}
curl "https://li.quest/v1/status?txHash=0xabc123..."
```
Or with optional parameters for faster response:
```bash theme={"system"}
curl "https://li.quest/v1/status?txHash=0xabc123...&fromChain=1&toChain=42161&bridge=stargateV2"
```
***
## Status Response
```json theme={"system"}
{
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"sending": {
"txHash": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"txLink": "https://etherscan.io/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"chainId": 1,
"amount": "10000000",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9961",
"gasPrice": "65729222",
"gasUsed": "220266",
"gasToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"priceUSD": "2923.27"
},
"gasAmount": "14477912813052",
"gasAmountUSD": "0.0423",
"timestamp": 1737625200
},
"receiving": {
"txHash": "0xdef456789abc...",
"txLink": "https://arbiscan.io/tx/0xdef456789abc...",
"chainId": 42161,
"amount": "9965731",
"token": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9618",
"timestamp": 1737625204
},
"lifiExplorerLink": "https://explorer.li.fi/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"tool": "across",
"status": "DONE",
"substatus": "COMPLETED"
}
```
***
## Status Values
### Primary Status
| Status | Meaning | Action |
| ----------- | --------------------------- | ----------------------------- |
| `NOT_FOUND` | Transaction not indexed yet | Continue polling |
| `PENDING` | Transfer in progress | Continue polling |
| `DONE` | Transfer finished | Check `substatus` for outcome |
| `FAILED` | Transfer failed | Handle error |
### Substatus (when status = DONE)
| Substatus | Meaning | User Outcome |
| ----------- | ------------------ | ------------------------------------- |
| `COMPLETED` | Success | Received exact requested token |
| `PARTIAL` | Partial success | Received different token (full value) |
| `REFUNDED` | Failed with refund | Tokens returned to sender |
***
## Decision Matrix
```
IF status === "NOT_FOUND"
→ Wait and poll again (tx may not be indexed yet)
IF status === "PENDING"
→ Wait and poll again (transfer in progress)
IF status === "DONE"
IF substatus === "COMPLETED"
→ Success! User received toToken on toChain
IF substatus === "PARTIAL"
→ Partial success - see Partial Completion guide
IF substatus === "REFUNDED"
→ Failed - tokens returned to user on fromChain
IF status === "FAILED"
→ Permanent failure - inform user, may need manual recovery
```
***
## Polling Implementation
### Recommended Strategy
| Attempt | Wait Time | Total Time |
| ------- | ----------- | ------------- |
| 1-6 | 10 seconds | 1 minute |
| 7-12 | 30 seconds | 4 minutes |
| 13-24 | 60 seconds | 16 minutes |
| 25+ | 120 seconds | Until timeout |
### JavaScript Implementation
```javascript theme={"system"}
async function pollTransferStatus(params, options = {}) {
const { txHash, bridge, fromChain, toChain } = params;
const {
maxDuration = 30 * 60 * 1000, // 30 minutes
onStatusUpdate = () => {}
} = options;
const startTime = Date.now();
let attempt = 0;
while (Date.now() - startTime < maxDuration) {
attempt++;
try {
const response = await fetch(
`https://li.quest/v1/status?txHash=${txHash}&bridge=${bridge}&fromChain=${fromChain}&toChain=${toChain}`
);
if (!response.ok) {
throw new Error(`Status API error: ${response.status}`);
}
const status = await response.json();
// Notify caller of status update
onStatusUpdate(status, attempt);
// Check for terminal states
if (status.status === 'DONE') {
return {
success: status.substatus === 'COMPLETED',
partial: status.substatus === 'PARTIAL',
refunded: status.substatus === 'REFUNDED',
status
};
}
if (status.status === 'FAILED') {
return {
success: false,
failed: true,
status
};
}
} catch (error) {
console.error(`Polling error (attempt ${attempt}):`, error.message);
// Continue polling despite errors
}
// Calculate wait time with backoff
const waitTime = getWaitTime(attempt);
await sleep(waitTime);
}
throw new Error('Status polling timeout');
}
function getWaitTime(attempt) {
if (attempt <= 6) return 10000; // 10 seconds
if (attempt <= 12) return 30000; // 30 seconds
if (attempt <= 24) return 60000; // 60 seconds
return 120000; // 2 minutes
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
```
### Python Implementation
```python theme={"system"}
import time
import requests
def poll_transfer_status(tx_hash, bridge, from_chain, to_chain,
max_duration=1800, on_update=None):
"""
Poll transfer status until completion or timeout.
Args:
tx_hash: Transaction hash
bridge: Bridge tool name
from_chain: Source chain ID
to_chain: Destination chain ID
max_duration: Maximum polling duration in seconds (default 30 min)
on_update: Optional callback for status updates
Returns:
dict with success, partial, refunded, failed flags and status
"""
start_time = time.time()
attempt = 0
while time.time() - start_time < max_duration:
attempt += 1
try:
url = (
f"https://li.quest/v1/status"
f"?txHash={tx_hash}"
f"&bridge={bridge}"
f"&fromChain={from_chain}"
f"&toChain={to_chain}"
)
response = requests.get(url)
response.raise_for_status()
status = response.json()
# Notify callback
if on_update:
on_update(status, attempt)
# Check terminal states
if status.get('status') == 'DONE':
substatus = status.get('substatus')
return {
'success': substatus == 'COMPLETED',
'partial': substatus == 'PARTIAL',
'refunded': substatus == 'REFUNDED',
'status': status
}
if status.get('status') == 'FAILED':
return {
'success': False,
'failed': True,
'status': status
}
except Exception as e:
print(f"Polling error (attempt {attempt}): {e}")
# Wait with backoff
wait_time = get_wait_time(attempt)
time.sleep(wait_time)
raise Exception('Status polling timeout')
def get_wait_time(attempt):
if attempt <= 6:
return 10
if attempt <= 12:
return 30
if attempt <= 24:
return 60
return 120
```
***
## Handling Each Outcome
### COMPLETED - Success
```javascript theme={"system"}
if (result.success) {
const { receiving } = result.status;
console.log('Transfer successful!');
console.log(`Received: ${receiving.amount} ${receiving.token.symbol}`);
console.log(`On chain: ${receiving.chainId}`);
console.log(`Tx: ${receiving.txHash}`);
// No further action needed
}
```
### PARTIAL - Different Token Received
```javascript theme={"system"}
if (result.partial) {
const { receiving } = result.status;
console.log('Transfer completed with different token');
console.log(`Received: ${receiving.amount} ${receiving.token.symbol}`);
// User may want to swap to their intended token
// See: Partial Completion guide
}
```
### REFUNDED - Tokens Returned
```javascript theme={"system"}
if (result.refunded) {
const { sending } = result.status;
console.log('Transfer failed - tokens refunded');
console.log(`Refunded: ${sending.amount} ${sending.token.symbol}`);
console.log(`On chain: ${sending.chainId}`);
// User's tokens are back on the source chain
// May retry with different parameters
}
```
### FAILED - Permanent Failure
```javascript theme={"system"}
if (result.failed) {
console.error('Transfer failed permanently');
console.error('Error:', result.status.error);
// May need manual intervention
// Contact support with transaction details
}
```
***
## Estimated Transfer Times
The quote response includes `estimate.executionDuration` (in seconds) which gives you the expected transfer time for that specific route. Always use this value rather than general estimates.
Transfer times vary significantly based on the bridge used and network conditions. The `executionDuration` field in the quote response provides the most accurate estimate for each specific route.
***
## Timeout Handling
If polling times out:
1. **Don't assume failure** - The transfer may still complete
2. **Save the transaction details** - txHash, bridge, fromChain, toChain
3. **Provide manual check** - User can check status later
4. **Link to explorer** - Provide transaction explorer URLs
```javascript theme={"system"}
function getExplorerUrl(chainId, txHash) {
const explorers = {
1: 'https://etherscan.io/tx/',
42161: 'https://arbiscan.io/tx/',
10: 'https://optimistic.etherscan.io/tx/',
137: 'https://polygonscan.com/tx/',
8453: 'https://basescan.org/tx/'
};
return `${explorers[chainId] || 'https://blockscan.com/tx/'}${txHash}`;
}
// On timeout
console.log('Status check timed out. The transfer may still be processing.');
console.log(`Check source tx: ${getExplorerUrl(fromChain, txHash)}`);
console.log('You can also check status manually at: https://li.fi/');
```
***
## Related Pages
* [Partial Completion](/agents/workflows/partial-completion) - Handle PARTIAL status
* [Transaction Execution](/agents/workflows/execution) - Execute transfers
* [Error Playbooks](/agents/reference/error-playbooks) - Handle failures
# List chains supported by LI.FI Earn
Source: https://docs.li.fi/api-reference/earn-chains/list-chains-supported-by-lifi-earn
/earn-openapi.yaml get /v1/chains
Returns the list of chains that have at least one Earn vault available. Derived from current vault data.
**This endpoint is scoped to LI.FI Earn (vaults).** Only chains where Earn currently has indexed vaults are returned — it is not a general list of all chains the LI.FI platform supports, and it should not be used to determine SDK / Swaps / Bridges chain coverage.
If you are integrating Swaps or Bridges (via the LI.FI SDK or the main API), call [`GET https://li.quest/v1/chains`](/api-reference/get-information-about-all-currently-supported-chains) instead. That endpoint returns every chain supported by the LI.FI Aggregation API.
# Get portfolio positions for a user
Source: https://docs.li.fi/api-reference/earn-portfolio/get-portfolio-positions-for-a-user
/earn-openapi.yaml get /v1/portfolio/{userAddress}/positions
Returns a user's DeFi positions across all supported protocols and chains.
# List supported protocols
Source: https://docs.li.fi/api-reference/earn-protocols/list-supported-protocols
/earn-openapi.yaml get /v1/protocols
Returns the list of protocols that have at least one vault available. Derived from current vault data.
# Get vault by chain and address
Source: https://docs.li.fi/api-reference/earn-vaults/get-vault-by-chain-and-address
/earn-openapi.yaml get /v1/vaults/{chainId}/{address}
Returns the full details for a single vault.
# List vaults with optional filtering
Source: https://docs.li.fi/api-reference/earn-vaults/list-vaults-with-optional-filtering
/earn-openapi.yaml get /v1/vaults
Returns a paginated list of vaults with optional filtering and sorting.
# Available Commands
Source: https://docs.li.fi/cli/commands
Complete reference for all commands exposed by the LI.FI CLI
The LI.FI CLI exposes the following commands, organized by category. Each command maps to a corresponding [LI.FI API](/api-reference/introduction) endpoint.
## Token Information
### tokens
Retrieve tokens supported by LI.FI on a specific chain.
```bash theme={"system"}
lifi tokens --chain 1 # List tokens on Ethereum
lifi tokens --chain 1 --min-price 100 # Filter by min USD price
```
| Flag | Required | Description |
| ------------- | -------- | --------------------------- |
| `--chain` | Yes | Chain ID to list tokens for |
| `--min-price` | No | Minimum token price in USD |
### token
Get details about a specific token including price, decimals, and contract address.
```bash theme={"system"}
lifi token 1 USDC # By symbol
lifi token 1 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 # By address
```
| Argument | Required | Description |
| -------- | -------- | -------------------------------- |
| `chain` | Yes | Chain ID (e.g., `1`) |
| `token` | Yes | Token symbol or contract address |
## Chain Information
### chains
List all supported blockchain networks.
```bash theme={"system"}
lifi chains # List all supported chains
lifi chains --type EVM # Filter by chain type
```
| Flag | Required | Description |
| -------- | -------- | ----------------------------------------- |
| `--type` | No | Filter by chain type (e.g., `EVM`, `SVM`) |
### chain
Get details for a specific chain by ID or name.
```bash theme={"system"}
lifi chain 42161 # By chain ID
lifi chain arbitrum # By name (case-insensitive)
```
| Argument | Required | Description |
| -------- | -------- | ---------------- |
| `chain` | Yes | Chain ID or name |
## Quote & Swap
### quote
Get the best route for a swap. Returns the optimal route, fees, estimated time, and a `transactionRequest` object ready for signing.
```bash theme={"system"}
lifi quote \
--from ethereum --to arbitrum \
--from-token USDC --to-token USDC \
--amount 1000000000 \
--from-address 0xd8dA...
```
Run without flags to enter interactive mode:
```bash theme={"system"}
lifi quote
```
| Flag | Required | Description |
| ---------------- | -------- | ---------------------------------------------------------------- |
| `--from` | Yes | Source chain ID or name |
| `--to` | Yes | Destination chain ID or name |
| `--from-token` | Yes | Source token address or symbol |
| `--to-token` | Yes | Destination token address or symbol |
| `--amount` | Yes | Amount in smallest unit (e.g., wei) |
| `--from-address` | Yes | Sender wallet address |
| `--order` | No | Route preference: `RECOMMENDED`, `FASTEST`, `CHEAPEST`, `SAFEST` |
The response includes a `transactionRequest` object with `to`, `data`, `value`, `gasLimit`, and `chainId` fields, ready to sign and broadcast with any wallet.
### routes
Get multiple route options for comparison.
```bash theme={"system"}
lifi routes \
--from 1 --to 42161 \
--from-token USDC --to-token USDC \
--amount 1000000000 \
--order CHEAPEST
```
| Flag | Required | Description |
| -------------- | -------- | ---------------------------------------------------------------- |
| `--from` | Yes | Source chain ID |
| `--to` | Yes | Destination chain ID |
| `--from-token` | Yes | Source token address or symbol |
| `--to-token` | Yes | Destination token address or symbol |
| `--amount` | Yes | Amount in smallest unit |
| `--order` | No | Route preference: `RECOMMENDED`, `FASTEST`, `CHEAPEST`, `SAFEST` |
## Transaction Status
### status
Track the progress of a cross-chain transfer.
```bash theme={"system"}
lifi status 0xabc123... # One-shot status check
lifi status 0xabc123... --watch # Poll until complete/failed
lifi status 0xabc123... --bridge hop # Speed up lookup with bridge hint
```
| Argument / Flag | Required | Description |
| --------------- | -------- | --------------------------------------------- |
| `txHash` | Yes | Transaction hash from the source chain |
| `--watch` | No | Poll until the transfer completes or fails |
| `--bridge` | No | Bridge name from the quote (speeds up lookup) |
## Connections
### connections
Check available swap routes between chains.
```bash theme={"system"}
lifi connections # All connections
lifi connections --from-chain 1 --to-chain 42161 # Specific pair
```
| Flag | Required | Description |
| -------------- | -------- | -------------------- |
| `--from-chain` | No | Source chain ID |
| `--to-chain` | No | Destination chain ID |
## Tools (Bridges & DEXes)
### tools
List available bridges and DEXes.
```bash theme={"system"}
lifi tools # List all bridges and DEXes
lifi tools --chain 1 # Filter by chain
```
| Flag | Required | Description |
| --------- | -------- | -------------------------- |
| `--chain` | No | Chain ID to filter results |
## Gas
### gas
Get current gas prices and suggestions.
```bash theme={"system"}
lifi gas # Gas prices for all chains
lifi gas 1 # Detailed gas suggestion for Ethereum
```
| Argument | Required | Description |
| --------- | -------- | ------------------------------------ |
| `chainId` | No | Chain ID for detailed gas suggestion |
## API Key Management
### auth show
Display the currently configured API key (masked).
```bash theme={"system"}
lifi auth show
```
### auth test
Validate the configured API key against the API.
```bash theme={"system"}
lifi auth test
```
The API key is read from the `LIFI_API_KEY` environment variable. See [Installation](/cli/installation) for setup instructions.
## Health Check
### health
Check API connectivity and latency.
```bash theme={"system"}
lifi health
```
*No arguments required.*
# Examples
Source: https://docs.li.fi/cli/examples
Output modes, exit codes, and end-to-end workflow examples for the LI.FI CLI
## Output Modes
The CLI auto-detects your terminal and adjusts output format accordingly.
| Mode | Trigger | Behaviour |
| ------- | ----------------------------- | ---------------------------------- |
| Human | Default (TTY detected) | Coloured tables, formatted amounts |
| Machine | `--json` flag or non-TTY pipe | Raw JSON, stable schema, no colour |
### Pipe-Friendly Output
The CLI auto-detects non-TTY environments and switches to JSON:
```bash theme={"system"}
# Auto-detects non-TTY — outputs JSON
lifi chains | jq '.chains[].name'
```
### Force JSON in Terminal
```bash theme={"system"}
lifi chains --json
```
### Disable Colour
```bash theme={"system"}
lifi chains --no-color
```
### Verbose Errors
Enable stack traces for debugging:
```bash theme={"system"}
lifi quote --from 1 --to 42161 --verbose
```
## Exit Codes
| Code | Meaning |
| ---- | ------------------------------------ |
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments / usage |
| 3 | Authentication error |
| 4 | API error (rate limit, server error) |
| 5 | Network error (unreachable) |
## End-to-End Workflow: Cross-Chain Swap
This example walks through a complete cross-chain USDC transfer from Ethereum to Base.
### Step 1: Find Chain IDs
```bash theme={"system"}
lifi chains --type EVM
```
### Step 2: Look Up Token Address
```bash theme={"system"}
lifi token 1 USDC
```
### Step 3: Get Best Quote
```bash theme={"system"}
lifi quote \
--from 1 --to 8453 \
--from-token USDC --to-token USDC \
--amount 1000000000 \
--from-address 0xYOUR_ADDRESS
```
The response includes a `transactionRequest` object ready for signing.
### Step 4: Approve and Sign (External)
Use your wallet to:
1. Approve the token spend if needed
2. Sign and broadcast the `transactionRequest` from the quote
### Step 5: Track Progress
```bash theme={"system"}
lifi status 0xTX_HASH --watch
```
The `--watch` flag polls until the transfer completes or fails.
## Scripting Example
Combine commands in a shell script:
```bash theme={"system"}
#!/bin/bash
# Get the cheapest route for 1000 USDC from Ethereum to Base
QUOTE=$(lifi quote \
--from 1 --to 8453 \
--from-token USDC --to-token USDC \
--amount 1000000000 \
--from-address "$WALLET_ADDRESS" \
--json)
# Extract estimated output
echo "$QUOTE" | jq '.estimate.toAmountUSD'
# Extract transaction request for external signing
echo "$QUOTE" | jq '.transactionRequest'
```
# Installation
Source: https://docs.li.fi/cli/installation
Setup instructions for installing and configuring the LI.FI CLI
## npm (Global Install)
Install the CLI globally to use the `lifi` command anywhere:
```bash theme={"system"}
npm install -g @lifi/cli
```
Verify the installation:
```bash theme={"system"}
lifi health
```
## npx (No Install)
Run any CLI command without installing:
```bash theme={"system"}
npx @lifi/cli chains
npx @lifi/cli quote --from 1 --to 8453 --from-token USDC --to-token USDC --amount 1000000000
```
## Configuration
All configuration is via environment variables. No config files needed.
### API Key
Set your LI.FI API key for higher rate limits:
```bash theme={"system"}
export LIFI_API_KEY=your_key_here
```
| Configuration | Rate Limit |
| ------------------- | ---------------------- |
| No API key (public) | 200 requests / 2 hours |
| With API key | 200 requests / minute |
### Verify Your Key
```bash theme={"system"}
lifi auth show # Display masked key
lifi auth test # Validate key against the API
```
## Testing Your Setup
Run the health check to verify API connectivity:
```bash theme={"system"}
lifi health
```
List supported chains to confirm everything works:
```bash theme={"system"}
lifi chains
```
If both commands return data successfully, your CLI is ready to use.
Source code and development instructions
# Overview
Source: https://docs.li.fi/cli/overview
A scriptable, human-readable command-line interface to LI.FI cross-chain swap infrastructure
The LI.FI CLI wraps the [LI.FI REST API](https://li.quest) to give developers, integrators, and internal teams a scriptable, human-readable interface to cross-chain swap infrastructure.
This CLI provides **read-only** tools — it does not sign or broadcast transactions. Quote responses include unsigned `transactionRequest` objects that must be signed and submitted externally using your own wallet.
## Quickstart
Install globally via npm:
```bash theme={"system"}
npm install -g @lifi/cli
lifi chains
```
Or run without installing:
```bash theme={"system"}
npx @lifi/cli chains
```
No API key required — works immediately with public rate limits. Add a key for higher throughput.
## How It Works
The CLI wraps the LI.FI REST API into structured commands that output human-readable tables (TTY) or machine-readable JSON (pipes / `--json` flag).
```text theme={"system"}
CLI Command (lifi quote, lifi chains, etc.)
│
▼
LI.FI API (https://li.quest)
│
▼
27+ Bridges & 31+ DEXes across 58 chains
```
## Example Workflow
A typical cross-chain swap via the CLI follows this flow:
```bash theme={"system"}
# 1. Find chain IDs
lifi chains --type EVM
# 2. Look up token addresses
lifi token 1 USDC
# 3. Get best quote
lifi quote \
--from 1 --to 8453 \
--from-token USDC --to-token USDC \
--amount 1000000000 \
--from-address 0xYOUR_ADDRESS
# 4. (External) Approve tokens and sign transactionRequest with your wallet
# 5. Track progress
lifi status 0xTX_HASH --watch
```
## API Key Configuration
Without an API key, the CLI uses the public rate limit (**200 requests / 2 hours**). With an API key, you get higher rate limits (**200 requests / minute**).
Set your API key via environment variable:
```bash theme={"system"}
export LIFI_API_KEY=your_key_here
```
Use the `auth` commands to verify your key:
```bash theme={"system"}
lifi auth show # Display masked key
lifi auth test # Validate key against the API
```
Sign up at li.fi to get your API key
## Common Chain IDs
| Chain | ID | Native Token |
| --------- | ----- | ------------ |
| Ethereum | 1 | ETH |
| Polygon | 137 | MATIC |
| Arbitrum | 42161 | ETH |
| Optimism | 10 | ETH |
| BSC | 56 | BNB |
| Avalanche | 43114 | AVAX |
| Base | 8453 | ETH |
Use `lifi chains` to dynamically look up chain IDs instead of hardcoding them.
## Next Steps
npm install, npx, and configuration options
Complete reference for all CLI commands
Output modes, exit codes, and end-to-end workflows
Source code and development instructions
# Integrate Your Protocol with Composer
Source: https://docs.li.fi/composer/for-protocols/integration-guide
Why and how to get your DeFi protocol integrated with LI.FI Composer. Reach users on every chain with one-click deposits.
LI.FI Composer enables users to deposit into your protocol from **any token on any EVM chain** in a single transaction. This page explains why you should integrate and how the process works.
***
## Why Integrate with Composer
### Reach Users on Every Chain
Without Composer, users must navigate to your protocol's UI and sign multiple transactions. With Composer, users can deposit into your protocol in one click, directly from wallets, dApps, and aggregators that integrate LI.FI, including from any other EVM chain.
### Distribution Through the LI.FI Ecosystem
LI.FI is integrated into hundreds of wallets, dApps, and DeFi platforms. When your protocol is available via Composer, it becomes accessible through:
* **LI.FI API:** used by integrators building custom DeFi experiences
* **LI.FI SDK:** TypeScript SDK for frontend and backend applications
* **LI.FI Widget:** embeddable UI component used by wallets and dApps
### Better UX for Your Users
Composer transforms multi-step, multi-chain operations into a single transaction:
| Without Composer | With Composer |
| -------------------------------- | ---------------- |
| Bridge assets to the right chain | One transaction |
| Swap to the required token | One signature |
| Approve the token | One click |
| Deposit into the protocol | Atomic execution |
### Pre-Execution Simulation
Every Composer transaction is simulated before execution, giving users certainty about the outcome before they sign. This reduces failed transactions and improves trust in your protocol.
***
## Integration Requirements
To be integrated with Composer, your protocol must meet these requirements:
### 1. EVM-Compatible Chain
Composer currently supports **EVM-compatible chains only**. Solana and other non-EVM chains are not yet supported.
### 2. Tokenised Positions
Your protocol must return a **tokenised position** to the user upon deposit. This includes:
* **Vault tokens** (e.g., ERC-4626 vault shares)
* **Receipt tokens** (e.g., Aave aTokens)
* **Liquid staking tokens** (e.g., wstETH)
* **Yield tokens** (e.g., Pendle yield tokens)
Protocols that do not return a token upon deposit are not currently supported.
### 3. Standard Deposit Interface
Your protocol's deposit function should follow standard patterns that Composer's eDSL compiler can encode. The LI.FI team will work with you to ensure compatibility.
***
## Currently Integrated Protocols
20+ protocols are already integrated with Composer, spanning lending, liquid staking, yield, and vault strategies.
View the full list of integrated protocols, supported actions, and example token addresses.
***
## Getting Integrated
Integrations are handled by the LI.FI team. Partner protocols can also be invited to contribute to the Composer backend codebase directly.
Fill out the protocol integration form to start the process.
You can also reach out directly:
* **Email**: [marketing@li.finance](mailto:marketing@li.finance)
For detailed technical requirements, see [Technical Requirements](/composer/for-protocols/technical-requirements).
***
## What You Get
Once integrated, your protocol is automatically available to:
* **All LI.FI API integrators:** your vault/staking tokens appear as valid `toToken` destinations
* **All LI.FI Widget deployments:** users can select your protocol's tokens as destinations
* **All LI.FI SDK users:** developers can build custom flows targeting your protocol
No additional work is required from your side after integration. LI.FI handles the routing, bridging, swapping, and deposit execution.
***
## Next Steps
Detailed technical requirements for integration
Learn how Composer works
Architecture deep-dive
Current constraints and supported chains
# Technical Requirements
Source: https://docs.li.fi/composer/for-protocols/technical-requirements
Technical requirements for integrating your DeFi protocol with LI.FI Composer.
This page details the technical requirements your protocol must meet to be integrated with Composer.
***
## Core Requirements
### 1. EVM Compatibility
Your protocol must be deployed on an **EVM-compatible chain**. Composer's onchain VM executes compiled bytecode on EVM chains. Non-EVM chains (Solana, SUI, etc.) are not currently supported.
### 2. Tokenised Position Return
Upon deposit, your protocol must return a **token** to the user's wallet. Composer routes are identified by the `toToken` parameter, which must be a valid ERC-20 token that represents the user's position.
**Supported token types:**
| Token Type | Example | Protocol Example |
| --------------------- | ----------------------------------- | ------------------------ |
| ERC-4626 vault shares | Vault share tokens | Morpho, Euler |
| aTokens | Interest-bearing receipt tokens | Aave V3 |
| Liquid staking tokens | LSTs representing staked positions | Lido wstETH, EtherFi |
| Yield tokens | Tokens representing yield positions | Pendle, Ethena sUSDe |
| Custom receipt tokens | Protocol-specific deposit receipts | Felix Vanilla, Neverland |
### 3. Standard Deposit Function
Your deposit function should accept standard parameters that Composer's eDSL compiler can encode. Common patterns include:
* **ERC-4626 `deposit(uint256 assets, address receiver)`:** Standard vault interface
* **Aave-style `supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)`:** Lending pool interface
* **Staking `stake(uint256 amount)`** or `wrap(uint256 amount)`: Staking/wrapping interface
Composer's eDSL supports dynamic calldata injection, meaning the exact deposit amount is determined at execution time based on the output of preceding swap/bridge steps. Your deposit function must accept a standard amount parameter.
***
## Deposit vs. Withdraw Support
Composer supports two action types per protocol:
| Action | Description | Required? |
| ------------ | ------------------------------------------------------ | ------------- |
| **Deposit** | User sends tokens, receives position tokens | Yes (minimum) |
| **Withdraw** | User sends position tokens, receives underlying tokens | Optional |
Some protocols are integrated with **deposit only** (e.g., Kinetiq, Ethena, Maple). This is acceptable, and withdraw support can be added later.
***
## After Integration
Once live, your protocol is accessible via:
```bash theme={"system"}
# Users can deposit from any token on any EVM chain
curl -X GET 'https://li.quest/v1/quote?fromChain=SOURCE_CHAIN_ID&toChain=YOUR_PROTOCOL_CHAIN_ID&fromToken=ANY_TOKEN_ADDRESS&toToken=YOUR_VAULT_TOKEN_ADDRESS&fromAddress=0xUSER_WALLET&toAddress=0xUSER_WALLET&fromAmount=AMOUNT'
```
LI.FI handles routing, bridging, swapping, and depositing automatically.
***
## Contact
To start the integration process or ask questions, reach out to the LI.FI team:
* **Email**: [support@li.fi](mailto:support@li.fi)
* **Website**: [li.fi](https://li.fi)
***
## Related Pages
Why integrate and what you get
Architecture: Onchain VM, eDSL, and compilation
Currently integrated protocols
Current constraints and limitations
# API Parameters
Source: https://docs.li.fi/composer/reference/api-parameters
How Composer uses the standard LI.FI API parameters, including tool identification, slippage, and route filtering.
Composer activates automatically when `toToken` is set to a [supported protocol](/composer/reference/supported-protocols) token address. No additional parameters are needed beyond the standard LI.FI quote parameters.
***
## How `toToken` Triggers Composer
When `toToken` corresponds to a supported vault/staking/deposit token, LI.FI's routing engine automatically:
1. Identifies this as a Composer route
2. Finds the optimal path (swap, bridge, or both) to acquire the underlying asset
3. Compiles the Composer instructions (eDSL → bytecode)
4. Simulates the full execution path
5. Returns a `transactionRequest` ready to sign
***
## Composer-Relevant Parameters
These parameters are part of the standard `GET /quote` and `POST /advanced/routes` endpoints. They are not Composer-specific, but are particularly relevant when building Composer integrations.
| Parameter | Type | Required | Description |
| ---------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `fromChain` | number | Yes | Source chain ID (e.g., `1` for Ethereum, `8453` for Base) |
| `toChain` | number | Yes | Destination chain ID. Same as `fromChain` for same-chain deposits. |
| `fromToken` | string | Yes | Source token address. Use `0x0000000000000000000000000000000000000000` for native tokens. |
| `toToken` | string | Yes | **Vault/staking/deposit token address.** This is what triggers Composer. |
| `fromAmount` | string | Yes | Amount in the `fromToken`'s smallest unit (e.g., `1000000` for 1 USDC with 6 decimals). |
| `fromAddress` | string | Yes | Sender's wallet address. |
| `toAddress` | string | Yes | Recipient's wallet address. Usually the same as `fromAddress`. Can differ for deposit-on-behalf flows. |
| `slippage` | number | No | Maximum acceptable price difference as a decimal. `0.005` = 0.5%. Defaults to `0.005`. |
| `integrator` | string | No | Identifies your application in analytics and on-chain events. Max 23 characters. |
| `allowBridges` | string\[] | No | Restrict routes to specific bridges (e.g., `["stargate","across"]`). Retrieved from `GET /v1/tools`. |
| `denyBridges` | string\[] | No | Exclude specific bridges from routing. |
| `allowExchanges` | string\[] | No | Restrict routes to specific DEX aggregators. Retrieved from `GET /v1/tools`. |
| `denyExchanges` | string\[] | No | Exclude specific DEX aggregators from routing. |
| `order` | string | No | Route preference: `"FASTEST"` or `"CHEAPEST"`. |
***
## Identifying Composer Routes
### The Composer tool name
Composer's tool identifier is **`"composer"`**. This string appears in:
* `quote.tool`: top-level tool used for the route
* `quote.toolDetails.key`: same value, with additional `name` and `logoURI`
* `quote.includedSteps[].tool`: tool used in each step of the route
Composer activates automatically when `toToken` matches a supported protocol token. You cannot filter for or against Composer routes at the request level. It is determined by the `toToken` address.
### Detecting Composer routes in your application
Use the `tool` field in the response to build conditional UI:
```ts theme={"system"}
const { data: quote } = await axios.get('https://li.quest/v1/quote', { params });
if (quote.tool === 'composer') {
// Composer route: show deposit/staking UI
showComposerUI(quote);
} else {
// Standard swap or bridge: show swap UI
showSwapUI(quote);
}
```
### Controlling bridges and exchanges
You can control which bridges or DEX aggregators are used within a Composer route using the standard bridge/exchange filter parameters:
```ts theme={"system"}
const { data: quote } = await axios.get('https://li.quest/v1/quote', {
params: {
...params,
allowBridges: ['stargate', 'across'], // Only use these bridges for cross-chain
order: 'FASTEST', // Prefer speed over cost
},
});
```
***
## Full Parameter Reference
For complete endpoint documentation, parameter lists, and authentication details, see:
Single-step quote endpoint with all parameters
Multi-route endpoint with all parameters
When to use each endpoint
Base URL, authentication, and rate limits
***
## Related Pages
Step-by-step Composer integration with response schema
Implement withdraw routes with the same API surface
Handle Composer-specific errors
Full list of supported vault/staking tokens with example addresses
# Error Handling
Source: https://docs.li.fi/composer/reference/error-handling
How to handle errors and failure modes when using LI.FI Composer, including API errors, transaction failures, and cross-chain edge cases.
This page covers the errors you may encounter when using Composer and how to handle them. Composer uses the same error system as the broader LI.FI API, and the errors documented here are contextualised for Composer-specific scenarios.
***
## API Errors (Quote/Route Request)
When requesting a Composer quote via `GET /quote` or `POST /advanced/routes`, the API may return errors. These follow the standard LI.FI error format:
1. **HTTP status code** (e.g., 200, 404, 429, 500)
2. **LI.FI error code** (numeric)
3. **Error message** (human-readable)
### Relevant API Error Codes
| Code | Name | Composer Context |
| ------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `1001` | `FailedToBuildTransactionError` | Composer could not build the transaction. The vault token may be invalid or the protocol may be temporarily unavailable. |
| `1002` | `NoQuoteError` | No Composer route found. The vault token address may not be supported, or there's insufficient liquidity for the requested path. |
| `1004` | `NotProcessableError` | The request cannot be processed. Check that all parameters are valid. |
| `1005` | `RateLimitError` | Too many requests. Implement backoff and retry. See [Rate Limits](/api-reference/rate-limits). |
| `1007` | `SlippageError` | Price impact exceeds slippage tolerance. Increase the `slippage` parameter or reduce the amount. |
| `1009` | `TimeoutError` | Request timed out. Retry the request. |
| `1011` | `ValidationError` | Invalid parameters. Check that `fromChain`, `toChain`, `fromToken`, `toToken`, and `fromAmount` are valid. |
For the complete list of API error codes, see [Error Codes](/api-reference/error-codes).
***
## Tool Errors
The API may also return tool-specific errors describing issues with the underlying protocols. These use the `ToolError` format:
```typescript theme={"system"}
interface ToolError {
errorType: 'NO_QUOTE';
code: string;
action: Action;
tool: string;
message: string;
}
```
### Relevant Tool Error Codes
| Code | Description | Composer Context |
| ------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `NO_POSSIBLE_ROUTE` | No route found for this action | The vault token may not be supported, or the source/destination chain combination is not available |
| `INSUFFICIENT_LIQUIDITY` | The tool's liquidity is insufficient | Not enough liquidity to complete the swap portion of the Composer flow |
| `TOOL_TIMEOUT` | The third-party tool timed out | The underlying protocol or DEX timed out. Retry the request. |
| `AMOUNT_TOO_LOW` | Amount is too low to transfer | Increase `fromAmount`. Composer routes may have higher minimums due to multiple steps. |
| `AMOUNT_TOO_HIGH` | Amount exceeds available liquidity | Reduce `fromAmount` or split into multiple transactions. |
| `FEES_HIGHER_THAN_AMOUNT` | Fees exceed the transfer amount | Increase `fromAmount` so it covers gas and protocol fees. |
| `TOOL_SPECIFIC_ERROR` | The third-party tool returned an error | The target protocol returned an error. Check that the vault is accepting deposits. |
### Example: Handling Tool Errors
```ts theme={"system"}
try {
const quote = await axios.get('https://li.quest/v1/quote', { params });
} catch (error) {
if (error.response?.data?.errors) {
for (const toolError of error.response.data.errors) {
console.error(`Tool: ${toolError.tool}`);
console.error(`Code: ${toolError.code}`);
console.error(`Message: ${toolError.message}`);
switch (toolError.code) {
case 'NO_POSSIBLE_ROUTE':
// Vault token may not be supported
console.log('Check that the toToken is a supported vault token address.');
break;
case 'INSUFFICIENT_LIQUIDITY':
// Try a smaller amount
console.log('Try reducing the fromAmount.');
break;
case 'AMOUNT_TOO_LOW':
// Increase the amount
console.log('Increase the fromAmount.');
break;
case 'TOOL_TIMEOUT':
// Retry
console.log('Retrying...');
break;
}
}
}
}
```
***
## Transaction Failures
### Pre-Execution Simulation Failure
Composer simulates the entire execution path before returning a quote. If simulation fails, the API returns an error **instead of** a transaction that would revert onchain. This protects users from wasting gas on failed transactions.
Common simulation failure causes:
* The vault is not accepting deposits (paused, full, or restricted)
* The token path involves incompatible tokens
* Insufficient liquidity in the swap path
### Onchain Transaction Revert
In rare cases, a transaction may revert onchain even after passing simulation (e.g., due to mempool front-running or rapid state changes between simulation and execution). If this happens:
1. The user's tokens remain in their wallet (for same-chain atomic transactions)
2. Gas fees for the reverted transaction are still consumed
3. Retry with a fresh quote to get updated simulation results
***
## Cross-Chain Failure Modes
Cross-chain Composer flows have two phases. Each phase is atomic within its chain, but the overall flow is eventually consistent.
### Status Values
Poll `GET /v1/status` to track cross-chain Composer transactions. The status values are:
| Status | Description |
| ----------- | ------------------------------------------ |
| `NOT_FOUND` | Transaction doesn't exist or not yet mined |
| `INVALID` | Hash is not tied to the requested tool |
| `PENDING` | Transfer is still in progress |
| `DONE` | Transaction completed successfully |
| `FAILED` | Transfer failed |
### Substatus Values
#### When Status is `PENDING`
| Substatus | Description |
| ------------------------------ | ---------------------------------------- |
| `WAIT_SOURCE_CONFIRMATIONS` | Waiting for source chain confirmations |
| `WAIT_DESTINATION_TRANSACTION` | Waiting for destination transaction |
| `BRIDGE_NOT_AVAILABLE` | Bridge API is unavailable |
| `CHAIN_NOT_AVAILABLE` | Source/destination chain RPC unavailable |
| `REFUND_IN_PROGRESS` | Refund in progress (if supported) |
| `UNKNOWN_ERROR` | Status is indeterminate |
#### When Status is `DONE`
| Substatus | Description |
| ----------- | ------------------------------- |
| `COMPLETED` | Transfer was successful |
| `PARTIAL` | Only partial transfer completed |
| `REFUNDED` | Tokens were refunded |
#### When Status is `FAILED`
| Substatus | Description |
| ------------------------------- | ------------------------------ |
| `NOT_PROCESSABLE_REFUND_NEEDED` | Cannot complete, refund needed |
| `OUT_OF_GAS` | Transaction ran out of gas |
| `SLIPPAGE_EXCEEDED` | Received amount too low |
| `INSUFFICIENT_ALLOWANCE` | Not enough token allowance |
| `INSUFFICIENT_BALANCE` | Not enough token balance |
| `EXPIRED` | Transaction expired |
| `UNKNOWN_ERROR` | Unknown or invalid state |
| `REFUNDED` | Tokens were refunded |
### Handling Cross-Chain Failures
```ts theme={"system"}
const status = await axios.get('https://li.quest/v1/status', {
params: { txHash, fromChain, toChain },
}).then(r => r.data);
switch (status.status) {
case 'DONE':
if (status.substatus === 'COMPLETED') {
console.log('Composer deposit completed successfully.');
} else if (status.substatus === 'PARTIAL') {
console.log('Partial completion: bridged tokens may be on destination chain but not deposited.');
} else if (status.substatus === 'REFUNDED') {
console.log('Tokens were refunded to source chain.');
}
break;
case 'FAILED':
if (status.substatus === 'SLIPPAGE_EXCEEDED') {
console.log('Slippage exceeded. Retry with higher slippage tolerance.');
} else if (status.substatus === 'INSUFFICIENT_ALLOWANCE') {
console.log('Insufficient allowance. Approve tokens before retrying.');
} else if (status.substatus === 'NOT_PROCESSABLE_REFUND_NEEDED') {
console.log('Transfer cannot complete. Refund will be processed.');
} else {
console.error('Transfer failed:', status.substatus, status.substatusMessage);
}
break;
case 'PENDING':
console.log('Still in progress:', status.substatus);
break;
}
```
For the full status reference, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).
***
## Common Composer Issues
| Issue | Cause | Resolution |
| ---------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| No routes returned | `toToken` is not a supported vault token address | Verify the address against the [Supported Protocols](/composer/reference/supported-protocols) list |
| Simulation failed | Vault is paused, full, or not accepting deposits | Check the protocol's status directly |
| Transaction reverted | State changed between simulation and execution | Retry with a fresh quote |
| Cross-chain stuck in PENDING | Bridge is slow or congested | Wait and continue polling. Check the bridge's own status page if it remains stuck. |
| Partial completion | Bridge succeeded but destination action failed | User has tokens on destination chain. They can retry the deposit directly. |
***
## Related Pages
Complete LI.FI API error code reference
Full status and substatus reference
Composer-specific API parameters
Current Composer limitations
# Limitations
Source: https://docs.li.fi/composer/reference/limitations
Current limitations and constraints of LI.FI Composer.
Composer is actively evolving. This page documents the current limitations so you can plan your integration accordingly.
***
## Current Limitations
### EVM Chains Only
Composer supports **EVM-compatible chains only**, including cross-chain deposits between EVM chains. There is no support for:
* Solana
* Other non-EVM chains
### Tokenised Positions Only
Composer targets must return a **tokenised position** to the user. This includes:
* Vault tokens (e.g., Morpho vault shares)
* aTokens (e.g., Aave aUSDC)
* Liquid staking tokens (e.g., wstETH, EtherFi tokens)
* Yield tokens (e.g., Pendle yield tokens, sUSDe, vkHYPE)
Protocols that do not return a token upon deposit are not currently supported by Composer.
### Deposit-Only Protocols
Some supported protocols only support **deposit** via Composer. Withdrawals must be performed directly through the protocol's interface:
| Protocol | Limitation |
| ----------- | --------------------------------------- |
| **Ethena** | Deposit only (USDe → sUSDe, ENA → sENA) |
| **Kinetiq** | Deposit only |
| **Maple** | Deposit only |
| **Royco** | Deposit only |
| **USDai** | Deposit only |
### Non-EVM Chains Are Not Supported
Cross-chain Composer deposits work across EVM chains today. Solana and other non-EVM chains are not supported.
Cross-chain flows are not fully atomic. Each chain's phase is atomic within itself, but the overall flow is eventually consistent. In rare cases, a bridge may succeed while the destination deposit fails, leaving the user with tokens on the destination chain rather than deposited into the target protocol. Always implement status polling and handle the `FAILED` case in your UI.
### Simulation Cannot Prevent Every Revert
Composer simulates the full execution path before returning a quote, but the onchain state can change between simulation and execution (e.g., mempool front-running, vault becoming full). In these edge cases, the transaction may revert onchain. Source tokens remain in the user's wallet (for same-chain), but gas fees are consumed.
***
## Related Pages
Full list of supported protocols and their capabilities
Handle Composer errors and failure modes
Understand same-chain vs cross-chain execution
What is Composer and why use it
# Supported Protocols & Chains
Source: https://docs.li.fi/composer/reference/supported-protocols
Live reference for protocols and chains supported by LI.FI Composer.
## Supported Protocols
Set `toToken` to the protocol's vault, staking, or deposit token address and Composer activates automatically.
Protocols marked **Deposit only** must be withdrawn through the protocol's own interface.
***
## Supported Chains
Composer currently supports deposits on the following chains.
***
## Example Token Addresses
These are example addresses you pass as `toToken` to trigger a Composer route.
Vault and market token addresses vary by chain, asset, and strategy. Always verify against the protocol's own app or documentation before using in production.
| Protocol | Token | Chain | Address | Decimals |
| -------- | ---------------- | ------------ | -------------------------------------------- | -------- |
| Morpho | Spark USDC Vault | Base (8453) | `0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A` | 18 |
| Aave V3 | aEthUSDC | Ethereum (1) | `0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c` | 6 |
| Ethena | sUSDe | Ethereum (1) | `0x9D39A5DE30e57443BfF2A8307A4256c8797A3497` | 18 |
Morpho vaults are named after their **curator** (e.g., Spark, Gauntlet), not after Morpho itself. A vault returning `symbol: "sparkUSDC"` is a Spark-curated Morpho vault. Browse all Morpho vaults at [app.morpho.org](https://app.morpho.org/).
For any supported protocol, find the correct vault token address in the protocol's own app or documentation.
***
## Requesting a New Protocol Integration
* The protocol must be on an **EVM-compatible chain**
* The protocol must return **tokenised positions** (e.g., vault tokens, aTokens, LSTs)
See [For Protocol Teams](/composer/for-protocols/integration-guide) or contact the LI.FI team to start the process.
***
## Related Pages
End-to-end deposit recipes for multiple protocols
Withdrawal patterns for supported protocols
Current limitations and constraints
# API Integration
Source: https://docs.li.fi/earn/guides/api-integration
Full reference for the LI.FI Earn Data API. Covers all endpoints, query parameters, response schemas, pagination, and error handling.
This guide covers every endpoint in the Earn Data API with full parameter documentation and response examples. The Earn Data API handles vault discovery, analytics, and portfolio tracking. For executing deposits and withdrawals, see the [Composer API Integration Guide](/composer/guides/api-integration).
***
## Base URL
```
https://earn.li.fi
```
All endpoints are prefixed with `/v1/`.
The Earn Data API and Composer use **different base URLs**. Earn Data API endpoints use [`https://earn.li.fi`](https://earn.li.fi/v1/vaults), while Composer endpoints (e.g., `GET /v1/quote`) use `https://li.quest`. See the [overview](/earn/overview#the-two-layers) for details.
***
## API Overview
| Endpoint | Description |
| ------------------------------------------ | ----------------------------------------- |
| `GET /v1/vaults` | List vaults with filtering and pagination |
| `GET /v1/vaults/:chainId/:address` | Get a single vault's full details |
| `GET /v1/chains` | List chains with active vaults |
| `GET /v1/protocols` | List protocols with active vaults |
| `GET /v1/portfolio/:userAddress/positions` | Get a user's DeFi positions |
***
## Authentication
The Earn Data API uses the same authentication as the rest of the LI.FI API. Pass your API key via the `x-lifi-api-key` header:
```bash theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults' \
--header 'x-lifi-api-key: YOUR_API_KEY'
```
API keys are created via the [LI.FI Partner Portal](https://li.fi/plans/). See [Authentication](/api-reference/authentication) for full details.
***
## Rate Limits
The default rate limit for Earn Data API endpoints is **50 requests per minute** per API key.
If you need higher limits, contact our team via the [LI.FI Partner Portal](https://li.fi/plans/).
If you exceed the limit, you'll receive a `429 Too Many Requests` response. See [Rate Limits](/api-reference/rate-limits) for details on rate limit headers and best practices.
***
## List Vaults
```
GET /v1/vaults
```
Returns a paginated list of vaults with optional filtering and sorting.
### Query Parameters
| Parameter | Type | Required | Default | Description |
| --------------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
| `chainId` | `integer` | No | — | Filter by EVM chain ID (e.g., `8453` for Base, `1` for Ethereum) |
| `asset` | `string` | No | — | Filter by underlying token symbol (e.g., `"USDC"`, `"ETH"`) |
| `protocol` | `string` | No | — | Filter by protocol name (e.g., `"morpho-v1"`, `"aave-v3"`) |
| `minTvlUsd` | `number` | No | — | Minimum TVL in USD (e.g., `1000000` for \$1M+) |
| `isTransactional` | `boolean` | No | — | Pass `"true"` to return only vaults that support programmatic deposits and withdrawals via Composer |
| `isRedeemable` | `boolean` | No | — | Pass `"true"` to return only vaults that currently support withdrawals |
| `isComposerSupported` | `boolean` | No | — | Pass `"true"` to return only vaults fully supported by the LI.FI Composer for end-to-end cross-chain deposit flows |
| `sortBy` | `string` | No | — | Sort order: `"apy"` (highest first) or `"tvl"` (highest first) |
| `limit` | `integer` | No | `50` | Results per page (1–100) |
| `cursor` | `string` | No | — | Pagination cursor from a previous response's `nextCursor` |
### Example Request
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults?chainId=8453&asset=USDC&sortBy=apy&minTvlUsd=100000&limit=10'
```
```ts TypeScript theme={"system"}
const params = new URLSearchParams({
chainId: '8453',
asset: 'USDC',
sortBy: 'apy',
minTvlUsd: '100000',
limit: '10',
});
const response = await fetch(`https://earn.li.fi/v1/vaults?${params}`);
const { data, nextCursor, total } = await response.json();
```
### Response
```json theme={"system"}
{
"data": [
{
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"network": "base",
"chainId": 8453,
"slug": "morpho-base-usdc-0x7bfa",
"name": "Morpho USDC Vault",
"description": "Optimized USDC lending vault on Morpho",
"protocol": {
"name": "Morpho",
"logoUri": "https://example.com/morpho-logo.png",
"url": "https://morpho.org"
},
"underlyingTokens": [
{
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6,
"weight": 1.0
}
],
"lpTokens": [
{
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"symbol": "mUSDC",
"decimals": 18,
"priceUsd": "1.02"
}
],
"rewardTokens": [],
"tags": ["stablecoin", "lending"],
"analytics": {
"apy": {
"base": 0.0534,
"reward": null,
"total": 0.0534
},
"apy1d": 0.0521,
"apy7d": 0.0538,
"apy30d": 0.0545,
"tvl": {
"usd": "12500000.00",
"native": "12500000000000"
},
"updatedAt": "2026-03-31T14:30:00.000Z"
},
"caps": {
"totalCap": "50000000000000",
"maxCap": "100000000000000"
},
"timeLock": 0,
"kyc": false,
"isTransactional": true,
"isRedeemable": true,
"depositPacks": [
{ "name": "morpho-deposit", "stepsType": "instant" }
],
"redeemPacks": [
{ "name": "morpho-redeem", "stepsType": "instant" }
],
"syncedAt": "2026-03-31T14:30:00.000Z"
}
],
"nextCursor": "eyJpZCI6MTAwfQ",
"total": 47
}
```
Some fields may be `null` in the response: `description` (absent for \~70% of vaults), `apy.base`, `apy.reward`, `apy1d`, `apy7d`, and `caps`. Always handle nullable fields in your integration.
### Pagination
The API uses cursor-based pagination. To fetch the next page, pass the `nextCursor` value from the response as the `cursor` query parameter:
```ts TypeScript theme={"system"}
// Fetch all vaults across pages
let cursor: string | undefined;
const allVaults = [];
do {
const params = new URLSearchParams({ limit: '100' });
if (cursor) params.set('cursor', cursor);
const response = await fetch(`https://earn.li.fi/v1/vaults?${params}`);
const { data, nextCursor } = await response.json();
allVaults.push(...data);
cursor = nextCursor;
} while (cursor);
```
***
## Get Vault by Chain and Address
```
GET /v1/vaults/:chainId/:address
```
Returns the full details for a single vault.
### Path Parameters
| Parameter | Type | Required | Description |
| --------- | --------- | -------- | ------------------------------------------------------- |
| `chainId` | `integer` | Yes | EVM chain ID |
| `address` | `string` | Yes | Vault contract address (0x-prefixed, 40 hex characters) |
### Example Request
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults/8453/0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A'
```
```ts TypeScript theme={"system"}
const chainId = 8453;
const address = '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A';
const response = await fetch(`https://earn.li.fi/v1/vaults/${chainId}/${address}`);
const vault = await response.json();
```
### Response
Returns a single [NormalizedVault](/earn/how-it-works#the-normalizedvault-schema) object (same shape as items in the list endpoint).
### Errors
| Status | Description |
| ------ | ---------------------------------- |
| `400` | Invalid chain ID or address format |
| `404` | Vault not found |
***
## List Earn-Supported Chains
```
GET /v1/chains
```
Returns the list of chains that have at least one Earn vault available. Derived from current vault data.
**Scoped to LI.FI Earn.** This endpoint only returns chains where Earn currently has indexed vaults — it is not a general list of all chains the LI.FI platform supports. If you are integrating **Swaps or Bridges** (via the LI.FI SDK or the main API), use [`GET https://li.quest/v1/chains`](/api-reference/get-information-about-all-currently-supported-chains) instead.
### Example Request
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/chains'
```
```ts TypeScript theme={"system"}
const response = await fetch('https://earn.li.fi/v1/chains');
const chains = await response.json();
```
### Response
```json theme={"system"}
[
{
"name": "Ethereum",
"chainId": 1,
"networkCaip": "eip155:1"
},
{
"name": "Base",
"chainId": 8453,
"networkCaip": "eip155:8453"
},
{
"name": "Arbitrum One",
"chainId": 42161,
"networkCaip": "eip155:42161"
}
]
```
***
## List Supported Protocols
```
GET /v1/protocols
```
Returns the list of protocols that have at least one vault available. Derived from current vault data.
### Example Request
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/protocols'
```
```ts TypeScript theme={"system"}
const response = await fetch('https://earn.li.fi/v1/protocols');
const protocols = await response.json();
```
### Response
```json theme={"system"}
[
{
"name": "Morpho",
"logoUri": "https://example.com/morpho-logo.png",
"url": "https://morpho.org"
},
{
"name": "Aave V3",
"logoUri": "https://example.com/aave-logo.png",
"url": "https://aave.com"
},
{
"name": "Euler",
"logoUri": "https://example.com/euler-logo.png",
"url": "https://www.euler.finance"
}
]
```
***
## Get User Portfolio Positions
```
GET /v1/portfolio/:userAddress/positions
```
Returns a user's DeFi positions across all supported protocols.
### Path Parameters
| Parameter | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------------------------------ |
| `userAddress` | `string` | Yes | User's wallet address (0x-prefixed, 40 hex characters) |
### Example Request
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/portfolio/0x1234567890abcdef1234567890abcdef12345678/positions'
```
```ts TypeScript theme={"system"}
const userAddress = '0x1234567890abcdef1234567890abcdef12345678';
const response = await fetch(
`https://earn.li.fi/v1/portfolio/${userAddress}/positions`
);
const { positions } = await response.json();
```
### Response
```json theme={"system"}
{
"positions": [
{
"chainId": 1,
"address": "0xa17581a9e3356d9a858b789d68b4d866e593ae94",
"protocolName": "aave-v3",
"asset": {
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"name": "USD Coin",
"symbol": "USDC",
"decimals": 6
},
"balanceUsd": "1523.45",
"balanceNative": "1523450000"
},
{
"chainId": 8453,
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"protocolName": "morpho",
"asset": {
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"name": "Morpho USDC Vault",
"symbol": "mUSDC",
"decimals": 18
},
"balanceUsd": "5000.00",
"balanceNative": "4901960784313725490196"
}
]
}
```
The `address`, `protocolName`, and `balanceUsd` fields on each position can be `null`. `address` is null when a contract address cannot be determined for the position. `protocolName` is null when the protocol cannot be identified. `balanceUsd` is null when a USD price is unavailable for the asset. Always handle these cases in your integration.
### Errors
| Status | Description |
| ------ | ----------------------------- |
| `400` | Invalid wallet address format |
***
## Error Handling
All Earn Data API endpoints return standard HTTP error responses:
| Status | Meaning |
| ------ | ---------------------------------------------------------------------- |
| `200` | Success |
| `400` | Bad request. Invalid parameters (check the error message for details). |
| `404` | Resource not found (vault does not exist) |
| `500` | Internal server error |
Error responses include a message describing the issue:
```json theme={"system"}
{
"statusCode": 400,
"message": "Invalid Ethereum address"
}
```
***
## Next Steps
End-to-end recipe combining Earn discovery with Composer deposits
Execute deposits and withdrawals via the Composer API
# Supported Protocols & Chains
Source: https://docs.li.fi/earn/guides/supported-protocols
Live reference for protocols and chains supported by LI.FI Earn.
## Supported Protocols
***
## Supported Chains
***
## Related Pages
Full endpoint reference with parameters and response examples
End-to-end recipe: find top vaults, then deposit via Composer
# How Earn Works
Source: https://docs.li.fi/earn/how-it-works
How the Earn Data API aggregates and normalizes vault data: the NormalizedVault schema, transactional capabilities, and data freshness.
This page explains how the Earn Data API aggregates vault data across protocols, normalizes it into a consistent schema, and keeps it fresh.
***
## Architecture Overview
The Earn Data API is a **data aggregation layer** that sits between protocol data and your application. It handles:
1. **Ingestion.** Fetching vault data from multiple data sources across supported protocols.
2. **Normalization.** Transforming protocol-specific formats into a unified `NormalizedVault` schema.
3. **Capability Resolution.** Determining which vaults support deposits and withdrawals via Composer.
4. **Serving.** Exposing the normalized data through a REST API with filtering, sorting, and pagination.
***
## Data Freshness
The Earn Data API runs a background sync pipeline that keeps vault data current:
| Data | Refresh Frequency |
| ----------------------------------------------------- | ----------------- |
| Vault metadata, APY, and TVL | Every 15 minutes |
| Transactional capabilities (deposit/withdraw support) | Every 2 minutes |
This means APY and TVL data is at most 15 minutes stale, and transactional capability data is at most 2 minutes stale.
***
## The NormalizedVault Schema
Every vault in the Earn API follows the same schema, regardless of which protocol or data provider it comes from. This is the core data structure:
```json theme={"system"}
{
"address": "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
"network": "base",
"chainId": 8453,
"slug": "morpho-base-usdc-0x7bfa",
"name": "Morpho USDC Vault",
"description": "Optimized USDC lending vault on Morpho", // optional, may be absent
"protocol": {
"name": "Morpho",
"logoUri": "https://example.com/morpho-logo.png",
"url": "https://morpho.org"
},
"underlyingTokens": [
{
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6,
"weight": 1.0
}
],
"lpTokens": [], // typically empty; use vault `address` as toToken for Composer
"rewardTokens": [], // optional, may be absent
"tags": ["stablecoin", "lending"],
"analytics": {
"apy": {
"base": 0.0534, // nullable, can be null
"reward": null, // nullable, can be null
"total": 0.0534
},
"apy1d": 0.0521, // nullable, can be null
"apy7d": 0.0538, // nullable, can be null
"apy30d": 0.0545,
"tvl": {
"usd": "12500000.00",
"native": "12500000000000" // optional, may be absent
},
"updatedAt": "2026-03-31T14:30:00.000Z"
},
"caps": { // optional, may be absent
"totalCap": "50000000000000",
"maxCap": "100000000000000"
},
"timeLock": 0, // optional, may be absent
"kyc": false, // optional, may be absent
"isTransactional": true,
"isRedeemable": true,
"depositPacks": [
{ "name": "morpho-deposit", "stepsType": "instant" }
],
"redeemPacks": [
{ "name": "morpho-redeem", "stepsType": "instant" }
],
"syncedAt": "2026-03-31T14:30:00.000Z"
}
```
### Key Fields
| Field | Type | Description |
| -------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `address` | `string` | Vault contract address |
| `network` | `string` | Network name (e.g., `"base"`, `"ethereum"`) |
| `chainId` | `number` | EVM chain ID |
| `slug` | `string` | Unique vault identifier across providers |
| `protocol` | `object` | Protocol metadata (name, logo, URL) |
| `underlyingTokens` | `array` | Tokens the vault accepts for deposit (with optional weight for multi-asset vaults) |
| `lpTokens` | `array` | Tokens representing the vault position. Typically empty; use the vault's `address` field as `toToken` for Composer deposits. |
| `rewardTokens` | `array?` | Additional reward tokens (e.g., governance tokens). Optional, may be absent. |
| `analytics.apy` | `object` | Current APY breakdown: `base` (nullable), `reward` (nullable), `total` |
| `analytics.apy1d` / `apy7d` / `apy30d` | `number \| null` | Rolling average APY over 1 day, 7 days, and 30 days. Nullable, may be `null` for new vaults. |
| `analytics.tvl` | `object` | Total value locked. `usd` is always present; `native` is optional. |
| `isTransactional` | `boolean` | Whether deposits are available via Composer |
| `isRedeemable` | `boolean` | Whether withdrawals are available via Composer |
| `depositPacks` / `redeemPacks` | `array` | Composer zap-pack entries describing available deposit/redeem methods |
| `description` | `string?` | Vault description. Optional, may be absent. |
| `caps` | `object?` | Deposit caps (`totalCap`, `maxCap`). Optional, may be absent. |
| `timeLock` | `number?` | Lock period in seconds (0 = no lock). Optional, may be absent. |
| `kyc` | `boolean?` | Whether the vault requires KYC. Optional, may be absent. |
### Transactional Capabilities
The `isTransactional` and `isRedeemable` flags are derived from Composer's zap-pack data, not from the vault itself. This tells you whether the LI.FI infrastructure can execute deposits and withdrawals for this vault:
* **`isTransactional: true`** means you can use [Composer's quote endpoint](/composer/guides/api-integration) with this vault's contract address as `toToken` to deposit.
* **`isRedeemable: true`** means you can use Composer with this vault's contract address as `fromToken` to withdraw.
Use these flags to control your UI. For example, hide the "Deposit" button for vaults where `isTransactional` is `false`.
***
## Supported Protocols and Chains
The Earn Data API aggregates vaults from 20+ protocols across 20 chains. Use the discovery endpoints to see what is currently indexed:
* `GET /v1/chains` — returns all chains with at least one active vault
* `GET /v1/protocols` — returns all protocols with at least one active vault
These endpoints reflect live data from the sync pipeline and will grow as new protocols and chains are onboarded.
***
## Next Steps
Make your first Earn API calls in under 5 minutes
Full endpoint reference with all parameters and response shapes
# What is LI.FI Earn?
Source: https://docs.li.fi/earn/overview
The fastest way to launch an Earn product. Access yield opportunities from 20+ protocols across 60+ chains, with built-in cross-chain execution for one-click deposits.
LI.FI Earn is the fastest way to launch and monetize an Earn product in any wallet, app, or platform. Access market-leading yield opportunities from 20+ protocols across 60+ chains through a single API integration, without requiring in-house DeFi expertise or managing underlying execution complexity.
**Two layers, one product.** LI.FI Earn combines the **Earn Data API** for
vault discovery and portfolio tracking with **Composer** for onchain
execution. Together, they let you surface yield opportunities and execute
deposits in a single integration, across any chain.
***
## Why Use Earn?
* **Access to 20+ protocols.** Surface yield opportunities from Aave, Morpho, Euler, Pendle, and more through a single API. No need to integrate each protocol individually.
* **Standardized data.** Every vault follows the same schema regardless of the underlying protocol. Compare APYs, TVLs, and token compositions without data wrangling.
* **One-click deposits.** Composer handles swap, bridge, and deposit in a single transaction. Users can enter any vault from any token on any chain.
* **Portfolio tracking.** Retrieve all user positions, balances, and returns across supported protocols in a standardized format.
* **Full control.** You decide which protocols to surface, how to structure your product, and what disclosures to show. LI.FI handles the infrastructure.
* **Built-in monetization.** Configure revenue sharing to monetize vault integrations.
***
## The Two Layers
LI.FI Earn provides the full infrastructure for building an Earn product, from discovering yield opportunities to executing deposits and tracking positions.
| Layer | Base URL | What It Does |
| ---------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **Earn Data API** | [`https://earn.li.fi`](https://earn.li.fi/v1/vaults) | Vault discovery, standardized metadata, APY and TVL analytics, portfolio positions and returns |
| **[Composer](/composer/overview)** | `https://li.quest` | Onchain execution: one-click deposits, cross-chain flows, any-token-to-vault transactions |
A typical integration flow:
1. **Discover.** Use the Earn Data API to list vaults, filter by chain/asset/protocol, and sort by APY or TVL.
2. **Display.** Show vault details (APY breakdown, TVL, underlying tokens) in your UI.
3. **Deposit.** When a user wants to deposit, pass the vault's LP token address to Composer's `GET /v1/quote` endpoint. Composer handles swap, bridge, and deposit in a single transaction.
4. **Track.** Use the Earn Data API's portfolio endpoint to show the user's positions and returns across vaults.
***
## Who Is Earn For?
Build a yield marketplace in your app. Show users the best available yields
across protocols, let them compare options, and enable one-click deposits
via Composer. Use the portfolio endpoint to display their active positions
and returns.
Aggregate a user's DeFi positions across protocols without integrating each
one individually. The Earn Data API normalizes position data into a
consistent format.
Use the Earn Data API as your vault data layer. Filter by chain, asset, or
protocol, sort by yield or TVL, and use Composer for execution.
***
## Try It Out
List the top USDC vaults on Base sorted by APY:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults?chainId=8453&asset=USDC&sortBy=apy&limit=5'
```
```ts TypeScript theme={"system"}
const response = await fetch(
'https://earn.li.fi/v1/vaults?chainId=8453&asset=USDC&sortBy=apy&limit=5'
);
const { data, total } = await response.json();
data.forEach((vault) => {
console.log(`${vault.name}: ${(vault.analytics.apy.total * 100).toFixed(2)}% APY`);
});
```
See the [Quickstart](/earn/quickstart) for the full step-by-step walkthrough including vault selection, deposits, and position tracking.
***
## Current Limitations
* **Data coverage.** Vault data reflects what LI.FI's sync pipeline has indexed. Not all vaults from every supported protocol are guaranteed to appear.
* **Deposits only via Composer.** The Earn Data API handles discovery and tracking only. Onchain execution requires the separate [Composer API](/composer/overview).
***
## Next Steps
Make your first API calls in under 5 minutes
Understand the data pipeline, NormalizedVault schema, and how data stays
fresh
Full endpoint reference with parameters and response examples
End-to-end recipe: find top vaults, then deposit via Composer
# Quickstart
Source: https://docs.li.fi/earn/quickstart
Get started with LI.FI Earn in under 5 minutes. List vaults, get vault details, check user positions, and deposit via Composer.
This quickstart walks you through the core Earn flows: discovering vaults via the Earn Data API, checking a user's positions, and then executing a deposit via Composer.
## Prerequisites
* **curl** or **Node.js 18+** (for `fetch`)
* An **API key** from the [LI.FI Partner Portal](https://li.fi/plans/), passed via the `x-lifi-api-key` header
* No wallet is needed for data calls (steps 1 to 4). A wallet is only required for deposits (step 5).
***
## 1. List Available Vaults
Fetch vaults sorted by APY, filtered to a specific chain:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults?chainId=8453&sortBy=apy&limit=5' \
--header 'x-lifi-api-key: YOUR_API_KEY'
```
```ts TypeScript theme={"system"}
const response = await fetch(
'https://earn.li.fi/v1/vaults?chainId=8453&sortBy=apy&limit=5',
{ headers: { 'x-lifi-api-key': 'YOUR_API_KEY' } }
);
const { data, nextCursor, total } = await response.json();
console.log(`Found ${total} vaults on Base`);
data.forEach((vault) => {
console.log(`${vault.name}: ${(vault.analytics.apy.total * 100).toFixed(2)}% APY`);
});
```
The response includes an array of [NormalizedVault](/earn/how-it-works#the-normalizedvault-schema) objects with full metadata, analytics, and transactional capabilities.
***
## 2. Get a Single Vault
Fetch full details for a specific vault by chain ID and contract address:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults/8453/0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A' \
--header 'x-lifi-api-key: YOUR_API_KEY'
```
```ts TypeScript theme={"system"}
const response = await fetch(
'https://earn.li.fi/v1/vaults/8453/0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A'
);
const vault = await response.json();
console.log(`${vault.name} on ${vault.network}`);
console.log(`APY: ${(vault.analytics.apy.total * 100).toFixed(2)}%`);
console.log(`TVL: $${vault.analytics.tvl.usd}`);
console.log(`Depositable: ${vault.isTransactional}`);
```
Use `GET /v1/chains` and `GET /v1/protocols` to build dynamic filter UIs showing only the chains and protocols that currently have vaults. Both endpoints return lightweight lists and can be fetched once per session.
***
## 3. Check a User's Positions
Look up a user's DeFi positions across all supported protocols:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/portfolio/0xYOUR_WALLET_ADDRESS/positions' \
--header 'x-lifi-api-key: YOUR_API_KEY'
```
```ts TypeScript theme={"system"}
const response = await fetch(
'https://earn.li.fi/v1/portfolio/0xYOUR_WALLET_ADDRESS/positions'
);
const { positions } = await response.json();
positions.forEach((pos) => {
console.log(`${pos.asset.symbol} on ${pos.protocolName}: $${pos.balanceUsd}`);
});
```
Example response:
```json theme={"system"}
{
"positions": [
{
"chainId": 1,
"address": "0xa17581a9e3356d9a858b789d68b4d866e593ae94",
"protocolName": "aave-v3",
"asset": {
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"name": "USD Coin",
"symbol": "USDC",
"decimals": 6
},
"balanceUsd": "1523.45",
"balanceNative": "1523450000"
}
]
}
```
***
## 4. Deposit via Composer
Once you've found a vault, use its contract address as the `toToken` with [Composer](/composer/overview) to execute a deposit. Composer handles the swap, bridge (if cross-chain), and deposit in a single transaction.
```bash curl theme={"system"}
# Deposit 1 USDC into a Morpho vault on Base
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000'
```
```ts TypeScript theme={"system"}
// Use the vault's contract address as toToken
const vault = data[0]; // from step 1
const quote = await fetch(
`https://li.quest/v1/quote?` +
`fromChain=${vault.chainId}` +
`&toChain=${vault.chainId}` +
`&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` + // USDC on Base
`&toToken=${vault.address}` +
`&fromAddress=0xYOUR_WALLET_ADDRESS` +
`&toAddress=0xYOUR_WALLET_ADDRESS` +
`&fromAmount=1000000` // 1 USDC
).then((r) => r.json());
// quote.transactionRequest is ready to sign and send
console.log('Transaction:', quote.transactionRequest);
```
The deposit step uses **Composer** (`li.quest`), not the Earn Data API. See the [Composer API Integration Guide](/composer/guides/api-integration) for full details on executing transactions.
***
## Next Steps
Full endpoint reference with all parameters and response examples
End-to-end recipe: find the best vault for a token, then deposit
# Discover and Deposit
Source: https://docs.li.fi/earn/recipes/discover-and-deposit
End-to-end recipe: find the best yield vaults for a token using the Earn Data API, then deposit via Composer in a single transaction.
This recipe walks through a complete LI.FI Earn integration: use the Earn Data API to find the highest-yielding vaults for a specific token, display them to a user, and then execute a deposit via Composer.
**Two layers, one flow.** This recipe uses the **Earn Data API** for discovery and **Composer** for execution. The Earn Data API tells you *where* to deposit. Composer handles *how*.
***
## Step 1: Find the Best USDC Vaults on Base
Query the Earn Data API for USDC vaults on Base, sorted by APY:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/vaults?chainId=8453&asset=USDC&sortBy=apy&minTvlUsd=100000&limit=5'
```
```ts TypeScript theme={"system"}
const discoverVaults = async (chainId: number, asset: string) => {
const params = new URLSearchParams({
chainId: String(chainId),
asset,
sortBy: 'apy',
minTvlUsd: '100000', // Only vaults with $100k+ TVL
limit: '5',
});
const response = await fetch(`https://earn.li.fi/v1/vaults?${params}`);
const { data } = await response.json();
return data;
};
const vaults = await discoverVaults(8453, 'USDC');
```
***
## Step 2: Filter for Depositable Vaults
Not all vaults support deposits via Composer. Filter using the `isTransactional` flag:
```ts TypeScript theme={"system"}
const depositableVaults = vaults.filter((vault) => vault.isTransactional);
// Display to user
depositableVaults.forEach((vault) => {
console.log(`${vault.name} (${vault.protocol.name})`);
console.log(` APY: ${(vault.analytics.apy.total * 100).toFixed(2)}%`);
console.log(` TVL: $${Number(vault.analytics.tvl.usd).toLocaleString()}`);
console.log(` 30d avg APY: ${(vault.analytics.apy30d * 100).toFixed(2)}%`);
console.log(` Deposit token: ${vault.address}`);
});
```
Use `apy30d` alongside `apy.total` to give users a sense of yield stability. A vault with a high current APY but low 30-day average may be experiencing a temporary spike.
***
## Step 3: Get a Composer Quote
When the user selects a vault, use its contract address as the `toToken` in a Composer quote request. This is the handoff from Earn to Composer.
```bash curl theme={"system"}
# User selected the first vault, deposit 100 USDC
curl -X GET 'https://li.quest/v1/quote?fromChain=8453&toChain=8453&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=100000000'
```
```ts TypeScript theme={"system"}
const depositIntoVault = async (
vault: any,
fromToken: string,
fromAmount: string,
userAddress: string
) => {
const params = new URLSearchParams({
fromChain: String(vault.chainId),
toChain: String(vault.chainId),
fromToken,
toToken: vault.address, // Vault contract address triggers Composer deposit
fromAddress: userAddress,
toAddress: userAddress,
fromAmount,
});
const response = await fetch(`https://li.quest/v1/quote?${params}`);
return response.json();
};
// Deposit 100 USDC into the top vault
const selectedVault = depositableVaults[0];
const quote = await depositIntoVault(
selectedVault,
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
'100000000', // 100 USDC (6 decimals)
'0xYOUR_WALLET_ADDRESS'
);
console.log('Estimated output:', quote.estimate);
console.log('Transaction to sign:', quote.transactionRequest);
```
The Composer quote endpoint is `https://li.quest/v1/quote`. This is the Composer layer of LI.FI Earn, not the Earn Data API. See the [Composer API Integration Guide](/composer/guides/api-integration) for full details on parameters, token approvals, and transaction submission.
***
## Step 4: Execute the Transaction
Submit the transaction from the quote response. See the [Composer API Integration Guide](/composer/guides/api-integration#send-the-transaction) for the full execution flow including token approvals and status tracking.
```ts TypeScript theme={"system"}
// Using ethers.js v6
import { ethers } from 'ethers';
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// Send the transaction from the Composer quote
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Transaction sent:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
```
***
## Step 5: Verify the Position
After the deposit confirms, use the Earn portfolio endpoint to verify the user's new position:
```bash curl theme={"system"}
curl -X GET 'https://earn.li.fi/v1/portfolio/0xYOUR_WALLET_ADDRESS/positions'
```
```ts TypeScript theme={"system"}
const response = await fetch(
`https://earn.li.fi/v1/portfolio/0xYOUR_WALLET_ADDRESS/positions`
);
const { positions } = await response.json();
// Find the position in the vault we just deposited into
const newPosition = positions.find(
(pos) => pos.address?.toLowerCase() === selectedVault.address.toLowerCase()
);
if (newPosition) {
const usd = newPosition.balanceUsd ?? 'N/A';
const protocol = newPosition.protocolName ?? 'unknown';
console.log(`Position: $${usd} in ${protocol}`);
}
```
***
## Cross-Chain Deposits
Vaults are chain-specific, but Composer handles cross-chain deposits seamlessly. To deposit from a different chain, change `fromChain` and `fromToken` in the quote request:
```ts TypeScript theme={"system"}
// Deposit ETH from Ethereum into a USDC vault on Base
// Note: fromChain differs from vault's chain. Composer handles the bridge automatically.
const params = new URLSearchParams({
fromChain: '1', // Ethereum (where the user's funds are)
toChain: '8453', // Base (where the vault lives)
fromToken: '0x0000000000000000000000000000000000000000', // ETH (native)
toToken: selectedVault.address, // Vault contract address on Base
fromAddress: '0xYOUR_WALLET_ADDRESS',
toAddress: '0xYOUR_WALLET_ADDRESS',
fromAmount: '100000000000000000', // 0.1 ETH
});
const response = await fetch(`https://li.quest/v1/quote?${params}`);
const crossChainQuote = await response.json();
// Composer routes: bridge ETH to Base, swap to USDC, deposit into vault, all in one transaction
```
Composer's routing engine automatically determines the optimal bridge and swap path. See [Cross-Chain Compose](/composer/guides/cross-chain-compose) for details.
***
## Full Example
Here is the complete flow in a single function:
```ts TypeScript theme={"system"}
const discoverAndDeposit = async (
chainId: number,
asset: string,
fromToken: string,
fromAmount: string,
userAddress: string
) => {
// 1. Discover vaults
const vaultParams = new URLSearchParams({
chainId: String(chainId),
asset,
sortBy: 'apy',
minTvlUsd: '100000',
limit: '5',
});
const { data: vaults } = await fetch(
`https://earn.li.fi/v1/vaults?${vaultParams}`
).then((r) => r.json());
// 2. Filter for depositable vaults
const depositable = vaults.filter((v) => v.isTransactional);
if (depositable.length === 0) throw new Error('No depositable vaults found');
// 3. Pick the highest-APY vault
const bestVault = depositable[0];
console.log(
`Best vault: ${bestVault.name} at ${(bestVault.analytics.apy.total * 100).toFixed(2)}% APY`
);
// 4. Get Composer quote
const quoteParams = new URLSearchParams({
fromChain: String(chainId),
toChain: String(chainId),
fromToken,
toToken: bestVault.address,
fromAddress: userAddress,
toAddress: userAddress,
fromAmount,
});
const quote = await fetch(
`https://li.quest/v1/quote?${quoteParams}`
).then((r) => r.json());
return { vault: bestVault, quote };
};
// Usage
const { vault, quote } = await discoverAndDeposit(
8453,
'USDC',
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
'100000000', // 100 USDC
'0xYOUR_WALLET_ADDRESS'
);
```
***
## Related
Full endpoint reference with all parameters
More vault deposit recipes with protocol-specific examples
Full Composer integration guide including approvals and status tracking
Cross-chain deposit patterns and bridge selection
# Fees and Monetization
Source: https://docs.li.fi/faqs/fees-monetization
LI.FI charges a 0.25% service fee on each transaction.
You can add your own fee by including a fee percentage and an integrator string during setup.
LI.FI also offers volume-based commercial discounts.
Fee structures vary by bridge or DEX. Some use a percentage-based model, others charge a flat fee.
The API provides detailed fee data in the feeCosts object when you request quotes or routes.
Create an account at LI.FI Portal.
Then, pass the integrator string and fee parameter as described in our
[fee setup guide](/introduction/integrating-lifi/monetizing-integration).
On all supported chains, fees are sent directly to your configured wallet at execution time. No withdrawal is needed for new transactions.
If you have **legacy fees** on EVM chains that were collected before the [FeeForwarder upgrade](/introduction/integrating-lifi/fee-forwarder), you can withdraw them from the LI.FI Portal or via the API:
```http theme={"system"}
GET /v1/integrators/{integratorId}/withdraw/{chainId}
```
See the [FeeForwarder documentation](/introduction/integrating-lifi/fee-forwarder#withdrawing-previously-collected-fees) for details.
No. Legacy fees in the FeeCollector contract can only be withdrawn by the wallet that was designated for fee collection when those fees were accrued. If you've updated your wallet, use the original one to claim older balances. See [Withdrawing previously collected fees](/introduction/integrating-lifi/fee-forwarder#withdrawing-previously-collected-fees) for instructions.
No. Fees can be collected in different tokens:
* Some bridge fees (e.g., Stargate, Arbitrum) are in the native asset.
* Gas fees are also in the native asset.
* Other fees are usually deducted from the sending asset.
* integrator: Identifies your integration for analytics and tracking.
* referrer: Optional on-chain string for your internal tracking.
* fee: Decimal percentage deducted from the sending asset as your commission.
Fees are taken from the sending token (the `fromToken` in the quote) and sent directly to your configured fee wallet at execution time on all supported chains. Custom fee models are available if required.
# Integration
Source: https://docs.li.fi/faqs/integration
Set the following options:
* allowSwitchChain: false to prevent automatic chain switching
* allowDestinationCall: true to allow the bridge to complete the swap on the destination chain
Yes. LI.FI emits on-chain events for all transfers and includes the integrator parameter.
You can filter by this parameter to identify swaps from your client.
No. The /quote/contractCalls endpoint does not support sending native ETH.
Yes. The quote includes an estimated gas amount and transaction data.
Add ?skipSimulation=true to the request. This skips on-chain simulation and improves response time.
Include the fromAmountGas parameter in your request. This is required for gaszip to function.
* On EVM: A balance check is required to generate transaction data.
* On Bitcoin and Sui: A balance check is also performed during transaction generation.
If order is not set, the default is CHEAPEST.
Use the LI.FI status endpoint. It reports full or partial completion, the token and amount delivered, and the destination chain.
Timing is configurable per request. Use a short target for fast flow, or wait longer on larger trades to collect more provider responses. A common pattern is \~400ms minimum for fast traders and \~1s with at least four results for larger trades.
Yes. LI.FI aggregates multiple providers, including Relay and Jupiter, giving unified fee collection, tracking, and routing without duplicate integrations.
Use the quote endpoint when you want the best single option with ready-to-execute transaction data. Use the routes endpoint when you need to present multiple choices before fetching step data. You can also filter to simpler, single-transaction flows for mainstream UX.
Yes. LI.FI can fund on one chain and complete an NFT purchase on another by passing call data; some marketplaces may need allowlisting or extra integration steps.
# Route availability
Source: https://docs.li.fi/faqs/route-availability
This slippage error appears when the expected return amount is lower than the minimum accepted threshold.
It often happens due to market volatility or low liquidity.
This error indicates a price change between the quote and execution.
To resolve it, increase the slippage tolerance. Be cautious—higher slippage can result in worse rates.
If you're using the SDK, implement acceptExchangeRateUpdateHook and return true to accept updated rates automatically.
Refer to the SDK docs: [acceptExchangeRateUpdateHook](/sdk/execute-routes#acceptexchangerateupdatehook)
Some versions of Mayan (mayanWH and mayanMCTP) do not support smart contract addresses as destinations.
Mayan MCTP uses CCTP, which has slower settlement times and higher gas costs on Ethereum.
It is generally used for large transfers when other bridges lack liquidity.
This usually means the fromAddress does not have enough funds to cover the transaction.
This message appears when the transaction has not yet been indexed by LI.FI.
Once it is indexed, the scanner will display its details.
This code likely represents a custom error from the DEX, such as MinimalOutputBalanceViolation.
Most swap failures come from DEX-level checks. If not, LI.FI applies its own fallback checks.
Error codes may vary by provider.
Refund policies depend on the bridge. Some bridges issue refunds automatically; others may leave transactions unresolved.
This substatus usually comes from cBridge. It means the transfer could not be processed due to price, gas, or liquidity issues.
LI.FI will then trigger a refund on the source chain.
PARTIAL is a final status. The user receives the full value, but in a different token than expected.
LI.FI blocks quotes if the wallet has no SOL. This prevents failures from rent and gas fees.
To fetch rates without requiring a funded wallet, use the /advanced/routes endpoint without specifying a wallet address.
An intent states the desired outcome (e.g., "Swap 1 ETH on Ethereum into at least X USDC on Base"). LI.FI can pass intents to a solver marketplace where solvers compete to fulfill them. Routing may present intent-based execution alongside classic DEX aggregation and bridge routes; integrators see intents as another path under the same fee model.
# Slippage and price impact
Source: https://docs.li.fi/faqs/slippage-price-impact
toAmount is the estimated post-swap amount. toAmountMin is the minimum amount based on your slippage setting.
A lower toAmount can result from price impact or market fluctuations.
To filter out routes with high price impact, use the maxPriceImpact parameter.
* Slippage: Difference between toAmount and toAmountMin
* Price impact: Difference between the original quote and toAmount
Our default slippage, when no slippage is set, is 0.5% per step (0.005 on the API). Slippage is expressed as a decimal from 0 to 1, where 1 = 100%.
For example, a route that includes swap + bridge + swap can result in 1.5% total slippage in that sense, but there are some exceptions:
* Some exchanges, such as those on Solana, may suggest their own slippage if none is set.
* Some bridges, such as cBridge for small amounts, may enforce their own slippage to help ensure successful transaction execution. In those cases, we pass on that slippage.
The API accepts manually set slippage of up to 1 or 100%, expressed as a decimal.
We recommend not tracking the slippage set by the user directly, but instead tracking the toAmountMin from our quotes. This is the value we promise the user after slippage. If they receive less than this amount, something is likely off. We also track this on our side so we can investigate transactions that users report.
No. Increasing slippage tolerance does not prevent rate changes. It only widens the acceptable range before failing the transaction.
Use 0.5%. Slippage is typically minimal unless the trade size is very small or very large.
It depends on the token, chain, and amount. There is no universal value.
To reduce failures:
* Execute the quote immediately
* For Solana same-chain swaps, omit the slippage parameter to let LI.FI auto-calculate
LI.FI calculates price impact using the difference in USD value between input and output tokens.
Price impact filtering does not apply to trades under \$10.
* Execute the quote immediately
* Refresh the quote if delayed
* Increase the allowed slippage in your API request
Yes. A minimum received amount is enforced when the user signs; execution must deliver at least that minimum or it reverts. Slippage exists because prices/liquidity move between quote and execution. Auto slippage picks a suitable buffer by asset liquidity (lower for stablecoins, higher for long-tail assets), and LI.FI commonly defaults to \~0.5% with the minimum slightly below the estimate to absorb normal movement.
# Token support and availability
Source: https://docs.li.fi/faqs/token-support
The /tokens endpoint uses a verified list, updated daily from sources like Debank and CoinMarketCap.
This ensures only trustworthy tokens are returned by default.
To include a custom token, submit a pull request to:
lifinance/customized-token-list
LI.FI supports all tokens, including new ones, through two methods:
* If requested by token address, LI.FI searches on-chain and finds available liquidity.
* If listed in the default /tokens endpoint, the token becomes searchable by name on jumper.exchange.
To avoid filtering new tokens due to missing USD prices, set:
```ts theme={"system"}
sdkConfig.routeOptions.maxPriceImpact = 1
```
For best results, notify LI.FI in advance of token launches.
Not currently. The Jupiter API sends tokens to the same address used to initiate the swap.
To include tokens in your own integration, use the widget's tokens option.
See the widget documentation for details.
To request a token listing across LI.FI's API and Jumper globally, submit a pull request to:
lifinance/customized-token-list
* Token coverage: All tokens supported by Jupiter are available.
* New token listings: Tokens added by Jupiter are typically available within one day.
* Fee collection: Use the same fee parameter as for EVM chains. Fees are sent directly to your wallet.
LI.FI ships curated token lists from supported exchanges and bridges with spam filtering. Users can still paste any token address, but defaults avoid promoting spam tokens.
# Common issues
Source: https://docs.li.fi/faqs/troubleshooting
This error indicates that the expected return amount is below the minimum accepted threshold.
It typically occurs due to market volatility or low liquidity.
This error occurs when the exchange rate changes between quoting and execution.
You can increase the slippage tolerance to reduce failures, but be aware that it may result in a worse rate.
For SDK users, implement acceptExchangeRateUpdateHook and return true to automatically accept updated rates.
See [SDK documentation](/sdk/execute-routes#acceptexchangerateupdatehook)
This error means the bridge version you used—mayanWH or mayanMCTP—does not support smart contracts as destination addresses.
Mayan MCTP runs on CCTP, which is slower and incurs higher gas costs on Ethereum mainnet.
This method is typically used for large transfers when other bridges lack liquidity.
This error often means the fromAddress does not have enough balance to cover the transaction.
This error appears when the transaction has not yet been indexed by LI.FI.
Once it is indexed, the scanner will display the transaction details.
For EIP-7702 delegated smart wallets, such as delegated MetaMask accounts, gasless or relayer routes are currently not offered, even when useRelayerRoutes: true is enabled.
In this setup, the Widget may still show regular routes. These routes require the account to hold enough native gas on the source chain, and execution can fail if the account does not have enough gas.
This behavior is intentional for now because EIP-7702 delegated accounts require a specific signature type, and a generic relayer integration would not be compatible with all smart wallet implementations.
If your users use EIP-7702 delegated wallets, make sure they hold enough source-chain gas before executing routes. Gasless support for this wallet type is not yet available.
This is likely a custom error from the DEX, such as MinimalOutputBalanceViolation.
Most swap errors originate at the DEX level. If not, LI.FI has fallback validation logic.
Error selectors vary depending on the DEX.
Refund eligibility depends on the bridge or tool used.
Some bridges refund automatically after failure. Others may leave the transaction unresolved.
This substatus is triggered by cBridge.
It indicates the transfer could not be processed due to price, gas, or liquidity changes.
LI.FI then initiates a refund on the source chain.
PARTIAL is a final status. The full value was received, but in a different token than expected.
Same-chain swaps are atomic: they either succeed or revert (gas may still be spent). In cross-chain routes, the bridge can succeed while the destination swap fails; the user is then automatically refunded on the destination chain, typically in the bridged asset. If a destination swap fails, prompt a fresh quote and approval to finish the swap on the destination chain.
# Debugging Failed Transactions
Source: https://docs.li.fi/guides/debug-failed-transactions
Learn how to diagnose and fix reverted on-chain transactions using Blocksec Explorer and Tenderly
Learn how to debug reverted transactions using trace analysis tools. This guide covers common failure patterns and their fixes based on real production cases.
## Prerequisites
Before you begin, you need:
* The **transaction hash** of the failed transaction
* Access to a trace analysis tool
View detailed call traces and analyze transactions
Simulate and debug transactions step-by-step
***
## Core Debugging Workflow
Follow these steps to diagnose most transaction failures.
Open the transaction in Blocksec or Tenderly and identify:
* The **top-level revert reason** (if available)
* The **first revert** in the call tree (this is often the actual root cause)
* The **contract or call before the revert** (this is the failing action)
Trace tools sometimes show multiple errors. When the UI summary conflicts with the raw trace, trust the raw trace.
Most reverts fall into these categories:
| Category | Common Indicators |
| ---------------------- | --------------------------------------------------------- |
| Out of gas | Trace ends abruptly, OOG error |
| Insufficient allowance | `transfer amount exceeds allowance` |
| Insufficient balance | `insufficient balance`, `transfer amount exceeds balance` |
| Missing msg.value | `insufficient balance for transfer` on native token |
| Slippage exceeded | Min output checks fail |
| Oracle issues | `StalePrice`, bad feed errors |
| Deadline expired | `EXPIRED`, `Transaction too old` |
| Token restrictions | Transfer blocked, tax fees, blacklist |
Once you identify the category, the fix becomes clear.
Aggregator and router failures often stem from input mismatches:
* `gasLimit` on-chain vs `gasLimit` from `/quote` or `/stepTransaction`
* Approval amount vs actual spend amount
* `msg.value` sent vs `msg.value` required
* `minAmountOut` vs actual output at execution
* Token address or price feed ID errors
Simulate the failed call with identical calldata and state. Then change one variable at a time:
* Increase `gasLimit`
* Add or adjust `msg.value`
* Relax slippage parameters
If changing one variable makes the simulation succeed, you have found the root cause.
***
## Common Failure Modes
### Symptom
The trace ends abruptly or shows an out-of-gas (OOG) error.
### How to Confirm
1. Check the transaction's `gasLimit`
2. Look for OOG in the revert reason
3. Re-simulate with a higher `gasLimit` (use the value from the quote API)
### Root Cause
The transaction used a lower `gasLimit` than the quote recommended. Wallets or relayers sometimes override gas settings.
### Fix
* Use the **exact `gasLimit`** from the quote response
* Add a 10–20% safety buffer if your product allows it
* Verify that wallets and relayers do not clamp gas values
### Example
The API returned sufficient `gasLimit`, but the on-chain transaction reverted. Re-simulating with the correct `gasLimit` succeeded.
* [Failed transaction](https://app.blocksec.com/explorer/tx/base/0xdb8adb7526ab1372f3e252ddc128c3e039bd29bb91567f5f3821b7c665b16dd9?line=302)
* [Successful simulation](https://app.blocksec.com/explorer/tx/base/0x9c0d91d0f2de0250b75f59276b879b5309f3f4d6eaa688b943fd11c39c418f45?event=simulation\×tamp=1767602633035\&type=0)
### Symptom
Revert message: `transfer amount exceeds allowance`
### How to Confirm
1. Find the `transferFrom(...)` call in the trace
2. Identify which spender is pulling funds
3. Check `allowance(owner, spender)` at the execution block
### Root Cause
The user either did not approve, approved the wrong spender, or approved less than the required amount. A previous transaction may have consumed the allowance.
### Fix
* Prompt an approval for the correct token and spender
* Consider infinite approval vs exact approval tradeoffs
### Example
[Transaction reverted due to insufficient allowance](https://app.blocksec.com/explorer/tx/base/0xa24e8a320ed5c7e9b5daa5d6c5e0616b0b4789cc6e68b2872b24970d0aca733c?line=11)
### Symptom
Errors such as:
* `insufficient balance for transfer`
* Native value forwarding fails
### How to Confirm
1. Check the transaction's `msg.value` in Tenderly
2. Find where the router forwards ETH in the trace
3. Look for balance failures at that point
### Root Cause
The route requires native tokens (for fees, bridged gas, or protocol payments), but the transaction sent `value=0`.
### Fix
* Include the `msg.value` returned by the API
* Verify that relayers and wallets do not drop the value field
### Example
The transaction failed because `msg.value` was missing despite the API returning a non-zero value.
[View in Tenderly](https://dashboard.tenderly.co/shared/simulation/b444cc7e-5deb-4ac6-a3f4-fdeb8fe7eded/debugger?trace=0.1.2)
### Symptom
Revert message: `PythErrors.StalePrice()`
### How to Confirm
Look for `getPriceNoOlderThan(..., age=N)` reverting in the trace.
### Root Cause
The oracle data is too old, or the pool uses an incorrect feed ID that is not being updated.
### Fix
Contact [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) to report the issue.
### Example
`PythErrors.StalePrice()` occurred because a pool used an incorrect feed ID.
[View in Tenderly](https://dashboard.tenderly.co/shared/simulation/05f5aaf2-3946-42ed-b9f6-45fd40b78e2d/debugger?trace=0.4.3.0.2.0.0.1.6.7.1.5.0.1.1.2.2.0.1.0.2)
### Symptom
Revert message: `insufficient balance` or `transfer amount exceeds balance`
### How to Confirm
1. Find the token transfer in the trace
2. Identify the "from" address
3. Check `balanceOf(from)` at execution time
### Root Cause
The user's balance changed between quote and execution. Fee-on-transfer or rebasing tokens can cause unexpected balance changes.
### Fix
* Re-quote and verify balances immediately before sending
* For fee-on-transfer tokens, contact LI.FI support to add the token to the deny list
### Example
[Transaction reverted due to insufficient balance](https://app.blocksec.com/phalcon/explorer/tx/eth/0xefb4dc79dc72b1df1d13edd2a4727594b1a1a8aff3c5ca6792a0ce1068f87d88?line=6)
### Symptom
Revert related to minimum output or slippage checks (exact message varies by DEX).
### How to Confirm
Find the swap step and compare:
* `amountOutMin` or `minReturnAmount`
* Actual computed `amountOut`
If `amountOut < minReturnAmount`, the transaction reverts.
### Root Cause
Price movement, MEV extraction, volatile pools, low liquidity, or stale quotes.
### Fix
* Re-quote and execute within 60–90 seconds of quoting (most common cause is a stale quote)
* If fresh quotes still fail, increase slippage tolerance for **diagnostic purposes only** (15–25% for volatile or new tokens)
* For unavoidable high slippage, use a private or MEV-protected RPC endpoint
Do not use high slippage values as a production default. This creates a wide window for [sandwich attacks](https://ethereum.org/en/developers/docs/mev/#mev-examples-sandwich-trading) where MEV bots can extract value from users. Only use these values for one-off debugging.
### Example
* [Slippage failure (Tenderly)](https://www.tdly.co/tx/0xd695011e11cdf38e92edf5c872a1bab4f011e90a493b5696e2a0092460268ccc)
* [Slippage failure (Blocksec)](https://app.blocksec.com/phalcon/explorer/tx/eth/0x9b613a583daa4a6a46e4bacbe13bb571d4a7e897a5b0b0ca44b3260856fc2ef4?line=2)
### Symptom
The final swap step fails with `SafeERC20: low-level call failed`.
### How to Confirm
In the trace, look for:
1. A `transferFrom` call that reverts
2. A nested external call that fails (often to a tax wallet or fee handler)
3. The error bubbling up as `SafeERC20: low-level call failed`
### Root Cause
Some tokens have custom transfer logic that fails under certain conditions. For example, tokens with sell taxes may call external contracts that reject ETH or revert unexpectedly.
### Fix
* Treat the token as incompatible for sell routes
* Report to [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) for addition to the token deny list
Use token risk scanners like [honeypot.is](https://honeypot.is) or [GoPlus](https://gopluslabs.io/) to check for honeypot flags, sell restrictions, or abnormal taxes.
### Example
The router failed to transfer tokens because the token's tax wallet rejected ETH.
[View in Blocksec](https://app.blocksec.com/phalcon/explorer/tx/eth/0xca85cc530034c4fec9b0ee6831607a947fa8032e7919881d5fd3101c1bd5cd92?line=75)
### Symptom
Revert inside pool math, such as Curve's `get_D` failing to converge after 255 iterations.
### How to Confirm
The trace shows a revert in pool math functions (`get_D`, invariant computation) with a `raise` statement.
### Root Cause
The pool is broken, often due to a compromised token causing extreme imbalance or invalid state.
### Fix
Contact [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) to blacklist the pool.
### Example
A Curve pool's `get_D` function failed to converge. The Curve team confirmed the pool was broken.
[View in Tenderly](https://dashboard.tenderly.co/tx/0x18505dfe414ab0e347d4d174217737c41d5b84db3bd6a6af3af6f0cff37b3367?trace=0.1.1.9.2.0.5.0.9.1.0)
### Symptom
Revert message: `UniswapV2: LOCKED`
### How to Confirm
In the trace:
1. The Uniswap V2 pair transfers a token
2. The token's transfer logic calls back into the same pair
3. The pair's reentrancy lock triggers
### Root Cause
A token with callback or fee-swap logic re-enters the pair during transfer. This often happens with misconfigured fee-on-transfer tokens.
### Fix
Contact [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) to blacklist the pool.
### Example
Swaps involving WeightedIndex reverted with `UniswapV2: LOCKED`. KyberSwap blacklisted the affected pool.
[View in Tenderly](https://dashboard.tenderly.co/shared/simulation/c5b100d0-067d-483a-9184-0880f709a382/debugger?trace=0.4.2.0.2.2.0.1.6.7.1.5.3.5.0.0.0.1.2.1.7.3.3.6.1)
### Symptom
Revert due to insufficient balance mid-route, even though the user had enough tokens initially.
### How to Confirm
The trace shows:
1. An earlier hop receives less tokens than expected
2. A later hop tries to transfer the originally calculated amount
3. The balance check fails
### Root Cause
Fee-on-transfer (FoT) or deflationary tokens take a fee during transfer. The route assumes exact amounts, but the actual received amount is less than calculated.
### Fix
Contact [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) to add the token to the deny list.
### Example
fBOMB failed with `Max20: insufficient balance` because its deflationary mechanism broke the route's exact calculations.
* [View in Tenderly](https://dashboard.tenderly.co/shared/simulation/0cb72ac2-1565-4d10-9919-11b584f07f4f?trace=0.4.2.0.5.1.0.1.2)
* [View token code](https://berascan.com/address/0xfab311fe3e3be4bb3fed77257ee294fb22fa888b#code#F1#L133)
### Symptom
Revert caused by a pool-level reentrancy lock, especially with fee-on-transfer tokens.
### How to Confirm
The trace shows a revert at the pool with reentrancy guard behavior, similar to "LOCKED" but protocol-specific.
### Root Cause
Newer DEXs add reentrancy protections that conflict with fee-on-transfer or callback tokens.
### Fix
Contact [LI.FI support](https://lifihelp.zendesk.com/hc/en-us) to blacklist the pool.
### Example
Kodiak v2 pools added reentrancy locks that caused fee-on-transfer tokens to revert.
[View in Tenderly](https://dashboard.tenderly.co/shared/simulation/8671ce2c-c34e-436d-8496-3bce168dea08?trace=0.4.2.0.2.8.0.1.6.7.1.7.3.5.0.0.1.2.1.7.3.3.6.1)
### Symptom
Revert messages such as:
* `Transaction too old`
* `EXPIRED`
* `SwapRouter: EXPIRED`
### How to Confirm
1. Find the encoded deadline in the calldata (usually a 32-byte word)
2. Convert the hex value to Unix time
3. Compare to the block timestamp when the transaction was mined
If `block_timestamp > deadline`, the router rejects the transaction.
**Example calculation:**
* Deadline: `0x6951e2e0` = 1,766,974,176
* Block timestamp: 1,766,975,579
* Delta: 1,403 seconds (\~23 minutes late)
[View VM trace](https://etherscan.io/vmtrace?txhash=0x15735cdc5b7576ff6059a7337c8d48767a56210ac88cce26f6a8d470d2c791b1\&type=gethtrace2)
### Root Cause
The transaction was submitted too late due to slow signing, mempool congestion, low gas price, or relayer delays.
### Fix
* Get a fresh quote and submit immediately
* Increase gas price for stuck transactions
* Do not reuse old calldata
Late execution often means the market has moved. Even without an explicit deadline revert, stale quotes frequently fail due to slippage. Always re-quote.
***
## Quick Decision Checklist
Follow these steps when debugging a failed transaction:
Look at the call tree, not just the UI headline. The first revert is usually the root cause.
| Check | Action |
| -------------- | ------------------------------------------- |
| Out of gas? | Compare submitted vs recommended `gasLimit` |
| Allowance? | Check `allowance(owner, spender)` |
| Balance? | Check `balanceOf` at execution block |
| Missing value? | Compare transaction value vs required value |
| Slippage? | Compare `minOut` vs actual output |
| Oracle? | Look for stale price or wrong feed ID |
| Token issue? | Check for transfer reverts or false returns |
Use Tenderly or Blocksec to reproduce the failure with identical parameters.
Modify one variable at a time (gas, value, approval, slippage) until the simulation succeeds.
***
## Quick Reference
| Failure | Key Indicator | Fix |
| ----------------- | ----------------------------------- | ------------------------------------------------- |
| Out of gas | Trace ends abruptly, OOG error | Use `gasLimit` from quote |
| Allowance | `transfer amount exceeds allowance` | Approve correct spender |
| Balance | `insufficient balance` | Re-quote and verify balance |
| Missing value | Native transfer fails | Include `msg.value` from API |
| Slippage | Min output check fails | Re-quote first, then cautiously increase slippage |
| Stale oracle | `StalePrice` errors | Report to LI.FI support |
| Token restriction | `SafeERC20: low-level call failed` | Report token for deny list |
| Broken pool | Math convergence fails | Report pool for blacklist |
| Reentrancy | `LOCKED` errors | Report pool for blacklist |
| Deadline expired | `EXPIRED` | Re-quote and execute quickly |
***
## Related Resources
API error code reference
Frequently asked troubleshooting questions
Track transaction status
Understand slippage settings
# Gas Fronting
Source: https://docs.li.fi/guides/gas-subsidy
Providing gas for users on a new chain
## Does LI.FI support gas fronting / subsidizing gas?
Sometimes called "Refuel".
When users bridge to a new chain they haven't interacted with before, they need the chain's native asset to cover gas fees. If the bridged asset is not a native token, users face a "cold-start" problem, where they cannot perform any on-chain actions due to the lack of gas.
To address this, LI.FI has streamlined the process into a single transaction. Users can designate a portion of the asset being sent to be converted into the native asset on the destination chain, along with the desired bridged asset. This ensures they have the necessary gas to begin interacting on the new chain.
This is possible by adding the `fromAmountForGas` to a quote or routes request
LI.Fuel is not supported via /contractCalls endpoint or Composer.
# Intermediate tokens
Source: https://docs.li.fi/guides/intermediate-tokens
Intermediate tokens are fallback assets that appear when the requested destination token can't be delivered as planned. This typically happens on cross chain routes where the bridge moves a token to the destination chain, and a final swap is supposed to convert it into the requested asset. If that destination swap can't be executed safely (because of the user's slippage settings or current liquidity), we stop at the bridged token and return a `DONE/PARTIAL` status instead of forcing a bad trade or letting the transaction fail.
You might encounter an intermediate token in cases such as:
* **Destination swap failure or reversion:** For example, the price moved outside the allowed slippage range, or there wasn't enough liquidity for a new or long tail token. In this case, the user receives the bridged asset (for example USDC, axlUSDC) to keep funds safe.
* **Liquidity or routing constraints:** In some edge cases, the route may exit into a different but closely related token (like another stablecoin or a wrapped gas token) when it offers better liquidity, rather than letting the route fail completely.
In all cases, the user still ends up with value on the destination chain and can complete a follow up swap themselves, often by adjusting slippage or choosing a different route.
***
## How PARTIAL Works in LI.FI
* When you see `status: DONE` with `substatus: PARTIAL`, it indicates that the transfer has been completed from LI.FI's perspective, but the user did not receive the exact token they originally requested on the destination chain.
* In essence, LI.FI compares the expected output with the actual outcome on the destination side. For any route where a value transfer is confirmed, LI.FI performs a check similar to the following:
```javascript theme={"system"}
if isExpectedAsset and (
tool-specific isPartialValue(...) === true
OR we saw a LiFiTransferRecovered event
) -> substatus = PARTIAL
else if refunded -> substatus = REFUNDED
else -> substatus = COMPLETED
```
***
## Intermediate Tokens by Bridge
| Bridge | When we flag DONE / PARTIAL | Intermediate tokens seen |
| :---------------------- | :--------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- |
| Across | No native partials; only LiFiTransferRecovered edge cases. | Cake, DAI, ETH, USDC, USDC.e, USDT, USD₮0, USDT0, USDbC, WBTC, WETH, WLD, sUSD, ZRO, L3 |
| Chainflip | Swap completed but a refund egress also fired (value split). | BTC, ETH, FLIP, USDC |
| Mayan Fast MCTP | Temporary fallback to USDC until the last hop executes. | USDC |
| Mayan MCTP | Same fallback behavior as the fast route. | USDC |
| Relay / RelayDepository | Relay reports a refund and still sends funds elsewhere (cross-chain split). | USDC, ETH, G |
| Squid | Axelar delivers an axl\* token mismatching the requested asset. | axlUSDC |
| Stargate v2 | Pool exits with an alternate USD stable or wrapped gas token because of liquidity constraints. | ETH, USDC, USDC.e, USDCe, USDC.n, USDC.e(Stargate), USDT, USDT0, USDt, USDT(Stargate), m.USDT, WETH |
| Symbiosis | Symbiosis indicates a "transit token" still en route. | UETH, USDC, USDC.e, USDbC, WBNB, WETH, ETH, USDT |
# API latency and optimization
Source: https://docs.li.fi/guides/latency
API latency and optimization guide
## Where Latency Comes From and How LI.FI Routing Works
LI.FI's routing engine aggregates quotes from multiple bridges and decentralized exchanges (DEXes). This process involves real-time requests to external protocols and tools, including:
* Bridge and DEX aggregators
* On-chain simulations for security and execution checks
* Off-chain services that may introduce their own delays
Latency can vary depending on the number of providers queried, the responsiveness of those providers, and whether on-chain simulations are enabled.
The LI.FI routing flow includes two main components:
1. **Swap Step Resolution** – Fetches quotes from DEXes.
2. **Route Composition** – Combines swaps and bridges to construct optimal cross-chain paths.
Both steps involve third-party systems and introduce potential delays. By default, LI.FI waits a short time for responses before returning the best available result. However, integrators can configure timing strategies to better control this behavior.
***
## Optimizing Response Timing
You can optimize how quickly you receive quotes by customizing:
### Choosing Between `/quote` and `/advanced/routes`
* Use `/quote` for **faster responses**. It returns a single best route. It combines route finding and transaction generation into a single call which cuts down on client to server latency.
* Use `/advanced/routes` to **retrieve multiple route options**. Those calls are quite fast to show results to the user quickly. In order to execute one of the routes a call to `/stepTransaction` is needed to generate the transaction data.
### Disabling Simulation
* By default, responses with transaction data include on-chain simulation checks.
* To improve speed, set the simulation option to `false` . You pass the `skipSimulation` flag as a query parameter to the `/quote` or `/stepTransaction` endpoint:
```json theme={"system"}
/v1/advanced/stepTransaction?skipSimulation=true
/v1/quote?skipSimulation=true&...
```
* **Note**: Disabling simulation reduces verification but improves response time. It is especially recommended when you simulate/gasEstimate the transaction either way in your system.
### Selecting Timing Strategies
LI.FI allows you to control how long it waits for results using timing strategies. Instead of only specify a timeout they are allow more advanced configuration to ensure that results of multiple tools get considered.
Timing strategies are applied in two ways when generating routes:
* `swapStepTimingStrategies`: applied when requesting same chain exchanges
* `routeTimingStrategies`: applied on the full route that can consist of multiple tools (e.g. swap+bridge)
#### Timing Strategy Format
A timing strategies consists of the following properties:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 600,
"startingExpectedResults": 4,
"reduceEveryMs": 300
}
```
* **strategy:** Currently only `minWaitTime` exists
* **minWaitTimeMs:** Minimum time to wait for responses (e.g. 600ms)
* **startingExpectedResults:** Number of expected quotes (e.g. 4)
* **reduceEveryMs:** Frequency of reducing expectations (e.g. every 300ms)
> When this strategy is applied, we give all tool 600ms (minWaitTimeMs) to return a result. If we received 4 or more (startingExpectedResults) results during this time we return those and don't wait for other tools.\
> If less than 4 results are present we wait another 300ms and check if now at least 3 results are present.
#### Passing Strategies in API calls
In `POST /v1/advanced/routes` requests:
```json theme={"system"}
{
...
"options": {
"timing": {
"swapStepTimingStrategies": [
{
"strategy": "minWaitTime",
"minWaitTimeMs": 600,
"startingExpectedResults": 4,
"reduceEveryMs": 300
}
],
"routeTimingStrategies": [
{
"strategy": "minWaitTime",
"minWaitTimeMs": 1500,
"startingExpectedResults": 6,
"reduceEveryMs": 500
}
]
}
}
}
```
In `GET /v1/quote` requests:
```
/v1/quote?...
&swapStepTimingStrategies=minWaitTime-600-4-300
&routeTimingStrategies=minWaitTime-1500-6-500
```
The passed strategies in those examples are the default strategies we apply.
#### Timing Strategy Examples
**Maximize Results**\
Returns the best routes even if it takes longer:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 5,
"reduceEveryMs": 300
}
```
**Balanced Approach**\
Waits a moderate amount of time to return a mix of speed and completeness:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 1,
"reduceEveryMs": 300
}
```
**Fastest Possible Response**\
Returns first available result with no delay:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 0,
"startingExpectedResults": 1,
"reduceEveryMs": 300
}
```
**Time-Limited Return (timeout)**\
Returns any result received within a fixed time limit:
```json theme={"system"}
{
"strategy": "minWaitTime",
"minWaitTimeMs": 900,
"startingExpectedResults": 0,
"reduceEveryMs": 0
}
```
The timing strategies are only used for how long LI.FI will wait for third party providers to respond, the total response time from LI.FI API will be the sum of roundTripTime+parsing+strategies+simulation.
# FeeForwarder
Source: https://docs.li.fi/introduction/integrating-lifi/fee-forwarder
How LI.FI's FeeForwarder contract works and what it means for partners
LI.FI has upgraded its fee handling mechanism on supported EVM chains, migrating from the **FeeCollector** contract to a new **FeeForwarder** contract. This page explains what changed, what it means for your integration, and what—if anything—you need to do.
## Why this change?
The previous FeeCollector model required fees to accumulate in a contract and be manually withdrawn later. This added operational overhead and delayed payouts to fee recipients.
With FeeForwarder, fees are forwarded immediately to the configured recipient wallet at transaction execution time. No manual withdrawal step is required. The contract is also designed to support future multi-party fee distribution models, such as splitting fees between LI.FI, an integrator, and a reseller.
## What changes for partners?
### Immediate fee forwarding
On chains where FeeForwarder is deployed, transactions call the new contract and fees are distributed automatically at execution time.
No action is required from partners.
### Backward compatibility
On chains where FeeForwarder has not yet been deployed, the system automatically falls back to the legacy FeeCollector contract. Existing withdrawal endpoints remain available, and no API changes have been introduced. Fee data in `/status` responses is unchanged.
## Event changes
For partners who parse on-chain events, the EVM fee event signature has changed.
**Previous event ([contract](https://github.com/lifinance/contracts/blob/main/src/Periphery/FeeCollector.sol)):**
```solidity theme={"system"}
event FeesCollected(
address indexed _token,
address indexed _integrator,
uint256 _integratorFee,
uint256 _lifiFee
);
```
**New event ([contract](https://github.com/lifinance/contracts/blob/main/src/Periphery/FeeForwarder.sol)):**
```solidity theme={"system"}
struct FeeDistribution {
address recipient;
uint256 amount;
}
event FeesForwarded(
address indexed token,
FeeDistribution[] distributions
);
```
This [sample transaction](https://basescan.org/tx/0x02e7f968e97382b43126d8f15a90b41f35a8b595b5a1a2d3be36a48ec5eb9854) shows fees forwarded directly from the LiFiDiamond address to the configured fee receiving wallets.
The Status API has been updated to correctly parse the new event format. Fee data, including `integratorFeeCost`, continues to be returned in the same `/status` response structure.
## What is not changing
* No API response structure changes
* No changes to fee configuration
* No changes required in partner integration code
* Solana, Bitcoin, Sui, and Move implementations are not affected
* Existing fees accumulated in FeeCollector are not automatically migrated; see the section below for how to withdraw them
## Withdrawing previously collected fees
Fees collected through the legacy FeeCollector contract before the FeeForwarder upgrade are **not** migrated automatically. They remain in the FeeCollector contract and must be withdrawn manually.
Only the wallet address that was designated for fee collection when the fees were accrued can withdraw those fees. If you have since updated your fee wallet, use the **original** wallet to claim older balances.
### Check your legacy balances
**Via the Partner Portal:**
Log in to [portal.li.fi](https://portal.li.fi/) and review your fee balances dashboard.
**Via the API:**
```http theme={"system"}
GET https://li.quest/v1/integrators/{integratorId}
```
This returns your fee balances per chain and per token:
```json theme={"system"}
{
"integratorId": "your-integrator-id",
"feeBalances": [
{
"chainId": 137,
"tokenBalances": [
{
"token": {
"address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"symbol": "USDC",
"decimals": 6,
"chainId": 137,
"name": "USD Coin",
"priceUSD": "1.00"
},
"amount": "50000000",
"amountUsd": "50.00"
}
]
}
]
}
```
### Withdraw legacy fees
**Via the Partner Portal:**
Use the withdrawal feature in [portal.li.fi](https://portal.li.fi/).
**Via the API:**
```http theme={"system"}
GET https://li.quest/v1/integrators/{integratorId}/withdraw/{chainId}
```
This returns a transaction request that you sign and submit from the original fee wallet:
```json theme={"system"}
{
"transactionRequest": {
"to": "0x...",
"data": "0x...",
"value": "0"
}
}
```
The withdrawal endpoint is available for EVM chains only. On Solana, Sui, and Bitcoin, fees have always been sent directly to your wallet and do not require withdrawal.
## Summary
| | FeeCollector (legacy) | FeeForwarder (new) |
| --------------------- | ----------------------------- | -------------------------------------------------------- |
| Fee settlement | Accumulated, claimed manually | Forwarded immediately at execution |
| Manual withdrawal | Required | Not required |
| Partner action needed | None | None |
| Backward compatible | — | Yes, chains without FeeForwarder fall back automatically |
This is a backend contract upgrade. Partners do not need to take any action.
If you have questions about fee configuration or how the Status API reports fees for your transactions, reach out via your usual support channel.
# Open Intents
Source: https://docs.li.fi/lifi-intents/architecture/open-contributions
LI.FI Intent is built on the Open Intents Framework. LI.FI supports the OIF as an active contributor to advance intent adoption and cross-chain interopability through contributions to The Compact, Open Intents Framework (OIF), and feedback to ERC-7683.
LI.FI Intent is built on the OIF reference contracts and is fully compatible with OIF intent system. Solvers capable of solving OIF intent systems can solve LI.FI intents by whitelisting the LI.FI Intent contracts.
LI.FI strives to support and contribute to existing and upcoming intent standards to unify intent networks to pave the road towards chain abstraction.
* By contributing to [The Compact](https://github.com/Uniswap/the-compact) with the goal of advancing Resource Lock development and spreading open and borderless account abstraction implementations.
* Working with the Ethereum Foundation on building a modular and flexible intent framework for emerging chains through the [Open Intents Framework (OIF)](https://github.com/openintentsframework/oif-contracts).
* By submitting feedback on [ERC-7683](https://www.erc7683.org/) and paving a road for ERC-7683 integration into LI.FI intents.
# Oracle Systems
Source: https://docs.li.fi/lifi-intents/architecture/oracle-systems
LI.FI Intents lets you plug in custom validation and oracle layers, empowering you to choose the optimal trust and speed tradeoffs for your cross-chain use case.
Any validation layer can be added as long as it supports validating a payload from the output chain. LI.FI intent supports any oracle that the Open Intents Framework (OIF) supports. An OIF oracle validation layer needs to support the following:
1. A submission interface where solvers can submit their filled outputs. The submission interface should accept arbitrary packages for validation and then call the associated output settlement contract to check whether the payloads are valid. If *submission* is automatic by being capable of proving contract calls, events, or storage this requirement can be ignored.
Oracle implementations can validate whether an output has been filled through the `IAttester` interface:
```solidity theme={"system"}
interface IAttester {
function hasAttested(
bytes[] calldata payloads
) external view returns (bool);
}
```
2. Implement the validation interfaces so attested (or filled) outputs can verify whether outputs have been filled:
```solidity theme={"system"}
interface IInputOracle {
function efficientRequireProven(
bytes calldata proofSeries
) external view;
}
```
Beware of the following when implementing oracle systems:
* Submission or receive interfaces do not have to be standardized; they need to be documented so solvers can implement it.
* Oracle systems can either have automatic relaying or manual relaying.
* Trust assumptions for the oracle system needs to be explicit, so intent issuers and solvers can make informed decisions.
For more documentation refer to the [OIF Contributing Guidelines](https://github.com/openintentsframework/oif-contracts/blob/main/CONTRIBUTING.md#oif-oracle-pr-requirements). These requirements specify how a proper OIF compatible oracle system should behave.
## Implemented Validation Interfaces
There are three types of validation interfaces:
1. **Self-serve**: Validation interfaces where submitting the payload generates an off-chain proof that must be collected and submitted on the input chain.
* [Polymer](#polymer)
* [Wormhole](#wormhole)
2. **Automatic**: Validation interfaces where submitting the payload automatically delivers the associated proof on the input chain.
* [Hyperlane](#hyperlane)
* [Chainlink CCIP](#chainlink-ccip)
* [Axelar](#axelar)
* [LayerZero](#layerzero)
3. **Same Chain**: Using the [Output Settlement](/lifi-intents/architecture/output-settlement) to produce an oracle proof for itself, skipping a dedicated oracle system.
* [OutputSettlerSimple](#outputsettlersimple)
Choosing an oracle system is up to the intent issuer. Oracle security is directly linked to the payment of users' inputs to solvers, since users are paid directly by solvers. When issuing an intent, it is important that solvers are capable of understanding how to fill an intent using a specific oracle. Users can freely choose which oracle system they want to secure a specific intent, but solvers have to implement it for the intent to be solved.
When using the LI.FI intent order server, it will only support oracle systems that solvers are quoting for at a specific time.
**Speed & Price**\
Some oracle systems are significantly faster than other oracle systems. While speed does not impact asset delivery for users, it matters for solvers. Choosing an oracle system with fast repayments results in cheaper intents, as solvers can rotate their capital faster.
The following documentation may be outdated if the oracle network updates their documentation and does not notify the LI.FI team. Always refer to the oracle system's official documentation for the most accurate and up-to-date information.
### [OutputSettlerSimple](https://github.com/openintentsframework/oif-contracts/blob/main/src/output/simple/OutputSettlerSimple.sol)
OutputSettlerSimple is an [Output Settler](/lifi-intents/architecture/output-settlement) that can be used as an oracle for a same chain intent. To use the OutputSettlerSimple, call [`setAttestation`](https://github.com/openintentsframework/oif-contracts/blob/035fa5782829cb54bbd461e6e45e3f4e7d4afc8a/src/output/OutputSettlerBase.sol#L324-L354) after filling the output.
```solidity theme={"system"}
function setAttestation(
bytes32 orderId,
bytes32 solver,
uint32 timestamp,
MandateOutput calldata output
) external
```
If the Output Settler is configured as both the input and output oracle, calling `setAttestation` will expose the filled outputs for the Input Settlement to verify.
### [Polymer](https://polymerlabs.org/)
Polymer is an event based system that provides real time proofs at low cost. Security is based on bundled state through Polymer with block headers sourced from Sequencer preconfirmations.
* Low gas cost.
* Low latency for quick settlement.
* Polymer can only prove one output at a time requiring batch proving to be implemented on the solver side to optimise efficiency.
* Broadcast
The target event for validation is [`OutputFilled`](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/output/OutputSettlerBase.sol#L92-L94), emitted when an output has been filled.
```solidity theme={"system"}
event OutputFilled(
bytes32 indexed orderId,
bytes32 solver,
uint32 timestamp,
MandateOutput output,
uint256 finalAmount
);
```
Using the Polymer [Prove API](https://docs.polymerlabs.org/docs/build/get%20started/prove-api-V2/api-endpoints), a proof of the event can be generated. Once generated, it can be submitted to [`receivedMessage`](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/integrations/oracles/polymer/PolymerOracle.sol#L74-L78).
For an example of a integration for fetching Polymer proofs, see [lintent.org/polymer](https://github.com/lifinance/lintent/blob/main/src/routes/polymer/%2Bserver.ts) or the [receiveMessage](https://github.com/lifinance/lintent/blob/5787de1e54217174062a3c8f5105bb5cdd189cf1/src/lib/utils/lifiintent/tx.ts#L668-L675) call made.
### [Wormhole](https://wormhole.com/)
Wormhole is a messaging based system that allows contracts to broadcast VAAs (Verified Action Approvals). VAAs are messages signed by the Wormhole Guardian network.
* Security through a decentralized guardian set.
* Supports batch proving of multiple outputs in a single VAA.
* Comparatively slow compared to alternatives.
* Broadcast
The Wormhole implementation is based on the broadcast functionality of Wormhole. Messages must be submitted to the Wormhole Implementation via the [`submit`](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/integrations/oracles/wormhole/WormholeOracle.sol#L48-L53) interface. Messages must be encoded into [`FillDescription`](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/libs/MandateOutputEncodingLib.sol#L22-L37)s and then submitted:
```solidity theme={"system"}
function submit(
address source,
bytes[] calldata payloads
) public payable returns (uint256 refund);
```
This message is then emitted to the Wormhole guardian set. Once the associated proof becomes available, the solver can submit the proof to [`receiveMessage`](https://github.com/openintentsframework/oif-contracts/blob/daa8913e5803d8b62b646335d4c5130cdfacfec8/src/oracles/wormhole/WormholeOracle.sol#L78-L80) on the input chain to validate their intents.
There are many ways to get Wormhole VAAs like the [Wormholescan api](https://docs.wormholescan.io) or the [Wormhole SPY](https://wormhole.com/docs/protocol/infrastructure/spy/).
The Wormhole implementation uses a significantly more efficient validation algorithm than Wormhole's `Implementation.sol` for gas saving purposes.
### [Hyperlane](https://www.hyperlane.xyz/)
Hyperlane is a highly customizable messaging based system that allows contracts to send and receive messages verified through a selected Interchain Security Modules (ISM).
* Highly customizable security modules.
* Supports batch proving of multiple outputs in a single message.
* Strong censorship guarantees.
* Automatic or manual relaying.
The Hyperlane implementation is based on the `dispatch` function on the Hyperlane mailbox. To configure Hyperlane relaying, provide the relevant `customHook` and `customHookMetadata` when calling [`submit`](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/integrations/oracles/hyperlane/HyperlaneOracle.sol#L60-L116).
```solidity theme={"system"}
function submit(
uint32 destinationDomain,
address recipientOracle,
uint256 gasLimit,
bytes calldata customMetadata,
address source,
bytes[] calldata payloads
) public payable
```
For more about dispatch hooks see the [Hyperlane documentation](https://docs.hyperlane.xyz/docs/protocol/core/post-dispatch-hooks-overview).
Because Hyperlane is a highly customizable system, the exact required interactions highly varies based on the selected ISM and relaying method. For more information, refer to the [Hyperlane documentation](https://docs.hyperlane.xyz).
### [Chainlink CCIP](https://chain.link/cross-chain)
Chainlink CCIP is a messaging based system that allows contracts to send and receive messages verified through Chainlink oracles.
* Security through Chainlink's oracle network.
* Supports batch proving of multiple outputs in a single message.
* [SOC2 and ISO 27001](https://blog.chain.link/chainlink-achieves-iso-soc-2-certifications/) compliant
Chainlink CCIP implementation is based on the CCIP router `ccipSend` with an `EVM2AnyMessage` . To submit filled outputs to the CCIP oracle, first call the quote endpoint: `getFee` to estimate the messaging fee. Then call `submit` to send the message to the output chain along with the required fee. If an ERC20 token is used to pay the fee, approve the token transfer first.
```solidity theme={"system"}
function submit(
uint64 destinationChainSelector,
bytes32 receiver,
bytes calldata extraArgs,
address source,
bytes[] calldata payloads,
address feeToken
)
```
CCIP is automatic and once submitted, Chainlink will relay the message to the input chain.
### [Axelar](https://www.axelar.network)
Axelar is a messaging based system that allows contracts to send and receive message verified by Axelar's Proof of State network.
* Security through Axelar's Proof of State network.
* Supports batch proving of multiple outputs in a single message.
* Automatic relaying.
Axelar implementation is based on the Axelar Gateway `callContract` function. To submit filled outputs to the Axelar oracle, first use the `gasService` to estimate the fees. Then call `submit` to send the message to the output chain along with the required fee.
```solidity theme={"system"}
submit(
string calldata destinationChain,
string calldata destinationAddress,
address source,
bytes[] calldata payloads
) public payable
```
Axelar is automatic and once submitted, Axelar will relay the message to the input chain.
### [LayerZero](https://layerzero.network/)
LayerZero is a security customizable messaging based system that allows contracts to send and receive messages verified through a selected set of Decentralized Verifier Networks (DVNs).
* Customizable security
* Popular and broad chain compatibility.
* Supports batch proving of multiple outputs in a single message.
* Expensive
The LayerZero implementation is based on `send` on the LayerZero endpoint. To submit filled outputs to the LayerZero oracle, first use the `quote` function to estimate fees and then call `submit` with the required fee to send the message to the output chain.
```solidity theme={"system"}
function submit(
uint32 dstEid,
address recipientOracle,
address source,
bytes[] calldata payloads,
bytes calldata options
) external payable;
```
LayerZero is automatic and once submitted, LayerZero will relay the message to the input chain.
### Bitcoin
LI.FI Intents has a Bitcoin Simplified Payment Validation (SPV) client implementation. This implementation works both as an Output Settlement implementation and as a validation layer.
The Bitcoin SPV client requires constant upkeep. The blockchain must be updated approximately every 10 minutes, or whenever a transaction needs to be proven, to properly validate transactions.
To generate a transaction proof, refer to the code below:
```typescript theme={"system"}
import mempoolJS from "@catalabs/mempool.js";
const mainnet: boolean;
const {
bitcoin: { transactions, blocks },
} = mempoolJS({
hostname: "mempool.space",
network: mainnet ? undefined : "testnet4",
});
export async function generateProof(
txid: string,
): Promise<{ blockHeader: string; proof: Proof; rawTx: string }> {
const tx = await transactions.getTx({ txid });
const merkleProof = await transactions.getTxMerkleProof({ txid });
// TODO: serialization version 1.
const rawTx = await transactions.getTxHex({ txid });
const blockHash = await blocks.getBlockHeight({
height: merkleProof.block_height,
});
// Most endpoints provide transactions witness encoded.
// The following function serves to strip the witness data.
const rawTxWitnessStripped = removeWitnesses(rawTx);
const blockHeader = await blocks.getBlockHeader({ hash: blockHash });
return {
blockHeader,
proof: {
txId: txid,
txIndex: merkleProof.pos,
siblings: merkleProof.merkle.reduce(
(accumulator, currentValue) => accumulator + currentValue,
),
},
rawTx: rawTxWitnessStripped,
};
}
```
# System Overview
Source: https://docs.li.fi/lifi-intents/architecture/overview
Overview of the LI.FI Intent System. The system consists of three modular components (Input Settler, Output Settler, and validation network) that can be selected based on the desired intent structure.
The LI.FI Intent System modularizes three components of the intent flow:
1. **Input Settler** – Handles the source chain funds.
2. **Output Settler** – Handles the destination chain funds.
3. **Oracle System** – Makes Output Settlement statements available for the input settler.
Historically, these components have been intertwined, which presented scaling challenges. A componentized approach allows for greater flexibility and extensibility, enabling different input and output settlement schemes and validation layers to be mixed and matched.
### Input settlement
The input settlement scheme manages user deposits and releases funds to solvers once intents are fulfilled. LI.FI Intents currently support two input settlement schemes:
* **Escrow** via [`InputSettlerEscrow.sol`](https://github.com/openintentsframework/oif-contracts/blob/main/src/input/escrow/InputSettlerEscrow.sol)
* [**The Compact**](https://github.com/Uniswap/the-compact) via [`InputSettlerCompact.sol`](https://github.com/openintentsframework/oif-contracts/blob/main/src/input/compact/InputSettlerCompact.sol). The Compact supports both fill-first flows and ordinary flows.
The intent system imposes no restrictions on the implementation of input settlements. Input settlements can access proven outputs through validation layers by calling [`efficientRequireProven`](https://github.com/openintentsframework/oif-contracts/blob/main/src/interfaces/IOracle.sol#L19-L27). If an order contains multiple outputs and the outputs are filled by different solvers, **the filler of the first output** in the order specification is considered the canonical solver.
[Learn more about Input Settlement →](/lifi-intents/architecture/input-settlement)
### Output settlement
The output settlement scheme handles the delivery of assets on destination chains. It imposes no interface requirements, order structure, or order type, except that an [interface to validate payloads](https://github.com/openintentsframework/oif-contracts/blob/main/src/interfaces/IPayloadCreator.sol) must be provided:
```solidity theme={"system"}
interface IPayloadCreator {
function arePayloadsValid(
bytes[] calldata payloads
) external view returns (bool);
}
```
This allows the output settlement scheme to be incredibly flexible; it can support any order type on any virtual machine, as long as the filled order can be expressed as an opaque bytes array.
The initial version of the intent system uses `MandateOutput`, with the encoding described by [`MandateOutputEncodingLib.sol`](https://github.com/openintentsframework/oif-contracts/blob/main/src/libs/MandateOutputEncodingLib.sol).
If the input settlement could validate this call, the inputs could be appropriately paid to the solver. However, this information only exists on the output settlement on the output chain.
[Learn more about Output Settlement →](/lifi-intents/architecture/output-settlement)
### Oracle system
The oracle system ferries valid payloads from the output chain to the input chain. It serves as the bridge that confirms output delivery has occurred.
Any validation layer can be added as long as it supports validating a payload from a remote chain. Currently available validation layers include:
* [**Polymer**](https://github.com/openintentsframework/oif-contracts/blob/main/src/oracles/polymer/PolymerOracleMapped.sol)
* [**Wormhole**](https://github.com/openintentsframework/oif-contracts/blob/main/src/oracles/wormhole/WormholeOracle.sol)
* [**Bitcoin**](https://github.com/openintentsframework/oif-contracts/blob/main/src/oracles/bitcoin/BitcoinOracle.sol)
Before emitting messages, the oracle is expected to check if one or more payloads are valid and then ship them to the input chain:
```solidity theme={"system"}
function submit(address proofSource, bytes[] calldata payloads) external payable {
// Check if the payloads are valid.
if (!IPayloadCreator(proofSource).arePayloadsValid(payloads)) revert NotAllPayloadsValid();
// Payloads are valid. We can submit them on behalf of proofSource.
_submit(proofSource, payloads);
}
```
Oracles may use custom message encodings, custom relaying properties, custom interfaces, or other special integration concerns. Internal oracle messaging is not standardized.
On the input chain, the oracle system is expected to validate the payload(s) through a virtual machine local hash:
```solidity theme={"system"}
interface IValidationLayer {
/**
* @notice Check if a series of data has been attested to.
* @dev Does not return a boolean; instead, it reverts if false.
* This function returns true if proofSeries is empty.
* @param proofSeries remoteOracle, remoteChainId, and dataHash encoded in chunks of 32*4=128 bytes.
*/
function efficientRequireProven(
bytes calldata proofSeries
) external view;
}
```
Using only the payload hash makes the system more efficient, as less data is passed around. No attempt is made at standardizing the payload. As a result, there may be [incompatibilities](/lifi-intents/architecture/oracle-systems) between the input and output settlement layers.
[Learn more about Validation Layers →](/lifi-intents/architecture/oracle-systems)
## Security Assumptions
The intent system includes resource locks, which create trust boundaries between key actors:
* **[Sponsors](/lifi-intents/knowledge-database/glossary#sponsor)** (users) trust that arbiters won't fraudulently finalize issued locks.
* **[Arbiters](/lifi-intents/knowledge-database/glossary#arbiter)** and **[solvers](/lifi-intents/knowledge-database/glossary#solver)** trust allocators not to co-sign overlapping locks exceeding user deposits.
No single actor can independently access funds, creating a secure environment for cross-chain transactions.
For more about security and intent validation, [see order-validation →](/lifi-intents/for-solvers/orderflow#order-validation)
## Integration Points
LI.FI intent is designed to be highly composable, with different components capable of being swapped out as needed:
* **Input Settlement**: New resource lock standards and standard intent interfaces can be integrated.
* **Output Settlement**: New order types and fulfillment mechanisms can be added.
* **Oracle System**: Different cross-chain messaging protocols and proof layers can be supported.
[Integrators](/lifi-intents/knowledge-database/glossary#integrator) can freely pick and choose which components to use for which swaps. This gives intent issuers maximum flexibility to describe their desired end state.
### Same-chain intents
LI.FI Intents were designed for composable cross-chain swaps and also support same-chain intents in a compatible envelope. In same-chain mode, the output settler can act as the oracle path.
To use LI.FI Intents for same-chain swaps, configure the output settler as both input and output oracle. Solvers filling same-chain intents can optionally use a callback to receive inputs before delivering outputs.
To receive inputs before delivering outputs, your solver has to support `orderFinalised`. When using the callback to receive inputs, it is important to `fill` and `setAttestations` for the intent outputs within the callback.
```solidity theme={"system"}
function orderFinalised(
uint256[2][] calldata inputs,
bytes calldata call
) external
```
For more on same-chain callbacks, see the [same-chain test case](https://github.com/catalystsystem/catalyst-intent/blob/main/test/integration/InputSettler7683LIFI.samechain.t.sol).
For more about same chain intents, see [Output Settler as Oracle](/lifi-intents/architecture/oracle-systems#outputsettlersimple)
### Smart contracts
Currently maintained LI.FI Intent deployments are listed below. Contracts are deployed to the same addresses across supported chains and can be permissionlessly deployed to new chains.
**Mainnet (including):** Ethereum, Base, Arbitrum
**Testnet (including):** Sepolia, Base Sepolia, Arbitrum Sepolia, Optimism Sepolia
* **Input Settler Compact**: `0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf`
* **Input Settler Escrow**: `0x000025c3226C00B2Cdc200005a1600509f4e00C0`
* **Output Settler**: `0x0000000000eC36B683C2E6AC89e9A75989C22a2e`
* **Polymer Oracle Mainnet**: `0x0000003E06000007A224AeE90052fA6bb46d43C9`
* **Polymer Oracle Testnet**: `0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00`
* **Uniswap The Compact**: `0x00000000000000171ede64904551eeDF3C6C9788`
# Settlement
Source: https://docs.li.fi/lifi-intents/architecture/settlement
How input and output settlement works in LI.FI Intents, covering escrow, Compact, and output settler contracts.
Settlement is the process of escrowing user funds (input settlement) and recording solver delivery (output settlement). This page covers both sides.
***
## Input settlement
Two single-chain input settlers are supported:
* [**InputSettlerEscrow**](https://github.com/openintentsframework/oif-contracts/blob/main/src/input/escrow/InputSettlerEscrow.sol). Standard escrow that associates tokens with their intent.
* [**InputSettlerCompact**](https://github.com/openintentsframework/oif-contracts/blob/main/src/input/compact/InputSettlerCompact.sol). Long-lived deposits via The Compact resource lock.
[Multi-chain input settlers](https://github.com/openintentsframework/oif-contracts/pull/49) are in progress.
The Compact supports fill-first flows through resource locks. The Escrow settler uses traditional flows where the escrow must be opened before fills.
### StandardOrder
Single-chain input settlers use the following order structure:
```solidity theme={"system"}
struct StandardOrder {
address user;
uint256 nonce;
uint256 originChainId;
uint32 expires;
uint32 fillDeadline;
address inputOracle;
uint256[2][] inputs;
MandateOutput[] outputs;
}
```
### MandateOutput
```solidity theme={"system"}
struct MandateOutput {
bytes32 oracle;
bytes32 settler;
uint256 chainId;
bytes32 token;
uint256 amount;
bytes32 recipient;
bytes call;
bytes context;
}
```
### Finalization
To claim input funds after filling an order, call one of:
1. **`finalise`** Called by the solver. The caller designates where to send assets and whether to make an external call.
2. **`finaliseWithSignature`** Called by anyone with an `AllowOpen` signature from the solver containing destination and call details.
### Compact Registration
Compact intents are signed as a `BatchClaim` using The Compact's EIP-712 domain separator:
```solidity theme={"system"}
struct BatchCompact {
address arbiter;
address sponsor;
uint256 nonce;
uint256 expires;
uint256[2][] idsAndAmounts;
Mandate mandate;
}
struct Mandate {
uint32 fillDeadline;
address inputOracle;
MandateOutput[] outputs;
}
```
Alternatively, intents can be registered on-chain by the sponsor or by someone paying for the claim on their behalf.
### Escrow Registration
Escrow intents can be registered in several ways:
1. **Direct open** by the owner through ERC-7683 `function open(bytes)`. Emits `event Open(bytes32 indexed orderId, bytes order)`.
2. **ERC-3009** with the orderId as nonce. A signature per input is required.
3. **Permit2** with an EIP-712 signed `PermitBatchWitnessTransferFrom`.
### Integration Examples
* Smart contract registration: [`RegisterIntentLib.sol`](https://github.com/catalystsystem/catalyst-intent/blob/27ce0972c150ed113f66ae91069eb953f23d920b/src/libs/RegisterIntentLib.sol#L100-L131)
* UI signing (Batch Compact): [lintent.org demo](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L144)
* UI deposit and registration: [lintent.org demo](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/lifiintent/tx.ts#L199)
***
## Output settlement
Currently one output settler exists: [`OutputSettlerSimple.sol`](https://github.com/openintentsframework/oif-contracts/blob/main/src/output/simple/OutputSettlerSimple.sol). It supports four order types: simple limit orders, Dutch auctions, exclusive limit orders, and exclusive Dutch auctions.
### Configuring Auction Type
The auction type is determined by the `context` field in `MandateOutput`:
```solidity theme={"system"}
// Simple limit order (no auction)
context = "0x" or "0x00";
// Dutch auction
context = abi.encodePacked(0x01, slope, stopTime);
```
For Dutch auctions with multiple outputs, only the first output functions as an auction. The rest resolve to their worst price, because solvers only compete on the first output (winning it wins the entire order).
For more details on auction mechanics, see [Auctions](/lifi-intents/for-solvers/auctions).
***
## Next Steps
Full architecture and contract addresses
Verification and proof systems
Order construction for integrators
How solvers deliver outputs
# Authentication
Source: https://docs.li.fi/lifi-intents/authentication
API keys, access levels, and environments for the LI.FI Intents API.
The LI.FI Intents API has two access levels: open integrator endpoints and authenticated solver endpoints.
***
## Integrators
Integrator endpoints are fully open. No API key, no registration, no rate limits. You can start building immediately.
### Endpoints
| Method | Path | Description |
| ------ | ---------------------------------- | ---------------------------------------- |
| `POST` | `/quote/request` | Request a quote for an intent |
| `GET` | `/api/v1/integrator/quote/request` | V1 integrator-friendly quote endpoint |
| `POST` | `/orders/submit` | Submit an order to the solver network |
| `GET` | `/orders` | List orders with optional filters |
| `GET` | `/orders/status` | Get order status |
| `GET` | `/chains/supported` | Get the list of supported chains |
| `GET` | `/routes` | Get supported routes (chain/asset pairs) |
***
## Solvers (API Key Required)
Solver endpoints require an API key passed via the `api-key` header. These endpoints are used for submitting quotes, managing solver accounts, and tracking reputation.
| Method | Path | Description |
| ------ | -------------------------------- | --------------------------------------------- |
| `POST` | `/quotes/submit` | Submit or update up to 200K solver quotes |
| `GET` | `/solver-api/quotes` | Get solver's uploaded quote inventory |
| `GET` | `/solver-api/solver/identities` | Get solver identities for reputation tracking |
| `POST` | `/solver-api/account/register` | Register a solver account |
| `POST` | `/solver-api/account/unregister` | Unregister a solver account |
### Authenticating Requests
Pass your API key in the `api-key` header:
```bash theme={"system"}
curl -X POST 'https://order.li.fi/quotes/submit' \
-H 'Content-Type: application/json' \
-H 'api-key: YOUR_SOLVER_API_KEY' \
-d '{ ... }'
```
### Getting an API Key
Register and manage your solver API key in the solver UI:
| Environment | Solver UI | API Base URL |
| ----------- | --------------------------------------------- | ------------------------- |
| Production | [intents.li.fi](https://intents.li.fi/) | `https://order.li.fi` |
| Testnet | [devintents.li.fi](https://devintents.li.fi/) | `https://order-dev.li.fi` |
We recommend starting on testnet to validate your integration before going live.
***
## Environments
| Environment | Base URL | Use |
| ----------- | ------------------------- | -------------------------------------------- |
| Production | `https://order.li.fi` | Live orders with real assets |
| Development | `https://order-dev.li.fi` | Testnet orders (Sepolia, Base Sepolia, etc.) |
Interactive API documentation is available at [`/docs`](https://order.li.fi/docs) on both environments.
***
## Next Steps
End-to-end escrow flow for integrators
Integrator endpoints, order lifecycle, and address encoding
Solver endpoints, quoting, and account management
# Solver API Overview
Source: https://docs.li.fi/lifi-intents/for-solvers/api-overview
Endpoints, authentication, and integration surface for LI.FI Intents solvers.
The Solver API is the authenticated interface for solvers to submit quotes, manage accounts, and track reputation. All solver endpoints require an API key.
For integrator-facing endpoints (quotes, orders, status tracking), see the [Intents API Overview](/lifi-intents/intents-api/api-overview).
***
## Base URLs
| Environment | Base URL | Solver UI |
| ----------- | ------------------------- | --------------------------------------------- |
| Production | `https://order.li.fi` | [intents.li.fi](https://intents.li.fi/) |
| Testnet | `https://order-dev.li.fi` | [devintents.li.fi](https://devintents.li.fi/) |
Interactive API documentation is available at [`/docs`](https://order.li.fi/docs) on both environments.
***
## Authentication
All Solver API endpoints require an API key passed via the `api-key` header. Register for a key in the solver UI linked above. See [Authentication](/lifi-intents/authentication#solvers-api-key-required) for full details.
```bash theme={"system"}
curl -X POST 'https://order.li.fi/quotes/submit' \
-H 'Content-Type: application/json' \
-H 'api-key: YOUR_SOLVER_API_KEY' \
-d '{ ... }'
```
***
## Endpoints
### Quote Management
| Method | Path | Description |
| ------ | -------------------- | ------------------------------------------------- |
| `POST` | `/quotes/submit` | Submit or update up to 200K solver quotes at once |
| `GET` | `/solver-api/quotes` | Retrieve your currently uploaded quote inventory |
Quotes define the routes, price curves, and amount ranges you're willing to fill. The order server uses your quotes to match intents, so you don't need to respond to individual quote requests. See [Quoting Orders](/lifi-intents/for-solvers/quoting) for the full quote format and strategy.
### Account Management
| Method | Path | Description |
| ------ | -------------------------------- | --------------------------- |
| `POST` | `/solver-api/account/register` | Register a solver account |
| `POST` | `/solver-api/account/unregister` | Unregister a solver account |
### Reputation
| Method | Path | Description |
| ------ | ------------------------------- | -------------------------------------------------------- |
| `GET` | `/solver-api/solver/identities` | Get solver identities registered for reputation tracking |
See [Reputation](/lifi-intents/for-solvers/reputation) for how solver performance is tracked.
### Order Collection
Solvers receive orders via two channels:
1. **WebSocket (recommended):** Connect to the order server for real-time order streaming.
2. **On-chain monitoring:** Watch for `Open` events on the `InputSettlerEscrow` or `InputSettlerCompact` contracts.
See [Order Flow](/lifi-intents/for-solvers/orderflow) for connection details and order validation.
***
## Solver Flow Summary
```
Register account → Submit quotes → Receive orders → Fill outputs → Validate via oracle → Settle
```
| Step | What you do | Reference |
| ------------ | ---------------------------------------------------------- | ----------------------------------------------------------- |
| **Register** | Create a solver account via `/solver-api/account/register` | This page |
| **Quote** | Push inventory to `/quotes/submit` | [Quoting](/lifi-intents/for-solvers/quoting) |
| **Receive** | Connect to WebSocket or monitor on-chain events | [Order Flow](/lifi-intents/for-solvers/orderflow) |
| **Fill** | Deliver assets via `OutputSettlerSimple.fill()` | [Filling Orders](/lifi-intents/for-solvers/filling-orders) |
| **Prove** | Submit fill proof through the oracle system | [Oracle Systems](/lifi-intents/architecture/oracle-systems) |
| **Settle** | Call `finalise` on the input settler to receive payment | [Settlement](/lifi-intents/for-solvers/settlement) |
***
## Next Steps
Full solver integration guide with order types and filling
Quote format, price curves, and inventory management
WebSocket connection, order validation, and on-chain monitoring
API key registration and environment details
# Auctions
Source: https://docs.li.fi/lifi-intents/for-solvers/auctions
LI.FI Intents powers on-chain auctions and flexible order types, letting you compete for fills and optimize execution across cross-chain settlement.
LI.FI Intents support four order types: Simple limit orders, Dutch auctions, Exclusive limit orders, and Exclusive dutch auctions. Each intent output encodes a specific order type. Generally, all outputs should be of the same order type.
For an order with multiple outputs, the solver of the first output will **always** be the recipient of the inputs. The inputs cannot be claimed until all outputs are filled. The *auction* happens on-chain, with the winner determined by speed.
The primary order type of LI.FI intents is [Exclusive Limit Order](#exclusive-limit-order) which is the one served from the LI.FI widget and most other integrators.
### Limit Order (First come, First serve)
LI.FI intent limit orders are simple: single price, first execution. That means the amount set in `OutputDescription` is final and will be what you are paying. The output can be filled once per `orderId` (the output is hashed and stored in a map with `orderId`).
The winner of an order is the first solver to call `fill(...)` and set their identifier for the output.
You can identify Limit Orders by `output.context` of either `0x` or the order type `0x00`.
### Exclusive Limit Order
A LI.FI intent exclusive limit orders are very similar to a LI.FI intent limit orders. Single price, first execution. **Except** it has a cutoff timestamp embedded that only allows a specific solver to fill the intent before. This type of intent is often used in conjunction with the LI.FI order Server [Reputation](/lifi-intents/for-solvers/reputation) to eliminate wasted gas on failed transactions.
You can identify Exclusive Limit Orders by their order type `0xe0`. The context is encoded as: `bytes1(0xe0) | bytes32(exclusiveFor) | bytes4(startTime)`, with `startTime` being the time when anyone can fill the order.
For intent issuers, it is recommended to set the exclusivity period to between 30 seconds to 1 minute but it may be shorter or longer.
### Dutch Auction
LI.FI intent Dutch auctions are capped dutch auctions. They are fixed in price before and after certain timestamps. The slope, startTime, and stopTime are encoded in `output.context`.
The winner of an order is the first solver to call `fill(...)` and set their identifier as the output. Notice that the win selection is equivalent to the limit order, except the earlier the fill is called, the more the solver pays.
You can identify Dutch Auctions by the order type: `0x01`. The context is encoded as: `bytes1(0x01) | uint32(startTime) | uint32(stopTime) | uint256(slope)`, with the final amount being computed as:
```solidity theme={"system"}
uint32 currentTime = max(block.timestamp, startTime);
if (stopTime < currentTime) return amount;
uint256 timeDiff = stopTime - currentTime;
return amount + slope * timeDiff;
```
The maximum amount of the auction is `amount + (stopTime - startTime) * slope`.
You can find the on-chain [implementation here](https://github.com/openintentsframework/oif-contracts/blob/222989b15241ec680d2a0503c7396e1b6c649a50/src/output/simple/OutputSettlerSimple.sol#L94-L118).
### Exclusive Dutch Auction
LI.FI intent exclusive dutch auctions are very similar to LI.FI intent dutch auctions. However, before the auction slope begins the order is exclusive to a specific solver like [Exclusive Limit Order](#exclusive-limit-order).
You can identify Exclusive Dutch Auctions by their order type `0xe1`. The context is encoded as `bytes1(0x01) | bytes32(exclusiveFor) | uint32(startTime) | uint32(stopTime) | uint256(slope)`.
It is important to clarify that the exclusivity window is before the dutch auction slope begins, thus if the intent is filled by the exclusive solver they will be paying the maximum price for the auction.
# Filling Orders
Source: https://docs.li.fi/lifi-intents/for-solvers/filling-orders
Deliver output assets on EVM chains and Bitcoin to fulfill LI.FI intents.
To finalize an intent and claim the locked input funds, the solver must deliver all outputs described in the order. This page covers both EVM and Bitcoin filling.
***
## EVM Orders
Submit the intent outputs to the Output Settler specified in `output.settler` by calling `fillOrderOutputs`.
```solidity theme={"system"}
function fillOrderOutputs(
uint32 fillDeadline,
bytes32 orderId,
MandateOutput[] calldata outputs,
bytes32 proposedSolver
) external;
```
The transaction reverts if:
1. Approvals are not set
2. Caller balances are not sufficient
3. `fillDeadline` has expired
4. The first output in `outputs` has already been filled
5. Any external call in `outputs` reverts
6. The order context cannot be decoded
7. The destination chain is wrong
8. The Output Settler contract is wrong
An `orderId` mismatch will not cause a revert.
After all outputs are delivered, proceed to [settlement](/lifi-intents/for-solvers/settlement) to finalize the order and receive the locked inputs.
***
## Bitcoin Orders
BTC support on LI.FI Intents is currently not live and still in development. If you are interested in filling BTC orders, reach out and we will assist you with onboarding.
To determine whether an order involves a Bitcoin transaction, check `orderDto.order.outputs[].token`.
* The first 30 bytes should be `0x000000000000000000000000BC0000000000000000000000000000000000` (the 13th byte is `0xBC`)
* The 31st byte indicates the number of confirmations required before on-chain verification (`0x00` and `0x01` both mean 1 confirmation, `0x02` means 2, etc.)
* The 32nd byte is an address version identifier decoded as `uint8`
### Address Versions
| Version | Name | Encoding | Prefix | Hash Length |
| ------- | ------- | ------------------- | ------ | ----------- |
| 0 | Unknown | Ignore | | |
| 1 | P2PKH | Base58Check(00+PKH) | 1 | 20 |
| 2 | P2SH | Base58Check(05+SH) | 3 | 20 |
| 3 | P2WPKH | Bech32 | bc1q | 20 |
| 4 | P2WSH | Bech32 | bc1q | 32 |
| 5 | P2TR | Bech32m | bc1p | 32 |
The `outputs[].recipient` field contains the destination hash or witness, not a human-readable address. Use the version byte and the recipient hash to reconstruct the Bitcoin address.
```typescript theme={"system"}
import bs58check from 'bs58check';
import { bech32, bech32m } from 'bech32';
const hexStringToUint8Array = (hexString: string) =>
Uint8Array.from(
hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))
);
function decodeBitcoinAddress(
version: number,
recipientHash: string,
testnet = false,
): string {
if (version === 1) {
const prefix = !testnet ? '00' : '6F';
const bytes = hexStringToUint8Array(
prefix + recipientHash.replace('0x', '').slice(0, 40)
);
return bs58check.encode(bytes);
}
if (version === 2) {
const prefix = !testnet ? '05' : 'C4';
const bytes = hexStringToUint8Array(
prefix + recipientHash.replace('0x', '').slice(0, 40)
);
return bs58check.encode(bytes);
}
const prefix = !testnet ? 'bc' : 'tb';
if (version === 3) {
const bytes = hexStringToUint8Array(
recipientHash.replace('0x', '').slice(0, 40)
);
const words = bech32.toWords(bytes);
words.unshift(0x00);
return bech32.encode(prefix, words);
}
const bytes = hexStringToUint8Array(
recipientHash.replace('0x', '').slice(0, 64)
);
if (version === 4) {
const words = bech32.toWords(bytes);
words.unshift(0x00);
return bech32.encode(prefix, words);
}
if (version === 5) {
const words = bech32m.toWords(bytes);
words.unshift(0x01);
return bech32m.encode(prefix, words);
}
throw Error(`Unsupported Address Type ${version}`);
}
```
### Constructing the Bitcoin Transaction
Create a transaction with at least one output that exactly matches the order's output description. The transaction can have any number of inputs and additional outputs, allowing for batch filling and consolidation.
If calldata is set on the output, add an `OP_RETURN` output immediately after the filling output:
```typescript theme={"system"}
import * as bitcoin from 'bitcoinjs-lib';
bitcoin.initEccLib(ecc);
const psbt = new bitcoin.Psbt({
network: mainnet ? bitcoin.networks.bitcoin : bitcoin.networks.testnet,
});
// ... add inputs
psbt.addOutput({ address: to, value: outputValue });
const opReturnData = returnData.replace("0x", "");
if (opReturnData.length > 0) {
const data_embed = bitcoin.payments.embed({
data: [hexStringToUint8Array(opReturnData)],
});
psbt.addOutput({
script: data_embed.output!,
value: 0n,
});
}
// ... complete transaction
```
***
## Next Steps
Finalize orders and claim locked input funds
WebSocket and REST order collection
Limit, Dutch, and exclusive auction types
Solver reputation system and registration
# Solving LI.FI Intents
Source: https://docs.li.fi/lifi-intents/for-solvers/intro
LI.FI Intents gives you open order flow and permissionless solving. Direct access to cross-chain liquidity, fast settlement cycles, and flexible integration.
As a solver in the cross-chain ecosystem, you're likely focused on:
* Accessing reliable order flow
* Minimizing user escrow unlock times to reduce inventory requirements
* Having flexibility in your liquidity sources
### How LI.FI Intents Help
* **Direct access to [LI.FI](http://li.fi/)'s significant order flow**
* **Freedom to use any liquidity source** for solving (e.g., DEXs, CEXs, or your own inventory)
* **Fast user escrow unlock times** (typically less than 2 minutes)
* **Lower capital requirements** due to quick repayment cycles
## Solving Intents
LI.FI Intents is an entirely permissionless system. Since the system is componentized and components have no inherent trust elements with other components, they can be mixed and matched as desired by users. As a result, it is important that you validate orders in their entirety once received. The general flow is:
1. The sponsor signs the LI.FI intent-compatible lock and sends it to the LI.FI order server.
2. The LI.FI order server preliminarily validates the order and obtains the allocator co-signature for the order. It is then broadcasted to solvers.
3. A solver submits the order's output to the output settlement contract, starting the oracle system.
[Output Settlement](/lifi-intents/architecture/output-settlement)
is denoted as the `output.settler`.
4. The proof is delivered to the input chain through the validation layer.
[Oracle system](/lifi-intents/architecture/oracle-systems)
is denoted as the `localOracle` and `output.oracle` in the order struct.
5. The solver submits the order to the input settlement contract, verifying the delivery and unlocking the associated input tokens.
### Order Types
LI.FI uses three order structures, listed from least to most verbose:
1. `BatchClaim`: A signed intent that allows for claiming input assets.
2. `StandardOrder`: An on-chain definition containing enough information to convey the intent.
3. Order Server response: A hydrated order with additional information that may be helpful to solvers.
Unless you are verifying order signatures, you won't interact with `BatchClaim`. All on-chain interactions use `StandardOrder` to standardize interfaces. However, `StandardOrder` does not contain enough information to fill intents. As a result, it is hydrated off-chain with `InputSolver`, `signatures`, and more.
### Collecting Orders
LI.FI Intents offers two ways to receive orders:
1. **Recommended**: Connect to the order server via WebSocket.
2. **Alternative**: Monitor on-chain deposits through the deposit interface.
The order server is the recommended integration surface for most solvers. However, if you are interested in the lowest latency, decentralized, and permissionless order discovery, you can read some orders on-chain. Not all orders will be discoverable on-chain. From either source, you will receive a `StandardOrder` and other fill details to be submitted across all VMs:
```solidity theme={"system"}
struct StandardOrder {
address user;
uint256 nonce;
uint256 originChainId;
uint32 expires;
uint32 fillDeadline;
address inputOracle;
uint256[2][] inputs;
MandateOutput[] outputs;
}
```
Where `uint256[2][] inputs === [uint256 tokenId, uint256 amount][]` and `MandateOutput` is:
```solidity theme={"system"}
struct MandateOutput {
bytes32 oracle;
bytes32 settler;
uint256 chainId;
bytes32 token;
uint256 amount;
bytes32 recipient;
bytes call;
bytes context;
}
```
For more information on collecting orders, [View Order Collection Guide →](/lifi-intents/for-solvers/orderflow)
## Filling Intents
To fill intents, all `MandateOutput`s must be executed. Each `MandateOutput` is a self-contained execution description.
* `output.settler` defines the execution with its associated context and should be called on its interface. Currently, only one OutputSettler is implemented: [`OutputSettlerSimple.sol`](https://github.com/openintentsframework/oif-contracts/blob/main/src/output/simple/OutputSettlerSimple.sol), which has two interfaces for filling:
```solidity theme={"system"}
/// @notice For implementing own batching functionality
function fill(
uint32 fillDeadline,
bytes32 orderId,
MandateOutput calldata output,
bytes32 proposedSolver
) external returns (bytes32 actualSolver);
/// @notice For batch filling entire orders.
function fillOrderOutputs(
uint32 fillDeadline,
bytes32 orderId,
MandateOutput[] calldata outputs,
bytes32 proposedSolver
) external;
```
For more information about filling intents [View Filling Orders Guide →](/lifi-intents/for-solvers/filling-orders)
## Validating Fills
After filling intents, the proof of fill has to be sent to the input chain. The oracle system used for the order is specified through `localOracle` and `output.oracle`. These should match. However, validating filled outputs is highly oracle specific. For more, see [Oracle System Architecture](/lifi-intents/architecture/oracle-systems#implemented-validation-interfaces).
## Settling Orders
Lastly, intents have to be settled before their expiry. This is achieved by calling `finalise[withSignature]` on the designated InputSettler. The caller has to be the designated solver set on fill.
```solidity theme={"system"}
struct SolveParams(
uint32 timestamp;
bytes32 solver;
)
// For the Compact Input Settler
function finalise(
StandardOrder calldata order,
bytes calldata signatures,
SolveParams[] calldata solveParams,
bytes32 destination,
bytes calldata call
) external;
// Or for the Escrow Input Settler
function finalise(
StandardOrder calldata order,
SolveParams[] calldata solveParams,
bytes32 destination,
bytes calldata call
) external;
```
For more information about settling orders [View Order Settlement Guide →](/lifi-intents/for-solvers/settlement)
# Collecting Orders
Source: https://docs.li.fi/lifi-intents/for-solvers/orderflow
LI.FI Intents delivers real-time order streams and on-chain discovery, ensuring you never miss a cross-chain fill and enabling proactive, data-driven solver strategies.
The order server is a helpful intermediary that allows users to submit intents (orders) and broadcasts those intents to solvers. It serves as a central hub that:
1. Collects user intents from various sources.
2. Broadcasts these intents to connected solvers in real-time.
3. Tracks the on-chain status of all transactions.
While the order server is the recommended integration surface due to its built in security aids, it is important to note that – *for on-chain orders* – the order server **is entirely optional**. However, not all intents are emitted on-chain and [lintent.org](https://lintent.org) does not emit intents on-chain. Intents emitted on-chain needs to be hydrated and very thoroughly validated.
## Order subscription
There are 2 ways to collect orders. Either through a websocket subscription where orders will be pushed or via a GET endpoint. It is recommended to get orders pushed via the websocket subscription. Both methods can be found in the [lintent.org](https://lintent.org) demo [implementation](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/api.ts#L75-L114).
If you use the GET endpoint too aggressively, you may be throttled or temporarily timed out. You can find swagger documentation on api interfaces on [https://order-dev.li.fi/docs](https://order-dev.li.fi/docs).
You may receive the same order more than once through the WebSocket interface. This can happen every time an intent is ingested from a source of truth. Secondary emissions can contain additional context. When consuming WebSocket intents, use the orderId to identify intents.
### Order Server events
When connected to the order server, the order server will continuously send a ping messages to which the client is expected to respond with a `pong`. This is used to disconnect misbehaving clients and for debugging purposes. You can find an example [implementation here](https://github.com/lifinance/lintent/blob/a4aa78cd058cade732b73d83aa2843dd4e9ea24d/src/lib/utils/api.ts#L89-L95).
For order collection, the important event is `user:vm-order-submit`. The Data will be returned similarly to it was [submitted](https://order-dev.li.fi/docs#:~:text=SubmitOrderDto).
## Order Validation
LI.FI Intents allows highly customizable orders. As a result, you should add further validation to ensure you support the order being relayed to you. It is important to properly validate orders.
Below, the term whitelisted refers to trusted and validated by **you**. When a token *must* be whitelisted, it means **you** trust the token. If a validation layer is whitelisted, it means **you** trust the validation layer, etc. Whitelisted does not mean permissioned by a central entity; it means trusted by **you**.
The following is an attempt at an exhaustive list of validations that solvers must implement. While this list may seem excessive, you have likely already implemented checks for most of these.
### General Validation
1. `fillDeadline`: Ensure you have sufficient time to fill the order:
* Time to fill on the destination chain, including potential source chain finality.
2. `expiry`: Ensure you have enough time to fill, relay, and claim the order:
* Time to fill on the destination chain, including potential source chain finality.
* Time to send message validation proof to the input chain.
* Time to claim the order after validation has been delivered.
3. Validation layer: Ensure you support submitting proofs (and, if not automatic, also relaying) to the validation layer. Additionally, the `inputOracle` and `output.oracle` must be of the same validation layer.
4. Ensure you have whitelisted every `input` token. If one input token is malicious, the order may be unclaimable. Additionally, for blacklistable tokens like USDC, ensure you are not on a blacklist.
* If an order has a 0 input or output of a token you have not whitelisted, the order may not be fillable. Be careful about filling orders containing unfamiliar tokens.
5. For each output:
1. `output.chainId` is whitelisted.
2. `output.oracle` and `inputOracle` are correctly configured regarding `originChainId` and `output.chainId`. On-chain configs are immutable, so this can be done once for each chain pair.
3. `output.settler` is whitelisted.
4. `output.context` is decodable, and the order type is supported and compatible with `output.settler`.
5. `output.token` is whitelisted. Additionally, for blacklistable tokens like USDC, ensure that neither you nor the recipient is on a blacklist.
* If an order has a 0 input or output of a token you have not whitelisted, the order may not be fillable. Be careful about filling orders containing unfamiliar tokens.
6. You have sufficient tokens for `output.amount`.
7. If the output has `calldata`, ensure you can execute it and other outputs atomically. For outputs on different chains, you may have to whitelist recipients if there is `calldata`.
* On OP-chains, [CrossL2Inbox](https://specs.optimism.io/interop/predeploys.html#validatemessage) needs to be blacklisted in the entire call tree. If similar contracts exist on another chain, they also need to be blacklisted.
8. Neither `call.length` nor `context.length` are more than 65'535 bytes long.
9. Validate the context depending on order type. For Bitcoin specifically, ensure the encoded multiplier is relative to the Bitcoin value.
6. If the order has multiple outputs, ensure you can fill all outputs and the first output is set to your solver identifier. If all outputs are on the same chain, `fillBatch` can be used as a protective measure.
7. If the InputSettler has any fees, check for an imminent fee change.
Tokens inputs are provided as uint256. This is a standard format for resource locks and cross-chain addresses. For EVM, the first 12 bytes represents a potential lock tag and the address is the last 20 bytes.
### Escrow Validation
1. Ensure no input tokens are fee on transfer.
2. Ensure every `inputs` token has no set bits in its upper 12 bytes.
3. Ensure the order has already been opened, either through the `event Open` or `function orderStatus` OR you are opening the order through `function openFor` before filling the order.
### Compact Validation
1. Ensure every `input` token uses the same AllocatorId. If the lockId are different, the lock expiry should be checked for every lockId. Alternatively, check if all lockIds are equal.
2. Ensure the potential `reset period` for a resource lock extends beyond the `expiry` AND there is no active withdrawal.
3. Ensure the `allocatorID` is whitelisted. The allocator can block claims from processing (by withdrawing signatures or reusing nonces.)
* The allocatorID is part of the `lock tag` for `inputs` (first 12 bytes).
* Optionally, ensure the user has sufficient tokens. This should have been validated by the allocator, though.
4. Ensure the user provides an ECDSA signature **or** they have a whitelisted [emissary](/lifi-intents/knowledge-database/glossary#emissary) registered **or** you register the claim on-chain before filling the claim.
* Note: If any of these actions are taken, the signature can be assumed to be trusted.
* Emissary signatures cannot be registered on-chain. The emissary must be trusted not to redact a signature.
5. Validate the `allocatorData`. You may have to do an on-chain call.
6. Validate that the allocator `nonce` has not been spent previously by any user. The Order nonce is not a user nonce.
### Signature Validation
The `StandardOrder` will be used to interface all functions on the input chain. Additionally, once hydrated with a signature, it allows one to verify the validity of an order.
The `StandardOrder` struct will be signed and stored as a witness in the appropriate lock/claim structure. For TheCompact, this is:
```solidity theme={"system"}
struct BatchCompact {
address arbiter; // Associated settlement contract
address sponsor; // StandardOrder.user
uint256 nonce; // StandardOrder.nonce
uint256 expires; // StandardOrder.expires
uint256[2][] idsAndAmounts; // StandardOrder.inputs
Mandate mandate;
}
struct Mandate {
uint32 fillDeadline; // StandardOrder.fillDeadline
address inputOracle; // StandardOrder.inputOracle
OutputDescription[] outputs; // StandardOrder.outputs
}
```
To validate an order, ensure that the sponsor and allocator signatures are valid for this EIP-712 signed structure.
#### On-chain Order Broadcast
When intents are registered on-chain, they can be broadcasted on the Input Settlement contract through the [`IntentRegistered`](https://github.com/catalystsystem/catalyst-intent/blob/27ce0972c150ed113f66ae91069eb953f23d920b/src/input/compact/InputSettlerCompactLIFI.sol#L67) event:
```solidity theme={"system"}
event IntentRegistered(bytes32 indexed orderId, StandardOrder order);
```
When collecting permissionless orders, ensure they are properly validated and co-signed. The order server aids with validation, but on-chain, potential fraudulent messages can be emitted.
# Quoting Orders
Source: https://docs.li.fi/lifi-intents/for-solvers/quoting
LI.FI Intents enables efficient quoting and inventory management. Broadcast liquidity and pricing, compete on your terms, and respond to market changes instantly.
The order server delivers quotes to intent issuers. These are based on the available inventory provided by solvers. Instead of responding to each quote request, solvers can regularly push their inventory to the order server as routes.
```ts theme={"system"}
// POST /quotes/submit
interface Body {
quotes: Quote[]
}
interface Quote {
expiry: number;
fromChainId: string;
fromAsset: string;
fromDecimals: number;
toChainId: string;
toAsset: string;
toDecimals: number;
ranges: QuoteRange[];
maxToAmount?: bigint;
exclusiveFor: string;
}
interface QuoteRange {
minAmount: string;
maxAmount: string;
quote: string;
}
```
When providing quotes, there are some important considerations to keep in mind:
* **expiry** is the user-side quote expiry, not the solver-side expiry. If a user requests a 30-second intent, only quotes expiring after 30 seconds are returned.
* **fromDecimals** and **toDecimals** are not validated and are only used to translate the quote between assets as a real exchange rate.
* **maxToAmount** only needs to be provided if the solver does not want to quote their entire inventory at once. Within a single quote, this is the maximum allocation issued. If not provided, max(quoteRange.maxAmount) will be used instead.
* **ranges** is the list of descriptions of how the solver can fill assets. Quotes should include gas costs.
The following code chunk provides an example quote of USDC from Optimism to Arbitrum.
```json theme={"system"}
{
"quotes": [{
"expiry": 1757673110,
"fromChainId": "10",
"toChainId": "42161",
"fromAsset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"toAsset": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"fromDecimals": "6",
"toDecimals": "6",
"ranges": [
{
"minAmount": "10",
"maxAmount": "100",
"quote": "0.95",
},
{
"minAmount": "100",
"maxAmount": "10000",
"quote": "0.995",
},
{
"minAmount": "10000",
"maxAmount": "50000",
"quote": "0.99",
}
]
}]
}
```
**You can overwrite old quotes by sending new quotes**
To avoid solvers sending short-lived quotes, the order server will overwrite existing quotes when a new one is received. This allows you to have long-lived quotes and only update them as markets evolve. If you send a quote with an expiry in the past, it will still overwrite old quotes and immediately expire.
By broadcasting quotes in this manner, solvers can:
* Efficiently communicate their available liquidity across multiple chain and token pairs.
* Set their own pricing and fee structure.
* Define limits on transaction sizes they are willing to process.
* Update their quotes as market conditions change.
The order server uses broadcasted quotes to match user requests without querying solvers in real time, resulting in faster responses and less overhead.
Submit quotes to [`/quotes/submit`](https://order.li.fi/docs#tag/solver-api/post/quotes/submit).
## Chain & Asset Support
The order server uses quotes to determine supported chains and assets. This allows solvers to add support for new chains and assets simply by producing quotes for them.
## Trust Components
LI.FI Intent is a modular system based on four components: output settler, output oracle, input oracle, and input settler. There may be differences in how these components are interacted with. As a result, it is important for the order server to know which components solvers support.
```ts theme={"system"}
// POST /quotes/trustComponents
interface Body {
trustedComponents: Component[]
}
interface Component {
address: string;
chainId: string;
forInput: bool;
forOutput: bool;
role: "settler" | "oracle"
}
```
Some address may be usable as both input and output component, thus it has to be provided explicitly where the compatibility is. Some common observations:
* Some oracles have different ways to fetch proofs depending on the origin chain. If an oracle has `forInput` as true, it means the solver knows how to deliver proofs. If an oracle has `forOutput` as true, it means the solver knows how to generate proofs. For oracles with no `inputOracle`(e.g., Polymer), use their`outputOracle` as descriptor.
* Input settlers and output settlers are not the same contract type. In most cases, either only `forInput` or only `forOutput` will be set.
More roles may be added in the future.
## Authentication
Solver API endpoints require an API key passed via the `api-key` header. Authentication links your API requests to your on-chain execution, allowing the order server to assign a reputation score.
See [Authentication](/lifi-intents/authentication#solvers-api-key-required) for registration and key management details.
# Reputation
Source: https://docs.li.fi/lifi-intents/for-solvers/reputation
The LI.FI Order Server tracks solver performance and assigns each solver a score. This score helps improve user experience through better solver selection.
To prevent competing solvers from causing failed transactions and wasting gas, the Order Server allows intent issuers to specify **solver exclusivity** for their issued intents.\
This process is handled through solver selection and quoting. You can read more about quoting in the [quoting section](/lifi-intents/for-solvers/quoting).
To access the reputation system and monitor solver performance, the first step is to sign up for the Solver UI:
* **Production**: [https://intents.li.fi/](https://intents.li.fi/)
* **Testnet**: [https://devintents.li.fi/](https://devintents.li.fi/)
***
### Solver Setup
After signing up on the dashboard, users must configure their **solver identity** by setting a solver name.\
This identity will be used to create API keys and participate in the solver marketplace.
***
### API Keys
Once a solver identity is established, users can create and manage **API keys**. Pass your key via the `api-key` header on all solver endpoints. See [Authentication](/lifi-intents/authentication#solvers-api-key-required) for details.
API keys are required for:
* **Pushing Quotes**: Submit inventory quotes to the Order Server using the [`/quotes/submit`](https://order.li.fi/docs#tag/solver-api/post/quotes/submit) endpoint.
* **Registering Solver Accounts**: Registering solver accounts is required for reputation tracking.
***
### Registering Addresses
To register solver accounts, the solver must sign a message to prove ownership and then submit this pairing to the Order Server.\
The Order Server supports both **ECDSA** and **EIP-1271** signatures.
Submit the signed message, signature, and address to:\
[`/solver-api/account/register`](https://order.li.fi/docs#tag/solver-api/post/solver-api/account/register)
#### Example Response
```json theme={"system"}
{
"id": 1,
"address": "0x1234567890123456789012345678901234567890",
"solverId": 5,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
```
#### Possible Errors
* `400 Bad Request`: Invalid request data
* `401 Unauthorized`: Invalid signature or API key
* `403 Forbidden`: Address already registered
* `404 Not Found`: Solver not found
Each address can only be registered to one API key. However, a single API key can have multiple addresses registered.
***
### Performance Monitoring
The dashboard provides key metrics, including:
* Orders quoted
* Quotes submitted
* Orders filled
* Current reputation score
***
## Solver Selection
Solvers submit quotes to the Order Server containing details such as price, route, expiry, and their desired `exclusiveFor` value.
When an intent issuer requests a quote, the Order Server evaluates all submitted solver quotes and selects the best one(s).
The final selection is based on a combination of **price** and **reputation**, ensuring that the chosen solver aligns with the `exclusiveFor` parameter of the returned quote.
***
### Reputation
A solver’s reputation reflects its **reliability** and **fill performance**:
* **Reliability**: How often does the solver successfully fill a quote once assigned?
* **Fill Performance**: How quickly does the solver complete fills compared to others?
Price competition is evaluated separately from reputation, allowing both quality and efficiency to influence solver selection.
# Settling Orders
Source: https://docs.li.fi/lifi-intents/for-solvers/settlement
LI.FI Intents streamlines finalization and settlement, closing the loop on cross-chain orders and giving you confidence and control over the entire lifecycle.
To settle LI.FI Intents orders, up to 2 transactions may be required:
1. Submission of message validation
2. Finalizing the order on the resource lock.
## Oracle Validation
For instructions on how to relay proof for oracle systems, please refer to their section in [validation](/lifi-intents/architecture/oracle-systems).
## Finalizing Orders
Once the `OutputProven` event has been observed, the output has been validated and `finalise` can be called.
```
event OutputProven(uint256 chainid, bytes32 remoteIdentifier, bytes32 application, bytes32 payloadHash);
```
Depending on the settler used, you either have to call a finalise function with a different abi. `InputSettlerCompact` uses a signature required finalise function where `InputSettlerEscrow` does not.
For the `InputSettlerCompact` submit the `StandardOrder`, the attached signatures as `bytes.concat(sponsor, allocator)`, the timestamps of the fills (which can be read from the `OutputFilled` event), and the solver's identifier. If there are multiple outputs, always index lists by their position in the order. Then, finalize can be called. The flow is the same for `InputSettlerEscrow` except no signatures are provided.
```solidity theme={"system"}
struct SolveParams(
uint32 timestamp;
bytes32 solver;
)
// For the Compact Input Settler
function finalise(
StandardOrder calldata order,
bytes calldata signatures,
SolveParams[] calldata solveParams,
bytes32 destination,
bytes calldata call
) external;
// Or for the Escrow Input Settler
function finalise(
StandardOrder calldata order,
SolveParams[] calldata solveParams,
bytes32 destination,
bytes calldata call
) external;
```
If an order contains multiple outputs and two solvers filled different outputs, then the solver of the first output is the canonical solver.
### Signature Finalise
The solver must be the caller of `finalise`, as they can redirect the funds and modify the optional callback. For some solvers, this is not be possible, i.e. when the solver is set to a pool. In that case, `finaliseWithSignature` can be used with an EIP-712 `AllowOpen` signature:
```solidity theme={"system"}
struct AllowOpen {
bytes32 orderId;
bytes32 destination;
bytes call;
}
```
This allows anyone with the signature to call the Input Settler finalise function with the signed destination and optionally call.
### Callback
Some solvers may use callbacks for configuring received funds. The `call` parameter of `finalise[WithSignature]` forwards the provided payload to `destination` via `orderFinalised`:
```solidity theme={"system"}
function orderFinalised(
uint256[2][] calldata inputs,
bytes calldata call
) external
```
It is required that the `orderFinalised` call does not fail.
For a same chain intent, `orderFinalised` is executed after inputs are paid to `destination` but before outputs proofs are validate. This allows solvers to collect intent inputs before filling the intents in an atomic transaction.
# Testing Integration
Source: https://docs.li.fi/lifi-intents/for-solvers/testing-integration
Set up your solver identity, register addresses, and test your integration on testnet or mainnet before going live.
## Step 1: Account Creation & Identity
Currently, solver identity is managed through a standalone dashboard.
1. **Register:** Go to the Solver UI: [intents.li.fi](https://intents.li.fi/) (or [devintents.li.fi](https://devintents.li.fi/) for testnet).
2. **Set Identity:** Choose a **Solver Name**. This is a permanent identifier used for your API keys and reputation tracking.
3. **Generate API Keys:** Create an API key in the dashboard. You will need this to submit quotes to the [`/quotes/submit`](https://order.li.fi/docs#tag/solver-api/post/quotes/submit) endpoint.
***
## Step 2: Address Registration
You must prove ownership of the wallet addresses you will use to fill intents.
1. **Sign Message:** Use your solver wallet to sign the registration message provided by the Order Server (supports ECDSA or EIP-1271).
2. **Submit Pairing:** Send the signature and address to the registration endpoint:
* [`POST /solver-api/account/register`](https://order.li.fi/docs#tag/solver-api/post/solver-api/account/register)
Each address can only be tied to one API key. If it is not possible to sign messages from your solver, the LI.FI team can manually add a solver address.
***
## Step 3: Testnet/Mainnet Testing (ExclusiveFor Mode)
Before being whitelisted for the LI.FI API, you must prove your execution logic works on mainnet. You can test using either the Lintent UI or the LI.FI Intents MCP Server — both are equally valid approaches.
### Manual Testing via Lintent
1. **Open Lintent:** Go to [lintent.org](https://lintent.org/).
2. **Configure Intent:** Set up a swap/bridge intent. Use "Escrow" input instead of "The Compact".
3. **Set Exclusivity:** Manually enter your registered solver address into the `exclusiveFor` field. This ensures only your solver can fill this specific intent.
4. **Execute:** Issue the intent and verify that your solver picks up the quote and completes the fill.
### Programmatic Testing via MCP
The [LI.FI Intents MCP Server](/lifi-intents/mcp-server/overview) gives AI assistants the ability to manage cross-chain escrow orders and solver operations through natural language — replacing the manual Lintent UI workflow with programmatic access.
Use the same `exclusiveFor` approach, but driven by AI tools instead of a web UI:
1. Connect the MCP server to your AI tool (see [Installation](/lifi-intents/mcp-server/installation))
2. Use `request-quote` to get pricing for your test swap
3. Use `prepare-order` and `submit-order` to issue the intent
4. Monitor with `track-order` and verify your solver completes the fill
Quickstart, architecture, and environments
Setup for Claude, Cursor, VS Code, and more
Full reference for all 13 tools
Workflows, prompts, and settlement flow
# Intent / Solver Marketplace
Source: https://docs.li.fi/lifi-intents/introduction
LI.FI's intent marketplace for cross-chain and same-chain transfers, where the order server matches user intents against solver standing quotes.
LI.FI Intent / Solver Marketplace helps wallets and applications execute same-chain and cross-chain token transfers through a solver network. Solvers publish standing quotes (price curves for routes and amount ranges), and the order server matches intents to available solver inventory.
LI.FI Intent/Solver Marketplace is the official foundation of the [Open Intents Framework (OIF)](https://openintents.xyz/), an open-source initiative led by the Ethereum Foundation for permissionless interoperability.
***
## What You Get as an Integrator
* **Competitive pricing.** Intents are matched against solver standing quotes and routed to the best available fill path.
* **Fast settlement.** Solvers use their own capital to deliver assets on the destination chain before settlement completes.
* **Same-chain and cross-chain support.** Route availability updates continuously based on active solver inventory.
* **Optional gasless order submission.** Off-chain order submission is gasless, while funding actions (approval/deposit into Escrow or Compact) are still on-chain transactions.
* **Calls on delivery.** Attach calldata to intents for custom logic after asset delivery.
***
## How It Works
1. **User expresses an intent.** The user deposits tokens into an input settler (escrow or Compact resource lock), then defines the desired output outcome without specifying execution details.
2. **Intent is distributed.** The lock generates a proof of deposit, which is broadcast to the order server and solver network.
3. **Intent is matched.** The order server matches the intent against existing solver quotes (standing inventory), not live per-intent RFQ bidding.
4. **Solver delivers.** The winning solver delivers the requested assets on the destination chain.
5. **Verification and settlement.** The output settler records the fill and generates attestations; the oracle/input settler verifies delivery and releases locked funds.
***
## Supported Chains
Same-chain intents are live across all supported chains. Coverage includes major pairs and a broad long-tail token set (including Ondo assets). For the most up-to-date chain support, call [`GET /chains/supported`](https://order.li.fi/docs#tag/bridge-api/GET/chains/supported). Testnet chains are also available on the same endpoint.
## Supported Routes
Route availability updates continuously based on active solver quoting. For the full route list including token-level detail, call [`GET /routes`](https://order.li.fi/docs#tag/bridge-api/GET/routes).
***
## Authentication
Integrator endpoints are fully open with no API key or rate limits. Solvers must register for an API key via the solver UI. See [Authentication](/lifi-intents/authentication) for details.
***
## Key Concepts
| Concept | Description |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Intent** | A user request that expresses the desired outcome without specifying execution details |
| **Solver** | An entity that publishes standing quotes and fulfills intents using its own capital and liquidity |
| **Input Settler** | Umbrella term for source-side settlement contracts that release locked funds after verified delivery |
| **Escrow (InputSettlerEscrow)** | Per-intent lock model where user funds are escrowed per order |
| **Compact (InputSettlerCompact)** | Deposit-based model where users can fund once and issue multiple intents from allocated balances |
| **Order Server** | Off-chain matching layer that maps intents to available solver quotes and distributes order flow |
| **Output Settler** | Destination-side contract that accepts solver fills and generates attestations used for verification + settlement |
| **Oracle** | Verification layer that proves delivery and enables settlement on the source chain. For same-chain intents, the output settler itself (OutputSettlerSimple) acts as the oracle. No cross-chain proof transmission is needed. |
| **StandardOrder** | Canonical on-chain order struct used for intents |
| **`fillDeadline` vs `expires`** | `fillDeadline` is solver fill cutoff; `expires` is final deadline after which refunds can be processed |
| **Order types** | Supported order types include limit, exclusive limit, Dutch auction, and exclusive Dutch auction |
For full definitions, see the [Glossary](/lifi-intents/knowledge-database/glossary).
***
## Next Steps
Request a quote, submit an order, and track it in under 5 minutes
Endpoints, base URLs, authentication, and order lifecycle
Solver endpoints, quoting, and filling orders
Settlers, oracles, and contract addresses
# Bitcoin Primer
Source: https://docs.li.fi/lifi-intents/knowledge-database/bitcoin-primer
What is Bitcoin? What is a Bitcoin Block? What is a Bitcoin transaction? And how do LI.FI intent prove Bitcoin fulfillments? This page contains all the information required to understand how LI.FI intent can interact with BTC.
Bitcoin is regarded as the original blockchain or cryptocurrency. While the last core upgrade to the Bitcoin network was in 2010, several smaller upgrades have been made adjacent to the core protocol. The most significant of these are [Segwit](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) and [Taproot](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki).
### Bitcoin Blocks
A Bitcoin block can be thought of as a container attesting to the validity of several transactions. It starts with `0xD9B4BEF9` and then continues describing the new state of the network, including listing all newly included transactions. The Bitcoin block also describes a block header. The block header is a self-contained description of the newly added state. If you only care about a subset of all transactions in a block, the block header is a more efficient description of the block itself.
For the purpose of validating transactions outside the *noise* of the core network, block headers are perfect. Satoshi Nakamoto designed block headers to be self-describing; that is, if you have a list of block headers, it is possible to verify if a new block header belongs to the list. A block header is 80 bytes and consists of:
`Version(4B) | PrevBlock(32B) | MerkleRoot(32B) | Time(4B) | Bits(4B) | Nonce(4B)`
> [https://en.bitcoin.it/wiki/Block\_hashing\_algorithm](https://en.bitcoin.it/wiki/Block_hashing_algorithm)
By checking if the hash of the Bitcoin hash is sufficiently *low* compared to the specified `Bits`, the header can be authenticated as correctly mined. By checking if `PrevBlock` is the same hash as the leading transaction in your list, it can be verified to extend your list. Lastly, `Bits` must be checked to ensure it follows the difficulty rules.
You will have noticed that these checks do not assert any validity of the included transactions within. The performed checks can be viewed as the least amount of work required to authenticate a Bitcoin block. This technique is very fittingly called Simplified Payment Validation.
### Bitcoin Transaction
This section has not been written yet.
#### Transaction Outputs
Transaction outputs contain the spending conditions written in Bitcoin Script. Legacy transactions contain the entirety of the spending condition within the output itself, while Segwit transactions place the spending condition in the witness and only store the hash of it in the output. The Bitcoin blockchain itself has no concept of addresses; instead, output scripts have been standardized into 7 defined transaction types, with 5 still in general use today. The 2 that are generally not used anymore are P2PK and P2MS.
While non-standard scripts may be spendable by a user's private key, they are unlikely to be recognized by their wallet. Additionally, most custom scripts are implemented through P2SH to allow wallets to pay into it.
Each standardized transaction type describes what the output looks like. The script below is a legacy `P2PKH` output script:
> `OP_DUP | OP_HASH160 | PUSH_20 | {publicKeyHash} | OP_EQUALVERIFY | OP_CHECKSIG`
If you need to pay to a `P2PKH` address, the output script needs to have the above format. Additionally, `publicKeyHash` defines who the spender is. To fully generate an output script, you thus need the target `publicKeyHash`. This is what the address is. A `P2PKH` address is `00 + publicKeyHash` encoded with `Base58Check`. A Bitcoin address has 2 purposes:
1. Identify which output script needs to be used.
2. Identify which variable elements need to be filled.
##### UTXO Type Table
The table below enumerates the 5 transaction types from 1 to 5.
Version
Name
Encoding Scheme
Prefix
Hash Length
0
Unknown
Ignore
1
P2PKH
Base58Check(00+PKH)
1\*
20
2
P2SH
Base58Check(05+SH)
3\*
20
3
P2WPKH
Bech32
bc1q\*\*
20
4
P2WSH
Bech32
bc1q\*\*
32
5
P2TR
Bech32m
bc1p\*\*
32
\* Prefix is determined by the encoding scheme.
\
\*\* Part of the prefix – 1q/1p – is determined by the encoding scheme.
#### Transaction Inputs
Transaction inputs link to other transactions' outputs along with the unlock conditions fulfilled. For a `P2PKH` transaction, this is the public key & signature of the transaction.
Importantly, the sum of all inputs must be greater than the outputs. The difference between the two is the fee and will be claimed by the miner.
## Proving Bitcoin Transactions
This section has not been written yet.
## Confirmations
SPV clients are not safe at 1 confirmation; it is required that multiple blocks are built on top. This is because anyone can mine a transaction that passes all SPV checks but contains fraudulent transactions. As a result, an SPV client is at best as good as the blocks built on it.
Additionally, the SPV client used does not validate the actual difficulty adjustments. Instead, it verifies the 1/4 law. As a result, each block shall only be assumed to hold 1/4 of the validation power of a fully verified Bitcoin block. As a rule of thumb, the table below can be used to map value to the number of confirmations.
| Size | Conf. |
| --------------- | ----- |
| \$0k - \$20k | 2 |
| \$20k - \$100k | 3 |
| \$100k - \$200k | 4 |
| \$200k - \$1m | 5 |
| \$1m+ | 6 |
Note that starting from 5 confirmations, you get full Bitcoin security as 2 Bitcoin blocks will always reorg the chain to the proper difficulty (assuming the minority chain isn't being mined with 51% of the mining power).
# Glossary
Source: https://docs.li.fi/lifi-intents/knowledge-database/glossary
Intent systems and cross-chain bridges use many specialized terms. This page explains arbiters, allocators, sponsors, locks, GMPs, settlers, and more.
## Arbiter
An entity, contract, or mechanism capable of determining when the desired action of a resource lock has been fulfilled. It may also act as an allocator.
## Allocator
An entity that validates that a lock does not exceed a user’s current balance. It may also act as an arbiter. When depositing into a lock, an allocator is chosen.
## emissary
For smart wallets unable to provide stable signatures, an emissary can be used to produce stable signatures. The emissary is usually a trusted entity, meaning that once a signature is issued it cannot be redacted. The terminology is specific to The Compact.
## Fill First
A swap flow where the tokens are delivered to the user *before* the input tokens are collected by a protocol. These flows use specialized wallet techniques like [resource locks](#resource-lock) to remain safe.
## Output Settler
A contract that accepts solver fills on destination chains and generates attestations used by oracle/input-settlement verification.
## GMP
Generalized Message Passing. Describes a method to send messages between two chains. Examples include Wormhole, LayerZero, Hyperlane, and more.
## Lock
An allowance issued by a user to an arbiter. Before locks are valid, they must be signed by the user and co-signed by the allocator.
## Input
The starting point. Input assets refer to the assets paid into the intent system. The input chain refers to the chain(s) of the input assets.
## Integrator
See [Intent Issuer](#intent-issuer).
## Intent
An issuance of a desired action. Intents are often used to describe swaps but can also describe desired cross-chain interactions. Unlike swaps, intents should generally be composable. Intents are usually self-contained, meaning they describe both the desired end state and the payment to achieve that state; the output and input, respectively.
## Intent Issuer
Someone who issues intents for a cross-chain intent system. The intent issuers specifies how the intent is configured. It is then up to [solvers](#solver) to determine whether or not they will fill the intent.
## Optimistic
A validation method that assumes statements are true unless disputed.
## Oracle
A contract that verifies whether solver delivery conditions were met so settlement can proceed. For same-chain intents, the output settler can be used directly as the oracle path. Oracles may use optimistic systems, messaging bridges, light clients, and other mechanisms.
## Order Server
A server that sits between intent issuers and solvers aiding with broadcasting and distribution of intents.
## Output
The endpoint. Output assets refer to the desired tokens to be paid to the user (or used within a larger action). The output chain refers to the chain(s) of the output assets.
## Resource Lock
An umbrella concept for lock-based input settlement mechanisms (for example escrow and Compact) that hold user funds until settlement conditions are met. You can find a primer in our [knowledge hub](/lifi-intents/knowledge-database/resource-locks).
## Input Settler
A contract on the origin chain that verifies proofs of delivery and releases locked funds to the solver. In a resource-lock-based system, the input settler often acts as an arbiter.
## Solver
A specialized third party who fulfills cross-chain intents using a variety of liquidity sources. They participate in the intent system with the goal of earning margins from the difference between the cost of achieving the end state and the inputs.
## Sponsor
The owner of the tokens to be used for locks in a resource lock.
## Validator Layer
An entity capable of validating whether certain information has occurred. Examples include GMP, optimistic proof, or bridges.
# Resource Locks
Source: https://docs.li.fi/lifi-intents/knowledge-database/resource-locks
LI.FI Intents use secure, flexible resource locks to enable trust-minimized cross-chain flows and new ways to design intent-based protocols.
Resource locks allow users to commit tokens to something or someone without making an on-chain transaction to issue the lock or recover the lock after its expiry. In a resource-lock context, it is important that these tokens are inaccessible for a certain period, longer than the expected settlement time for a claim.
In a sense, they are a form of credit account allowing users to provide guaranteed allowances to protocols.
## Actors
In a resource lock flow, there are three actors:
1. Sponsor, user, owner: The actor that initially owns the funds in the resource lock.
2. Allocator, manager, credible commitment: The actor that validates that no conflicting locks are issued.
3. Arbiter, validator, settlement: The actor that is capable of validating whether a lock has been resolved and can be paid.
Depending on the resource lock system, each user chooses an allocator, and each lock chooses the arbiter. When the term "the arbiter" is used, it refers specifically to the arbiter of a lock, not to a general single arbiter that the user has chosen.
Think of a lock as a time-bound approval, co-signed by an allocator, to a specific protocol (arbiter).
While resource lock flows can look very different depending on the application, they generally follow four steps:
The **sponsor** makes a deposit into a resource lock, if one does not already exist.
The **sponsor** signs a lock that describes a desired outcome.
The **allocator** ensures that the appropriate funds exist for the lock to be valid. In other words, if the sponsor has deposited 10 tokens, no set of approved locks shall exceed 10 tokens. The allocator then co-signs the lock.
The desired event takes place.
The **arbiter** validates that the event has taken place and releases the token to the appropriate recipient.
In this flow, two signatures and one transaction – ignoring the 0th step – are required. The lock has to be signed by the sponsor and co-signed by the allocator. Then the arbiter makes the final call on whether the desired event took place.
## Resource-Locked Intents
In an intent system, the third step – the execution of the desired event – is usually performed by a fourth actor, often referred to as either the **solver** or the **relayer**.
Additionally, there are generally two ways to build intent systems around resource locks:
##### Intent system as *executor* (Tribunal)
In these flows, the intent system describes how the action takes place on the destination chain. However, the system does not directly validate the resource lock completion.
These systems instead rely on **allocators as arbiters** or resource lock support by **general message passing** (GMP) protocols.
The advantage of such a system is its simplicity. The third party (whether allocator or GMP) generally handles the entire validation/settlement pipeline, and thus solvers only have to concern themselves with filling orders.
##### Intent system as *arbiter* (OIF)
In these flows, the intent system is the arbiter and usually describes how the entire system is constructed: output settlement, validation, and input settlement. This makes the system more flexible and customizable.
While these systems may also rely on GMPs, they can also use other – potentially cheaper – validation layers like optimistic oracles, storage proofs, or secret reveals (like HTLCs).
The advantage of such a system is indirectly its complexity. Since more logic is explicitly defined by the system itself, the expressivity of intents can be greater and more specialized.
For more on the resource locks OIF supports, read the chapter on [Input Settlement Implementations](/lifi-intents/architecture/input-settlement).
## Trust Assumptions
In a resource lock system, the actors need to trust each other, but generally, no single actor can independently access any funds.
* The sponsor needs to trust the arbiter such that **issued** locks are not fraudulently *finalized*. However, the arbiter can never access any of the sponsor's funds without a relevant signed lock. The trust assumptions between the sponsor and the arbiter are equivalent to permit/permit2 approvals.
* The arbiter/solver needs to trust that the allocator does not co-sign overlapping locks that exceed the tokens the user has deposited. Otherwise, a filled intent may not have enough tokens available to resolve the payment to the solver for the fulfillment of the lock.
# Examples
Source: https://docs.li.fi/lifi-intents/mcp-server/examples
Example workflows, prompts, and end-to-end settlement flow for the LI.FI Intents MCP Server
## Example Workflows
### Cross-Chain Swap
A full order lifecycle from quote to settlement:
```
1. get-supported-routes # Discover available routes
2. request-quote # Get pricing (chain names, symbols, or IDs all work)
3. prepare-order # Build order, auto-sign if key is configured
4. submit-order # Submit to order server
5. track-order # Monitor: Submitted -> Open -> Signed -> Delivered -> Settled
```
### Solver Operations
Managing your solver presence and diagnosing issues:
```
1. register-solver # Register wallet for reputation tracking
2. submit-standing-quotes # Publish pricing for routes
3. get-quote-inventory # Audit your published quotes
4. check-route-health # Diagnose why orders aren't being filled
5. debug-order # Inspect a specific order's full lifecycle
```
***
## Example Prompts
These prompts work with any MCP-compatible AI tool connected to the LI.FI Intents MCP server.
### Discover Routes
**Prompt:** "What cross-chain routes are available on LI.FI Intents?"
**Tool called:** `get-supported-routes`
Returns available chain + token pairs with fees, activity status, and supported settlement methods.
### Get a Quote
**Prompt:** "Get me a quote for 5 USDC from Ethereum to Base for 0x42fB...749b"
**Tool called:** `request-quote` with chain names, token symbol, and recipient address. The server handles EIP-7930 encoding internally — no need to convert amounts to smallest units.
### Submit an Order
**Prompt:** "Prepare and submit an order from that quote"
**Tools called:**
1. `prepare-order` — builds the `StandardOrder` from the cached quote
2. The AI prompts you to sign the order using EIP-712
3. `submit-order` — submits the signed order to the order server
### Check Route Health
**Prompt:** "Check the health of the Ethereum Sepolia to Base Sepolia USDC route"
**Tool called:** `check-route-health` — runs composite diagnostics including route support, quote inventory, and recent activity. Useful for diagnosing why orders aren't being filled.
### Debug an Order
**Prompt:** "Debug order intent\_YaY\_b3qxF3..."
**Tool called:** `debug-order` — returns the order's full lifecycle, transaction hashes on both chains, solver info, and settlement status.
***
## End-to-End Settlement Flow
To complete a full cross-chain swap (not just submit an order, but actually move tokens on-chain), you need to:
1. **Approve the escrow settler** to spend your tokens
2. **Request a quote, prepare, and submit** the order via MCP
3. **Deposit funds** into the escrow contract on-chain
Once deposited, a solver picks up the order, delivers tokens on the destination chain, and the settlement completes automatically.
### Step 1: Token Approval
Approve the escrow settler contract to spend your input token. The escrow settler address is the same on all chains:
```
0x000025c3226C00B2Cdc200005a1600509f4e00C0
```
Using viem/TypeScript:
```typescript theme={"system"}
import { createWalletClient, http, parseAbi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains"; // or baseSepolia for testnet
const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const client = createWalletClient({ account, chain: mainnet, transport: http("YOUR_RPC_URL") });
const tx = await client.writeContract({
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // token address (e.g. WETH on mainnet)
abi: parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]),
functionName: "approve",
args: [
"0x000025c3226C00B2Cdc200005a1600509f4e00C0", // escrow settler
BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // max approval
],
});
```
### Step 2: Submit Order via MCP
Use the MCP tools in sequence:
```
1. request-quote -> Get pricing (e.g., "0.001 WETH from Ethereum to Base")
2. prepare-order -> Build + sign the order (auto-signs if SIGNER_PRIVATE_KEY is set locally)
3. submit-order -> Submit to the LI.FI order server
```
After submission, the order status is `Signed` — but funds are not yet locked on-chain.
### Step 3: Deposit into Escrow
Call `open()` on the escrow settler contract to lock your tokens. This triggers a `transferFrom` using your prior approval.
```typescript theme={"system"}
import { encodeFunctionData, createWalletClient, http, parseAbi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
const ESCROW_SETTLER = "0x000025c3226C00B2Cdc200005a1600509f4e00C0";
// The order object from prepare-order — pass the full encoded order bytes
const tx = await client.sendTransaction({
to: ESCROW_SETTLER,
data: encodeFunctionData({
abi: parseAbi(["function open(bytes calldata order) external"]),
functionName: "open",
args: [orderBytes], // the encoded order from prepare-order
}),
});
```
### What Happens Next
Once the escrow is funded:
1. **Solver sees the deposit** and delivers tokens on the destination chain
2. **Settlement** happens automatically via the Polymer oracle
3. **Order status** progresses: `Signed -> Deposited -> Delivered -> Settled`
Use `track-order` to monitor the full lifecycle. A typical mainnet settlement takes \~10 minutes.
### Testnet vs Mainnet
| | Testnet | Mainnet |
| ----------------------- | ------------------------------------------------------------------ | ------------------------------------------ |
| **Chains** | Ethereum Sepolia, Base Sepolia, Optimism Sepolia, Arbitrum Sepolia | Ethereum, Base, Arbitrum, Optimism, + more |
| **Tokens** | Testnet USDC, testnet WETH | Real USDC, WETH, ETH, etc. |
| **Solver availability** | Limited — may not have active solvers | Active solvers filling orders |
| **RPC** | Public Sepolia RPCs work | Use Alchemy/Infura for reliability |
| **MCP URL** | `https://intents-mcp-testnet.li.fi/mcp` | `https://intents-mcp.li.fi/mcp` |
# Installation
Source: https://docs.li.fi/lifi-intents/mcp-server/installation
Setup instructions for connecting the LI.FI Intents MCP Server to your AI tool
The fastest way to get started is connecting to the **hosted server**. No installation required. Below are setup instructions for each supported AI tool.
## Claude Desktop
Add to your Claude Desktop config:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
* **Linux**: `~/.config/Claude/claude_desktop_config.json`
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"type": "http",
"url": "https://intents-mcp.li.fi/mcp"
}
}
}
```
With a solver API key for full access:
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"type": "http",
"url": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "your-solver-api-key"
}
}
}
}
```
Restart Claude Desktop after saving.
## Claude Code
Add via CLI:
```bash theme={"system"}
claude mcp add lifi-intents --transport http --url https://intents-mcp.li.fi/mcp
```
Or add a `.mcp.json` file at your project root (shareable via git):
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"type": "http",
"url": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "your-solver-api-key"
}
}
}
}
```
Use `https://intents-mcp-testnet.li.fi/mcp` for testnet. Run `/mcp` to connect.
## Cursor
Add to `.cursor/mcp.json` (project-level) or `~/.cursor/mcp.json` (global):
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"url": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "your-solver-api-key"
}
}
}
}
```
## Windsurf
Add to `~/.codeium/windsurf/mcp_config.json`:
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"serverUrl": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "your-solver-api-key"
}
}
}
}
```
Windsurf uses `serverUrl` instead of `url`.
## VS Code (GitHub Copilot)
Add to `.vscode/mcp.json` in your project:
```json theme={"system"}
{
"inputs": [
{
"id": "solver-api-key",
"type": "promptString",
"description": "LI.FI Solver API Key",
"password": true
}
],
"servers": {
"lifi-intents": {
"type": "http",
"url": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "${input:solver-api-key}"
}
}
}
}
```
## Signing Orders
**The hosted server never holds private keys.** You must sign orders yourself.
1. `request-quote` — get a quote for a cross-chain swap
2. `prepare-order` — returns an **unsigned** order structure
3. **Sign the order** using EIP-712 typed data signing with your wallet (e.g. viem, ethers.js)
4. `submit-order` — submit the signed order to the order server
All orders require gas. The signer pays gas on the source chain to initiate the cross-chain escrow.
## Auto-Signing (Local Server)
If you want the server to sign orders on your behalf, you can run the MCP server locally with a signer key:
1. Set up a `.env` file with your private key:
```
SIGNER_PRIVATE_KEY=0xYourPrivateKeyHere
```
2. Start the local server — it will use the key to automatically sign orders during `prepare-order`
3. Connect your MCP client to `http://localhost:3000/mcp` instead of the hosted URL
Never share your private key or commit it to version control. The local server should only be used in trusted environments.
## Testing Your Setup
You can verify your setup by asking your AI tool a simple question like:
> "What cross-chain routes are available on LI.FI Intents?"
If the MCP server is connected correctly, the tool will call `get-supported-routes` and return a list of available chain + token pairs.
To verify solver tools are enabled, try:
> "Show my registered solver identities"
If your API key is configured correctly, the tool will call `get-solver-identities` and return your registered addresses.
# Intents MCP Server
Source: https://docs.li.fi/lifi-intents/mcp-server/overview
Connect AI tools to LI.FI Intents cross-chain escrow orders and solver operations via the Model Context Protocol
The LI.FI Intents MCP Server integrates with the [LI.FI Intents API](https://docs.li.fi/intents) to provide cross-chain escrow order management and solver operations via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
Built for **solver operators** — replaces the manual [Lintent.org](https://lintent.org) UI workflow with programmatic access to quote management, order lifecycle, and route diagnostics.
Unlike the general [LI.FI MCP server](/mcp-server/overview) (read-only quotes and routing), this server performs **write operations** — it submits signed orders, registers solvers, and manages standing quotes. The hosted server never holds private keys.
## Quickstart
Add the hosted LI.FI Intents MCP server to your AI tool. No installation required:
```json theme={"system"}
{
"mcpServers": {
"lifi-intents": {
"type": "http",
"url": "https://intents-mcp.li.fi/mcp",
"headers": {
"x-api-key": "your-solver-api-key"
}
}
}
}
```
Paste this into your MCP client config and start querying intents data. See [Installation](/lifi-intents/mcp-server/installation) for tool-specific setup instructions.
The `x-api-key` header is only required for solver tooling. If you have a Solver API key, set it as `x-api-key` in `headers`; otherwise remove the field.
## How It Works
The MCP server wraps the LI.FI Intents REST API into MCP-compatible tools that AI agents can call directly. Instead of constructing HTTP requests, your AI tool discovers and invokes structured tools like `request-quote`, `prepare-order`, and `submit-standing-quotes`.
```
AI Tool (Claude, Cursor, etc.)
|
v
MCP Protocol
|
v
LI.FI Intents MCP Server (https://intents-mcp.li.fi/mcp)
|
v
LI.FI Order Server (https://order.li.fi)
|
v
Cross-chain escrow settlement via Polymer oracle
```
## Example Workflow
A typical cross-chain swap via the MCP server follows this flow:
```
1. get-supported-routes # Discover available chain + token pairs
2. request-quote # Get pricing (chain names, symbols, or IDs all work)
3. prepare-order # Build order structure (unsigned)
4. (external) Sign the order using EIP-712 typed data signing
5. submit-order # Submit signed order to the order server
6. track-order # Monitor: Submitted -> Open -> Signed -> Delivered -> Settled
```
## API Key Configuration
Without an API key, only **integrator tools** are available (read-only operations). With an API key, solver tools are also enabled, including `submit-standing-quotes`, `debug-order`, and `check-route-health`.
1. Go to **[intents.li.fi](https://intents.li.fi)** (mainnet) or **[devintents.li.fi](https://devintents.li.fi)** (testnet)
2. Connect your wallet and register as a solver
3. Copy your API key from the dashboard
4. Add it in `"headers"` as `x-api-key` in your MCP config
Register at intents.li.fi to get your solver API key
## Environments
| Environment | MCP Server URL | Order Server | Chains |
| ----------- | --------------------------------------- | ------------------------- | ------------------------------------------------------------------ |
| **Mainnet** | `https://intents-mcp.li.fi/mcp` | `https://order.li.fi` | Ethereum, Base, Arbitrum, Optimism, + more |
| **Testnet** | `https://intents-mcp-testnet.li.fi/mcp` | `https://order-dev.li.fi` | Ethereum Sepolia, Base Sepolia, Optimism Sepolia, Arbitrum Sepolia |
## Contract Addresses
Shared across testnet and mainnet:
| Contract | Address |
| ------------------------------ | -------------------------------------------- |
| **Escrow Settler** | `0x000025c3226C00B2Cdc200005a1600509f4e00C0` |
| **Compact Settler** | `0x0000000000cd5f7fDEc90a03a31F79E5Fbc6A9Cf` |
| **Multichain Escrow Settler** | `0xb912b4c38ab54b94D45Ac001484dEBcbb519Bc2B` |
| **Multichain Compact Settler** | `0x1fccC0807F25A58eB531a0B5b4bf3dCE88808Ed7` |
| **CoinFiller** | `0x0000000000eC36B683C2E6AC89e9A75989C22a2e` |
| **The Compact** | `0x00000000000000171ede64904551eeDF3C6C9788` |
**Polymer Oracle** differs by environment:
| Environment | Oracle Address |
| ----------- | -------------------------------------------- |
| Testnet | `0x00d5b500ECa100F7cdeDC800eC631Aca00BaAC00` |
| Mainnet | `0x0000003E06000007A224AeE90052fA6bb46d43C9` |
## Next Steps
Setup instructions for Claude, Cursor, Windsurf, VS Code, and more
Complete reference for all integrator and solver tools
Example workflows, prompts, and settlement flow
Source code and self-hosting instructions
# Available Tools
Source: https://docs.li.fi/lifi-intents/mcp-server/tools
Complete reference for all tools exposed by the LI.FI Intents MCP Server
The LI.FI Intents MCP Server exposes tools organized into two categories. Without an API key, only integrator tools are available. With a solver API key, all tools are enabled.
## Integrator Tools
These tools are available to everyone — no authentication required.
### get-supported-routes
Discover available chain + token pairs with fees and activity status. Use this to verify route availability before requesting a quote.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------- |
| *none* | — | Returns all supported routes |
### request-quote
Get pricing for a cross-chain swap. Handles EIP-7930 encoding internally — you can use plain `0x` addresses and human-readable amounts.
| Parameter | Required | Description |
| ------------- | -------- | ----------------------------------- |
| `fromChain` | Yes | Source chain ID or name |
| `toChain` | Yes | Destination chain ID or name |
| `fromToken` | Yes | Source token address or symbol |
| `toToken` | Yes | Destination token address or symbol |
| `fromAmount` | Yes | Amount to swap (human-readable) |
| `fromAddress` | Yes | Sender wallet address |
The response includes a guided `message` suggesting the next tool to call — typically `prepare-order`.
### prepare-order
Construct a `StandardOrder` from a quote, optionally sign it via EIP-712. Returns an unsigned order structure if the server has no signer key.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------- |
| `quoteId` | Yes | Quote ID from a `request-quote` response |
### submit-order
Submit a signed order to the LI.FI order server.
| Parameter | Required | Description |
| ------------------ | -------- | ---------------------------------------- |
| `order` | Yes | The order object from `prepare-order` |
| `sponsorSignature` | Yes | EIP-712 signature from the order sponsor |
Solver coverage varies — submitted orders may not be picked up immediately or at all depending on available solvers for the route.
### track-order
Check order status, solver address, and transaction hashes. Status progresses: `Submitted -> Open -> Signed -> Delivered -> Settled`.
| Parameter | Required | Description |
| --------- | -------- | ----------------- |
| `orderId` | Yes | Order ID to track |
### list-orders
List orders with filters for status, user, chain, and pagination.
| Parameter | Required | Description |
| --------- | -------- | -------------------------- |
| `status` | No | Filter by order status |
| `user` | No | Filter by user address |
| `chain` | No | Filter by chain ID |
| `limit` | No | Number of results per page |
| `offset` | No | Pagination offset |
## Solver Tools
These tools require a valid solver API key passed via the `x-api-key` header.
### get-solver-identities
View registered solver addresses and reputation tracking data.
| Parameter | Required | Description |
| --------- | -------- | ----------------------------------------------- |
| *none* | — | Returns identities for the authenticated solver |
### get-quote-inventory
View uploaded quotes for a specific route. Flags expired quotes that need refreshing.
| Parameter | Required | Description |
| ----------- | -------- | ------------------------- |
| `fromChain` | Yes | Source chain ID |
| `toChain` | Yes | Destination chain ID |
| `fromToken` | No | Source token address |
| `toToken` | No | Destination token address |
### submit-standing-quotes
Submit or update pricing quotes in bulk — up to 200K quotes per call.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------------------- |
| `quotes` | Yes | Array of quote objects with route, price, and expiry |
### debug-order
Deep inspect an order's full lifecycle, including transaction hashes and solver info.
| Parameter | Required | Description |
| --------- | -------- | ----------------- |
| `orderId` | Yes | Order ID to debug |
### check-route-health
Composite diagnostics tool: checks route support + quote inventory + recent activity.
| Parameter | Required | Description |
| ----------- | -------- | ------------------------- |
| `fromChain` | Yes | Source chain ID |
| `toChain` | Yes | Destination chain ID |
| `fromToken` | No | Source token address |
| `toToken` | No | Destination token address |
### unregister-solver
Remove a registered solver address. This is a destructive operation — the tool requires confirmation before proceeding.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------- |
| `address` | Yes | Wallet address to unregister |
Unregistering a solver address removes it permanently from reputation tracking. This action cannot be undone.
## Coming Soon
These tools are planned but not yet fully supported.
### register-solver
Register a wallet address for reputation tracking. The registration flow requires a multi-step process: retrieve a registration message from the Order Server, sign it with your solver wallet (ECDSA or EIP-1271), then submit the signed message back.
| Parameter | Required | Description |
| ----------- | -------- | -------------------------- |
| `address` | Yes | Wallet address to register |
| `signature` | Yes | Ownership proof signature |
# Quickstart
Source: https://docs.li.fi/lifi-intents/quickstart
Request a quote, open an escrow order on-chain, and track it to completion with the LI.FI Intents API.
This quickstart walks you through a complete cross-chain USDC transfer (Base → Arbitrum) using the **standard escrow flow**, the recommended path for most integrations. By the end, you'll have requested a quote, approved tokens, opened an order on-chain, and tracked it to settlement.
If you need **gasless off-chain order submission** or are building on top of resource locks, see the [Compact Orders](/lifi-intents/intents-api/compact-orders) guide instead.
The entire flow uses TypeScript with [ethers.js](https://docs.ethers.org/v6/) since the escrow flow requires on-chain transactions. You'll need a provider connected to Base and a signer (wallet) with USDC.
No API key is required. All integrator endpoints are open with no rate limits. See [Authentication](/lifi-intents/authentication) for details.
***
## Prerequisites
The escrow flow involves on-chain transactions (approving tokens and calling the escrow contract to lock funds). We use [ethers.js](https://docs.ethers.org/v6/) to interact with the Base network, but any EVM-compatible library (viem, web3.js, etc.) works the same way.
```bash theme={"system"}
npm install ethers
```
```ts TypeScript theme={"system"}
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
```
The code below uses contract addresses from the [System Architecture](/lifi-intents/architecture/overview#smart-contracts) reference. These are the same across all supported chains.
***
## Escrow Flow
The standard escrow integration is a 4-step process: request a quote, approve tokens, open the order on-chain, and track it to settlement.
The Intents API uses [interoperable addresses (EIP-7930)](https://eips.ethereum.org/EIPS/eip-7930). See [Request a Quote](/lifi-intents/intents-api/request-quote) for encoding details.
Call `POST /quote/request` with the user's input and desired output. This example sends 10 USDC from Base to Arbitrum.
```ts TypeScript theme={"system"}
const userAddress = await signer.getAddress();
const userAddressRaw = userAddress.slice(2); // Remove 0x prefix
const response = await fetch('https://order.li.fi/quote/request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: `0x0001000002210514${userAddressRaw}`,
intent: {
intentType: 'oif-swap',
inputs: [{
user: `0x0001000002210514${userAddressRaw}`,
asset: '0x0001000002210514833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: '10000000', // 10 USDC (6 decimals)
}],
outputs: [{
receiver: `0x0001000002A4B114${userAddressRaw}`,
asset: '0x0001000002A4B114af88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
amount: null,
}],
swapType: 'exact-input',
},
supportedTypes: ['oif-escrow-v0'],
}),
});
const { quotes } = await response.json();
const bestQuote = quotes[0];
console.log('Output amount:', bestQuote.preview.outputs[0].amount);
```
The interoperable address prefix encodes the chain. `0x00010000022105` is Base (8453) and `0x0001000002A4B1` is Arbitrum (42161). See [Interoperable Address Encoding](/lifi-intents/intents-api/request-quote#interoperable-address-encoding) for details.
The response contains an array of quotes sorted by best price. The best quote is at index 0. Use `preview.outputs[0].amount` from the best quote when constructing your order in the next steps.
Before opening the escrow, the `InputSettlerEscrow` contract needs permission to transfer your tokens. Approve USDC on Base.
```ts TypeScript theme={"system"}
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const INPUT_SETTLER_ESCROW = '0x000025c3226C00B2Cdc200005a1600509f4e00C0';
const erc20 = new ethers.Contract(USDC_BASE, [
'function approve(address spender, uint256 amount) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
], signer);
const currentAllowance = await erc20.allowance(userAddress, INPUT_SETTLER_ESCROW);
if (currentAllowance < BigInt('10000000')) {
const tx = await erc20.approve(INPUT_SETTLER_ESCROW, '10000000');
await tx.wait();
console.log('Approval confirmed');
}
```
You can also use [Permit2](https://github.com/Uniswap/permit2) for gasless approvals. The escrow supports registration via Permit2 signatures. See [Input Settlement](/lifi-intents/architecture/input-settlement) for details.
Build a `StandardOrder` from the quote response and call `open()` on the `InputSettlerEscrow` contract. This locks your tokens and broadcasts the intent to solvers.
```ts TypeScript theme={"system"}
const POLYMER_ORACLE = '0x0000003E06000007A224AeE90052fA6bb46d43C9';
const OUTPUT_SETTLER = '0x0000000000eC36B683C2E6AC89e9A75989C22a2e';
const USDC_ARBITRUM = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
const outputAmount = bestQuote.preview.outputs[0].amount;
const order = {
user: userAddress,
nonce: BigInt(Date.now()),
originChainId: BigInt(8453),
expires: Math.floor(Date.now() / 1000) + 3600, // 1 hour
fillDeadline: Math.floor(Date.now() / 1000) + 1800, // 30 minutes
inputOracle: POLYMER_ORACLE,
inputs: [
[BigInt(USDC_BASE), BigInt('10000000')] // [tokenId (address as uint256), amount]
],
outputs: [{
oracle: ethers.zeroPadValue(POLYMER_ORACLE, 32),
settler: ethers.zeroPadValue(OUTPUT_SETTLER, 32),
chainId: BigInt(42161), // Arbitrum
token: ethers.zeroPadValue(USDC_ARBITRUM, 32),
amount: BigInt(outputAmount),
recipient: ethers.zeroPadValue(userAddress, 32),
call: '0x', // No callback
context: '0x', // Limit order (no auction)
}],
};
const escrow = new ethers.Contract(INPUT_SETTLER_ESCROW, [
'function open(bytes calldata order) external',
], signer);
const encodedOrder = ethers.AbiCoder.defaultAbiCoder().encode(
[
'tuple(address user, uint256 nonce, uint256 originChainId, uint32 expires, uint32 fillDeadline, address inputOracle, uint256[2][] inputs, tuple(bytes32 oracle, bytes32 settler, uint256 chainId, bytes32 token, uint256 amount, bytes32 recipient, bytes call, bytes context)[] outputs)',
],
[order]
);
const tx = await escrow.open(encodedOrder);
const receipt = await tx.wait();
console.log('Order opened! Tx:', receipt.hash);
const orderId = receipt.logs[0]?.topics[1];
console.log('Order ID:', orderId);
```
The `open()` call transfers your tokens into escrow and emits an `Open` event. Solvers and the order server detect this event automatically. No separate submission step is needed.
Set `fillDeadline` well before `expires`. The solver must deliver before `fillDeadline`; `expires` is the final deadline after which you can claim a refund if the order wasn't filled.
Poll `GET /orders/status` until the order reaches a terminal state. Use the `onChainOrderId` from the `Open` event.
```ts TypeScript theme={"system"}
const trackOrder = async (onChainOrderId: string) => {
let status: string;
do {
const res = await fetch(
`https://order.li.fi/orders/status?onChainOrderId=${onChainOrderId}`
);
const data = await res.json();
status = data.meta.orderStatus;
console.log(`Status: ${status}`);
if (status !== 'Settled' && status !== 'Expired') {
await new Promise(r => setTimeout(r, 3000));
}
} while (status !== 'Settled' && status !== 'Expired');
return status;
};
await trackOrder(orderId);
```
| Status | Meaning |
| ----------- | ---------------------------------------------------------- |
| `Open` | Order registered on-chain, tokens locked in escrow |
| `Signed` | Order signed and available for solver pickup |
| `Delivered` | Solver has delivered assets on the destination chain |
| `Settled` | Proof verified, locked funds released to solver. Complete. |
Once `Settled`, the user has received USDC on Arbitrum and the solver has been paid from escrow.
***
## What Just Happened
1. **Requested a quote.** The order server returned pricing from its solver network based on your input/output pair.
2. **Approved tokens.** The escrow contract was authorized to transfer your USDC.
3. **Opened the order.** Tokens were locked in escrow and the intent was broadcast to solvers via the `Open` event.
4. **Solver delivered.** A solver fulfilled the order by delivering USDC on Arbitrum.
5. **Settlement completed.** The oracle verified delivery and the escrow released the locked funds to the solver.
***
## Next Steps
Full endpoint reference, base URLs, and authentication
Exact-input, exact-output, and exclusive quote details
Off-chain gasless order submission via The Compact
On-chain events and order server status polling
# Examples
Source: https://docs.li.fi/mcp-server/examples
Example prompts, responses, and code samples for the LI.FI MCP Server
## Example Prompts & Responses
These examples demonstrate actual tool responses from the LI.FI MCP server. Use them as reference for understanding data formats and building integrations.
### Get Chain Information
**Prompt:** "What are the details for Ethereum?"
**Tool called:** `get-chain-by-name` with `name: "ethereum"`
```json theme={"system"}
{
"id": 1,
"key": "eth",
"name": "Ethereum",
"nativeToken": {
"address": "0x0000000000000000000000000000000000000000",
"symbol": "ETH",
"decimals": 18,
"name": "ETH"
},
"metamask": {
"chainId": "0x1",
"blockExplorerUrls": ["https://etherscan.io/"],
"chainName": "Ethereum Mainnet",
"rpcUrls": ["https://ethereum-rpc.publicnode.com", "https://eth.drpc.org"]
}
}
```
### Get Token Details
**Prompt:** "Get USDC token info on Ethereum"
**Tool called:** `get-token` with `chain: "1"`, `token: "USDC"`
```json theme={"system"}
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"priceUSD": "0.99969",
"marketCapUSD": 70829343624,
"volumeUSD24H": 19781184506,
"tags": ["stablecoin"]
}
```
### Same-Chain Swap Quote
**Prompt:** "Get a quote to swap 0.01 ETH to USDC on Ethereum"
**Tool called:** `get-quote` with:
```json theme={"system"}
{
"fromChain": "1",
"toChain": "1",
"fromToken": "0x0000000000000000000000000000000000000000",
"toToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"fromAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"fromAmount": "10000000000000000",
"slippage": "0.03"
}
```
**Response (key fields):**
```json theme={"system"}
{
"type": "lifi",
"tool": "sushiswap",
"toolDetails": {
"key": "sushiswap",
"name": "SushiSwap Aggregator"
},
"action": {
"fromToken": { "symbol": "ETH", "decimals": 18, "priceUSD": "2249.76" },
"fromAmount": "10000000000000000",
"toToken": { "symbol": "USDC", "decimals": 6, "priceUSD": "0.99969" },
"slippage": 0.03
},
"estimate": {
"toAmount": "22392397",
"toAmountMin": "21720625",
"fromAmountUSD": "22.4976",
"toAmountUSD": "22.3855",
"gasCosts": [{
"amount": "66216217693968",
"amountUSD": "0.1490",
"token": { "symbol": "ETH" }
}],
"feeCosts": [{
"name": "LIFI Fixed Fee",
"amountUSD": "0.0562",
"percentage": "0.0025"
}]
},
"transactionRequest": {
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x736eac0b5a28cb9d...",
"value": "0x2386f26fc10000",
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"chainId": 1,
"gasPrice": "0xa7a9b10",
"gasLimit": "0x778a1"
}
}
```
Key fields to note:
* **`estimate.toAmount`**: Expected USDC output (22.39 USDC with 6 decimals)
* **`estimate.toAmountMin`**: Minimum after slippage (21.72 USDC)
* **`transactionRequest`**: Ready-to-sign transaction object
### Cross-Chain Swap Quote
**Prompt:** "Bridge 0.01 ETH from Ethereum to USDC on Base"
**Tool called:** `get-quote` with:
```json theme={"system"}
{
"fromChain": "1",
"toChain": "8453",
"fromToken": "0x0000000000000000000000000000000000000000",
"toToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"fromAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"fromAmount": "10000000000000000",
"slippage": "0.03"
}
```
**Response (key fields):**
```json theme={"system"}
{
"type": "lifi",
"tool": "near",
"toolDetails": {
"key": "near",
"name": "NearIntents"
},
"action": {
"fromChainId": 1,
"toChainId": 8453,
"fromToken": { "symbol": "ETH", "chainId": 1 },
"toToken": { "symbol": "USDC", "chainId": 8453 }
},
"estimate": {
"toAmount": "22293079",
"toAmountMin": "21624286",
"fromAmountUSD": "22.4976",
"toAmountUSD": "22.2862",
"executionDuration": 47,
"gasCosts": [{
"amountUSD": "0.0797",
"token": { "symbol": "ETH" }
}],
"feeCosts": [
{ "name": "LIFI Fixed Fee", "amountUSD": "0.0562" },
{ "name": "NearIntents Protocol Fee", "amountUSD": "0.0022" }
]
},
"includedSteps": [
{ "type": "protocol", "tool": "feeCollection" },
{ "type": "cross", "tool": "near" }
],
"transactionRequest": {
"to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
"data": "0x3110c7b900000000...",
"value": "0x2386f26fc10000",
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"chainId": 1,
"gasPrice": "0xa7a9b10",
"gasLimit": "0x3fee7"
}
}
```
Key fields to note:
* **`executionDuration`**: Estimated \~47 seconds for cross-chain transfer
* **`includedSteps`**: Shows the route (fee collection then bridge via NearIntents)
### Check Wallet Balance
**Prompt:** "What's the ETH balance of vitalik.eth?"
**Tool called:** `get-native-token-balance` with `chain: "1"`, `address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"`
```json theme={"system"}
{
"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"balance": "32115625288281011210",
"chainId": "1",
"decimals": 18,
"tokenSymbol": "ETH"
}
```
> **Conversion:** 32115625288281011210 / 10^18 = **\~32.12 ETH**
***
## Code Samples
Build agents that connect to the LI.FI MCP server programmatically.
### Anthropic API (MCP Connector) - TypeScript
The simplest approach. Anthropic handles the MCP connection. No MCP client library needed.
```typescript theme={"system"}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.beta.messages.create(
{
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
mcp_servers: [
{
type: "url",
url: "https://mcp.li.quest/mcp",
name: "lifi",
},
],
messages: [
{
role: "user",
content: "What chains does LI.FI support? List the first 5.",
},
],
tools: [{ type: "mcp_toolset" }],
},
{
betas: ["mcp-client-2025-11-20"],
}
);
console.log(response.content);
```
### Anthropic API (MCP Connector) - Python
```python theme={"system"}
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
betas=["mcp-client-2025-11-20"],
mcp_servers=[
{
"type": "url",
"url": "https://mcp.li.quest/mcp",
"name": "lifi",
}
],
messages=[
{
"role": "user",
"content": "What chains does LI.FI support? List the first 5.",
}
],
tools=[{"type": "mcp_toolset"}],
)
print(response.content)
```
### TypeScript (MCP SDK)
```typescript theme={"system"}
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.li.quest/mcp")
);
const client = new Client({
name: "my-agent",
version: "1.0.0",
});
await client.connect(transport);
// List all available tools
const { tools } = await client.listTools();
console.log("Available tools:", tools.map((t) => t.name));
// Call a tool
const result = await client.callTool({
name: "get-chain-by-id",
arguments: { id: "1" },
});
console.log("Ethereum:", result.content);
await client.close();
```
### Python (MCP SDK)
```python theme={"system"}
from mcp.client.streamable_http import streamablehttp_client
import asyncio
async def main():
async with streamablehttp_client("https://mcp.li.quest/mcp") as (
read,
write,
_,
):
from mcp import ClientSession
async with ClientSession(read, write) as session:
await session.initialize()
# List all available tools
tools = await session.list_tools()
print("Available tools:", [t.name for t in tools.tools])
# Call a tool
result = await session.call_tool(
"get-chain-by-id", arguments={"id": "1"}
)
print("Ethereum:", result.content)
asyncio.run(main())
```
### Vercel AI SDK
```typescript theme={"system"}
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const mcpClient = await createMCPClient({
transport: new StreamableHTTPClientTransport(
new URL("https://mcp.li.quest/mcp")
),
});
const tools = await mcpClient.tools();
const { text } = await generateText({
model: anthropic("claude-sonnet-4-5-20250929"),
tools,
maxSteps: 5,
prompt: "Get a quote for swapping 0.01 ETH to USDC on Ethereum",
});
console.log(text);
await mcpClient.close();
```
# Installation
Source: https://docs.li.fi/mcp-server/installation
Setup instructions for connecting the LI.FI MCP Server to your AI tool
The fastest way to get started is connecting to the **hosted server**. No installation required. Below are setup instructions for each supported AI tool.
## Claude Desktop
Add to your Claude Desktop config:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp"
}
}
}
```
With an API key for higher rate limits:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp",
"headers": {
"X-LiFi-Api-Key": "your_api_key"
}
}
}
}
```
## Claude Code
Add via CLI:
```bash theme={"system"}
claude mcp add lifi --transport http --url https://mcp.li.quest/mcp
```
Or add a `.mcp.json` file at your project root (shareable via git):
```json theme={"system"}
{
"servers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp"
}
}
}
```
## Cursor
Add to `.cursor/mcp.json` (project-level) or `~/.cursor/mcp.json` (global):
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"url": "https://mcp.li.quest/mcp"
}
}
}
```
## Windsurf
Add to `~/.codeium/windsurf/mcp_config.json`:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"serverUrl": "https://mcp.li.quest/mcp"
}
}
}
```
Windsurf uses `serverUrl` instead of `url`.
## VS Code (GitHub Copilot)
Add to `.vscode/mcp.json` in your project:
```json theme={"system"}
{
"servers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp"
}
}
}
```
With secure API key prompting:
```json theme={"system"}
{
"inputs": [
{
"id": "lifi-api-key",
"type": "promptString",
"description": "LI.FI API Key",
"password": true
}
],
"servers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp",
"headers": {
"X-LiFi-Api-Key": "${input:lifi-api-key}"
}
}
}
}
```
## ChatGPT
ChatGPT supports MCP via developer mode. Add the server through the ChatGPT UI. There is no config file to edit.
1. Open ChatGPT settings
2. Navigate to the MCP servers section
3. Add the server URL: `https://mcp.li.quest/mcp`
## Stdio Transport (Self-Hosted / Local)
For local MCP clients running the binary directly:
```bash theme={"system"}
go install github.com/lifinance/lifi-mcp@latest
```
Add to your MCP client config:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"command": "lifi-mcp",
"env": {
"LIFI_API_KEY": "your_api_key"
}
}
}
}
```
In stdio mode, the API key is read from the `LIFI_API_KEY` environment variable. Without it, the server uses the public rate limit (200 requests / 2 hours).
## Self-Hosted HTTP Transport
If you're running your own instance of the MCP server:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"url": "http://localhost:8080/mcp"
}
}
}
```
With an API key:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"url": "http://localhost:8080/mcp",
"headers": {
"X-LiFi-Api-Key": "your_api_key"
}
}
}
}
```
Start the server locally with:
```bash theme={"system"}
go install github.com/lifinance/lifi-mcp@latest
lifi-mcp --transport http --port 8080
```
Or with Docker:
```bash theme={"system"}
docker run -p 8080:8080 ghcr.io/lifinance/lifi-mcp
```
Full source code, Docker setup, and self-hosting documentation
## Testing Your Setup
Use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to interactively test the server:
```bash theme={"system"}
npx @modelcontextprotocol/inspector --url https://mcp.li.quest/mcp
```
This opens a web UI at `http://localhost:6274` where you can browse and test all available tools.
You can also verify your setup by asking your AI tool a simple question like:
> "What chains does LI.FI support?"
If the MCP server is connected correctly, the tool will call `get-chains` and return a list of supported blockchains.
# Overview
Source: https://docs.li.fi/mcp-server/overview
Connect AI tools to LI.FI cross-chain swap functionality via the Model Context Protocol
The LI.FI MCP Server integrates with the [LI.FI API](https://li.quest) to provide cross-chain swap functionality across multiple liquidity pools and bridges via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
MCP is a standard protocol for AI model integration, allowing AI tools like Claude, Cursor, Windsurf, and VS Code Copilot to access external tools and data sources directly.
This server provides **read-only** tools. It does not sign or broadcast transactions. Quote responses include unsigned `transactionRequest` objects that must be signed and submitted externally using your own wallet.
## Quickstart
Add the hosted LI.FI MCP server to your AI tool. No installation required:
```json theme={"system"}
{
"mcpServers": {
"lifi": {
"type": "http",
"url": "https://mcp.li.quest/mcp"
}
}
}
```
Paste this into your MCP client config and start querying cross-chain swap data. See [Installation](/mcp-server/installation) for tool-specific setup instructions.
For higher rate limits, add your [LI.FI API key](https://li.fi/) via the `X-LiFi-Api-Key` header or `Authorization: Bearer` header.
## How It Works
The MCP server wraps the LI.FI REST API into MCP-compatible tools that AI agents can call directly. Instead of constructing HTTP requests, your AI tool discovers and invokes structured tools like `get-quote`, `get-chains`, and `get-token`.
```text theme={"system"}
AI Tool (Claude, Cursor, etc.)
│
▼
MCP Protocol
│
▼
LI.FI MCP Server (https://mcp.li.quest/mcp)
│
▼
LI.FI API (https://li.quest)
│
▼
27+ Bridges & 31+ DEXes across 58 chains
```
## Example Workflow
A typical cross-chain swap via the MCP server follows this flow:
```text theme={"system"}
1. get-chains # Find chain IDs and RPC URLs
2. get-token (chain, symbol) # Get token addresses
3. get-quote (...) # Get best route and transactionRequest
4. get-allowance (...) # Check if token approval is needed
5. (external) Approve tokens using your wallet if allowance < amount
6. (external) Sign and broadcast transactionRequest using your wallet
7. get-status (txHash) # Track cross-chain progress
```
## API Key Configuration
Without an API key, the server uses the public rate limit (**200 requests / 2 hours**). With an API key, you get higher rate limits (**200 requests / minute**).
Pass your API key via HTTP headers in your MCP client config:
* `Authorization: Bearer your_api_key`
* `X-LiFi-Api-Key: your_api_key`
Use the `test-api-key` MCP tool to verify your key is valid and check rate limit information.
Sign up at li.fi to get your API key
## Common Chain IDs
| Chain | ID | Native Token |
| --------- | ---------------- | ------------ |
| Ethereum | 1 | ETH |
| Polygon | 137 | MATIC |
| Arbitrum | 42161 | ETH |
| Optimism | 10 | ETH |
| BSC | 56 | BNB |
| Avalanche | 43114 | AVAX |
| Base | 8453 | ETH |
| Solana | 1151111081099710 | SOL |
Use `get-chains` or `get-chain-by-name` to dynamically look up chain IDs instead of hardcoding them.
## Next Steps
Setup instructions for Claude, Cursor, Windsurf, VS Code, and more
Complete reference for all MCP tools
Example prompts, responses, and code samples
Source code and self-hosting instructions
# Available Tools
Source: https://docs.li.fi/mcp-server/tools
Complete reference for all tools exposed by the LI.FI MCP Server
The LI.FI MCP Server exposes the following tools, organized by category. Each tool maps to a corresponding [LI.FI API](/api-reference/introduction) endpoint.
## Token Information
### get-tokens
Retrieve all tokens supported by LI.FI. Use this to discover available tokens before requesting swaps.
| Parameter | Required | Description |
| ------------- | -------- | ------------------------------------------- |
| `chains` | No | Comma-separated chain IDs (e.g., `"1,137"`) |
| `chainTypes` | No | Filter by chain type (e.g., `"EVM,SVM"`) |
| `minPriceUSD` | No | Minimum token price in USD |
### get-token
Get details about a specific token including price, decimals, and contract address.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------------- |
| `chain` | Yes | Chain ID or name (e.g., `"1"` or `"ethereum"`) |
| `token` | Yes | Token address or symbol (e.g., `"USDC"`) |
## Chain Information
### get-chains
List all supported blockchain networks. Returns chain IDs, names, RPC URLs, and block explorer links.
| Parameter | Required | Description |
| ------------ | -------- | ------------------------------ |
| `chainTypes` | No | Filter by type (e.g., `"EVM"`) |
### get-chain-by-id
Look up a chain by its numeric ID.
| Parameter | Required | Description |
| --------- | -------- | -------------------------------------------------------- |
| `id` | Yes | Chain ID (e.g., `"1"` for Ethereum, `"137"` for Polygon) |
### get-chain-by-name
Look up a chain by name (case-insensitive).
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------------------------- |
| `name` | Yes | Chain name (e.g., `"ethereum"`, `"polygon"`, `"arbitrum"`) |
## Quote & Swap
These are the primary tools for executing cross-chain transfers.
### get-quote
Get the best route for a swap. This is the **primary tool** for most use cases. It returns the optimal route, fees, estimated time, and a `transactionRequest` object ready for signing.
| Parameter | Required | Description |
| ----------------- | -------- | ---------------------------------------------------------------- |
| `fromChain` | Yes | Source chain ID or name |
| `toChain` | Yes | Destination chain ID or name |
| `fromToken` | Yes | Source token address or symbol |
| `toToken` | Yes | Destination token address or symbol |
| `fromAddress` | Yes | Sender wallet address |
| `fromAmount` | Yes | Amount in smallest unit (e.g., wei) |
| `toAddress` | No | Recipient address (defaults to `fromAddress`) |
| `slippage` | No | Max slippage as decimal (e.g., `"0.03"` for 3%) |
| `order` | No | Route preference: `RECOMMENDED`, `FASTEST`, `CHEAPEST`, `SAFEST` |
| `allowBridges` | No | Comma-separated bridge keys to include |
| `preferBridges` | No | Comma-separated bridge keys to prefer |
| `denyBridges` | No | Comma-separated bridge keys to exclude |
| `allowExchanges` | No | Comma-separated exchange keys to include |
| `preferExchanges` | No | Comma-separated exchange keys to prefer |
| `denyExchanges` | No | Comma-separated exchange keys to exclude |
The response includes a `transactionRequest` object with `to`, `data`, `value`, `gasLimit`, and `chainId` fields, ready to sign and broadcast with any wallet.
### get-routes
Get multiple route options for comparison. Unlike `get-quote`, this returns several alternatives to choose from. Use with `get-step-transaction` to execute a specific route.
| Parameter | Required | Description |
| ------------------ | -------- | ------------------------- |
| `fromChainId` | Yes | Source chain ID |
| `toChainId` | Yes | Destination chain ID |
| `fromTokenAddress` | Yes | Source token address |
| `toTokenAddress` | Yes | Destination token address |
| `fromAddress` | Yes | Sender wallet address |
| `fromAmount` | Yes | Amount in smallest unit |
### get-step-transaction
Convert a route step into an executable transaction.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------- |
| `step` | Yes | Step object from a `get-routes` response |
### get-status
Track the progress of a cross-chain transfer.
| Parameter | Required | Description |
| ----------- | -------- | --------------------------------------------- |
| `txHash` | Yes | Transaction hash from the source chain |
| `bridge` | No | Bridge name from the quote (speeds up lookup) |
| `fromChain` | No | Source chain ID |
| `toChain` | No | Destination chain ID |
### get-quote-with-calls
Get a quote with custom contract calls (Zaps). Execute complex DeFi operations like bridge + deposit/stake in a single transaction.
| Parameter | Required | Description |
| --------------- | -------- | -------------------------------------------------------- |
| `fromChain` | Yes | Source chain ID or name |
| `toChain` | Yes | Destination chain ID or name |
| `fromToken` | Yes | Source token address or symbol |
| `toToken` | Yes | Destination token address or symbol |
| `fromAddress` | Yes | Sender wallet address |
| `fromAmount` | Yes | Amount in smallest unit |
| `contractCalls` | Yes | Array of contract call objects to execute after the swap |
## Discovery & Routing
### get-connections
Check available swap routes between chains. Use this to verify if a route exists before calling `get-quote`.
| Parameter | Required | Description |
| -------------- | -------- | -------------------------- |
| `fromChain` | Yes | Source chain ID |
| `toChain` | Yes | Destination chain ID |
| `fromToken` | No | Source token address |
| `toToken` | No | Destination token address |
| `chainTypes` | No | Filter by chain type |
| `allowBridges` | No | Filter by specific bridges |
### get-tools
List available bridges and DEXes. Returns both `key` values (for use in API calls) and `name` values (human-readable).
| Parameter | Required | Description |
| --------- | -------- | ------------------------------------ |
| `chains` | No | Array of chain IDs to filter results |
Use the `key` values returned by `get-tools` in `get-quote` with the `allowBridges` or `allowExchanges` parameters to filter routes to specific providers.
## Gas Information
### get-gas-prices
Get current gas prices for all supported chains. Returns fast, standard, and slow prices in gwei.
*No parameters required.*
### get-gas-suggestion
Get detailed gas parameters for a specific chain, including recommended gas amount in USD.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------- |
| `chainId` | Yes | Chain ID to get gas suggestion for |
## Balance & Allowance Queries
### get-native-token-balance
Check the native token balance (ETH, MATIC, etc.) for a wallet address.
| Parameter | Required | Description |
| --------- | -------- | ---------------------------------------------- |
| `chain` | Yes | Chain ID or name (e.g., `"1"` or `"ethereum"`) |
| `address` | Yes | Wallet address to check |
| `rpcUrl` | No | Custom RPC URL override |
### get-token-balance
Check an ERC20 token balance for a wallet address.
| Parameter | Required | Description |
| --------------- | -------- | ----------------------- |
| `chain` | Yes | Chain ID or name |
| `tokenAddress` | Yes | Token contract address |
| `walletAddress` | Yes | Wallet address to check |
| `rpcUrl` | No | Custom RPC URL override |
### get-allowance
Check token spending approval. Verify allowance before swaps. If insufficient, the user must approve tokens using their wallet before executing the transaction.
| Parameter | Required | Description |
| ---------------- | -------- | --------------------------------------------------------------- |
| `chain` | Yes | Chain ID or name |
| `tokenAddress` | Yes | Token contract address |
| `ownerAddress` | Yes | Token owner's wallet address |
| `spenderAddress` | Yes | Spender address (use `estimate.approvalAddress` from the quote) |
| `rpcUrl` | No | Custom RPC URL override |
Always check allowance before executing ERC20 swaps. If the allowance is less than the swap amount, the transaction will fail. Native token transfers (ETH, MATIC, etc.) do not require approval.
## API Key & Health
### test-api-key
Verify that your API key is valid. Returns key status and rate limit information.
The API key must be provided via the `Authorization: Bearer` or `X-LiFi-Api-Key` header in your MCP client configuration.
### health-check
Check server health and version. Returns server status and version information.
*No parameters required.*
## Common Token Addresses
| Token | Chain | Address |
| ------------ | ------------ | ---------------------------------------------- |
| ETH (native) | Ethereum (1) | `0x0000000000000000000000000000000000000000` |
| ETH (native) | Base (8453) | `0x0000000000000000000000000000000000000000` |
| USDC | Ethereum (1) | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
| USDC | Base (8453) | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| USDC | Solana | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| SOL (native) | Solana | `11111111111111111111111111111111` |
| USDT | Ethereum (1) | `0xdAC17F958D2ee523a2206206994597C13D831ec7` |
| DAI | Ethereum (1) | `0x6B175474E89094C44Da98b954EedeAC495271d0F` |
Use `get-token` with the symbol (e.g., `"USDC"`) to dynamically fetch the correct address for any chain instead of hardcoding addresses.