SPB Git

Repository Public

lifi.md 19.1 KB 229 lines markdown

1# LI.FI — execution dossier23Status in this phase: **price discovery only**. The adapter (`packages/swap/src/providers/lifi.ts`) asks for priced routes and4never asks LI.FI for transaction data. Quotes are `executability: opaque`. Everything below was read on 2026-11-10 from the5official documentation and from the live API; statements that come from a live answer rather than from the docs say so.67## 1. What LI.FI is89A routing aggregator: one API in front of bridges (Relay, Mayan, NEAR Intents, Chainflip, Polymer/CCTP, Layerswap, Symbiosis,10Across, Stargate…) and DEX aggregators (1inch, Kyberswap, Sushi, OKX, Jupiter…). On EVM chains and TRON every route is executed11through one contract, the **LiFiDiamond** (EIP-2535 proxy with one "facet" per bridge), which pulls the input, takes LI.FI's fee,12optionally swaps, then calls the bridge. LI.FI itself holds no liquidity and settles nothing: finality, speed and refunds are13those of the bridge the route names.1415## 2. Networks and identifiers (verified live)1617`GET https://li.quest/v1/chains` (EVM) and `GET /v1/chains?chainTypes=SVM,UTXO,MVM,TVM,STL`:1819| Wallet network | LI.FI chain id | type | native token id | LiFiDiamond |20|---|---|---|---|---|21| ethereum | 1 | EVM | `0x0000000000000000000000000000000000000000` | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |22| base | 8453 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |23| arbitrum | 42161 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |24| polygon | 137 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |25| optimism | 10 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |26| bsc | 56 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |27| avalanche | 43114 | EVM | zero address | `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` |28| hyperevm | 999 | EVM | zero address | **`0x0a0758d937d1059c356D4714e57F5df0239bce1A`** (differs) |29| tron | 728126428 | TVM | `T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb` | `TU3ymitEKCWQFtASkEeHaPb8NfZcJtCHLt` |30| solana | 1151111081099710 | SVM | `11111111111111111111111111111111` (wSOL is a different token) | none |31| bitcoin | 20000000000001 | UTXO | `bitcoin` | none |32| sui | 9270000000000000 | MVM | `0x0000…0002::sui::SUI` | none |3334- The Sui id is **above 2^53** (not a "safe" integer). It is an exact double, so JSON round-trips it unchanged (tested), but it35  must never go through arithmetic or a 32-bit field.36- Not served: Litecoin, Dogecoin, Bitcoin Cash, XRPL, Aptos, Cosmos, NEAR, TON (not LI.FI chains; `chainTypes` rejects anything37  but EVM, SVM, UTXO, MVM, TVM, STL). **Stellar** is listed (id 1201081091099710, Soroban contract ids) but a live request for38  native XLM returned no route (`near`: "Quoting for this pair is not available", `layerswap`: "Route not found"; the USDC39  routes need a trustline). It is not declared; recording in `test/fixtures/lifi/no-route-stellar.json`.40- TRON is cross-chain only ("Quotes with Tron as the source chain are always cross-chain", Tron overview). Addresses are41  base58, case-sensitive; the hex `41…` form is refused.42- Solana: "we only support single step transactions per ecosystem" (Solana overview).4344Official sources for the contracts:45- <https://docs.li.fi/introduction/lifi-architecture/smart-contract-addresses> (Diamond `0x1231…4EaE` "on most supported46  networks"; "on some networks the address is different").47- `diamondAddress` of each chain in <https://li.quest/v1/chains> — the values in the table above.48- Deployment files, one per network: <https://github.com/lifinance/contracts/tree/main/deployments> (`mainnet.json`,49  `base.json`, `arbitrum.json`, `polygon.json`, `optimism.json`, `bsc.json`, `avalanche.json`, `hyperevm.json`, `tron.json`),50  read 2026-11-10. They agree with the API for `LiFiDiamond` on all nine networks.5152Other contracts a route names (from the same deployment files):5354| Contract | Address | Role |55|---|---|---|56| FeeForwarder | `0xDe2b920506EE931a6A2ABDF566526220214FBbAE` (7 EVM chains), `0x960413AF34CF1e428E9A157Cd4aABab78134d865` (HyperEVM), `TNZ3fznhvEssLeovS9Uc7zCLgYjKdNjX9P` (TRON) | receives LI.FI's fee inside the Diamond call; it is the `approvalAddress` of the `feeCollection` included step |57| Permit2Proxy | `0x8eEd5E28216C0Bc6F817df40Ae6CA11C2294CC1c` (7 EVM chains), `0x5c006F9d4F33C13DE97FA7BD1c8745Ef0A5bfaCC` (HyperEVM) | optional gasless-approval path; not needed |58| Permit2 (Uniswap) | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | named by `/v1/chains` (`permit2`); not needed |5960The per-bridge contracts (`includedSteps[].estimate.approvalAddress`, e.g. Relay depository `0xf70da97812CB96acDF810712Aa562db8dfA3dbEF`61on Base/Ethereum, Kyberswap router `0x6131B5fae19EA4f9D964eAc0408E4408b66337b5`) are approved **by the Diamond**, not by the62user. The adapter reports them in `steps[].contracts` for display. They are not pinned and were not checked against each63bridge's own documentation: that is work for the execution phase, per bridge that gets allowlisted.6465## 3. API6667Base `https://li.quest/v1` (pinned in the adapter). Docs: <https://docs.li.fi/api-reference/introduction>.6869| Endpoint | Use | Returns transaction data |70|---|---|---|71| `POST /advanced/routes` | several priced routes (used by the adapter) | **no** |72| `POST /advanced/stepTransaction` | turns one step of a chosen route into a transaction ("fresh quotes are used") | yes |73| `GET /quote` | best single-transaction route in one call | yes (`transactionRequest`) |74| `GET /status` | tracking | — |75| `GET /chains`, `/tokens`, `/tools`, `/connections` | discovery | — |7677Why `/advanced/routes` and not `/quote`: it gives every candidate (cheapest and fastest can differ by 18 minutes on the same78pair) in one round, and it does not build a transaction, which is exactly the boundary of this phase.7980Request sent by the adapter (identical to the recorded requests in `test/fixtures/lifi/_requests.json`):8182```json83{ "fromChainId": 8453, "toChainId": 1151111081099710,84  "fromTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "toTokenAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",85  "fromAmount": "25000000", "fromAddress": "<sender on the source chain>", "toAddress": "<recipient on the destination chain>",86  "options": { "slippage": 0.005, "integrator": "sp-wallet", "allowSwitchChain": false } }87```8889- **Sender / recipient on different chain families**: `fromAddress` is in the format of the source chain, `toAddress` in the90  format of the destination chain (EVM hex, Solana base58, Bitcoin address, TRON base58, Sui 32-byte hex). If `toAddress` is91  omitted LI.FI uses `fromAddress`, which is wrong across families: the adapter always sends both and rejects any route whose92  `fromAddress`/`toAddress` differ from the request.93- **Slippage**: a fraction (`0.005` = 0.5 %), applied per step by default (`slippageScope: step`; `route` makes it one budget94  for the whole route). The adapter sends `slippageBps / 10000` and rejects routes priced with another value.95- **Guaranteed minimum**: `toAmountMin` — "the guaranteed minimum value that the transfer will yield including slippage".96  Mapped to `minAmountOut` as is; `toAmount` is the estimate. On some routes the two are equal (seen live on USDC Base →97  USDC Solana with Polymer, Mayan Swift and LI.FI Intents: the bridge delivers a fixed amount).98- **`allowSwitchChain: false`**: routes that need a second signature on the destination chain (Allbridge, Glacis to Solana…)99  are filtered out by LI.FI (`unavailableRoutes.filteredOut`), so each route is one user transaction (one top-level step). A100  route with more than one top-level step or `containsSwitchChain: true` is dropped by the adapter.101- **`integrator`**: free-text tracking string (`LIFI_INTEGRATOR`, default `sp-wallet`). It is not a credential; registering it102  in LI.FI's Partner Portal is what links it to an account and to fee sharing. No `fee` is requested.103- **Duration**: `steps[0].estimate.executionDuration`, seconds (can be fractional; rounded up).104- **Validity**: LI.FI states none for a route. The adapter uses 30 s. Transactions built later carry their own limits (TRON:105  `raw_data.expiration`, about 10 minutes; Bitcoin: "send in a timely manner (~30 min)"; Solana: the blockhash).106- **Errors**: unknown token → HTTP 404 `{ "code": 1003 }` (treated as no route); invalid request → 400 `{ "code": 1011 }`;107  no route → 200 with `routes: []`.108109### Fees, as reported in `estimate`110111| Field | Meaning | FeeItem |112|---|---|---|113| `gasCosts[]` (`type: SEND`) | gas of the source transaction, in the source chain coin (`amount` = `estimate × price`; use `limit` when sending). TRON: bandwidth and energy, burned as TRX unless the account has staked resources (66 TRX ≈ 21 USD for a USDT transfer, live) | `network`, not in output, estimate |114| `feeCosts[]` "LIFI Fixed Fee" (`feeSplit.lifiFee`) | LI.FI's service fee, **0.25 % of the input** ("LI.FI charges a 0.25% service fee on each transaction"), taken from the input before the first leg | `provider`, inside the output |115| `feeCosts[]` `feeSplit.integratorFee` | integrator's own fee when `fee` is requested (not requested: always 0) | `affiliate` |116| `feeCosts[]` of a `cross` step (Relayer Gas/Service fee, NearIntents fees, Redeem Relayer Fee, Chainflip ingress/network/broker/egress, LayerSwap fee…) | bridge fees, possibly in several assets on several chains | `bridge`, inside the output |117| `feeCosts[]` of a `swap` step | DEX fees when the DEX reports them | `protocol` |118119`included: true` means "already accounted for in the quoted amounts and must not be added again"; `false` means the user pays120it on top of `fromAmount`. Every fee seen live was `included: true`. A fee in an asset outside the wallet registry (e.g. Relay121fees in cirBTC on a LINK → cirBTC → JUP route) cannot be a `FeeItem`: when it is inside the output it is left in `raw`; when122it would have to be paid on top the route is dropped.123124### Steps125126`steps[0].includedSteps[]` in order, each `swap` (same chain), `cross` (bridge) or `protocol` (LI.FI fee collection, same127token in and out). Mapping: the fee step is folded into the next leg (its contract is reported, its amount is in `fees`);128each `swap`/`cross` becomes a `RouteStep` (`swap` / `bridge`) with the tool's display name in `via`. **When the asset between129two legs is not in the wallet registry, the two legs are merged into one `RouteStep`** whose `via` lists both venues in order130(`['Kyberswap', 'Relay']`): the wallet never names an asset it has not vetted, and the route is still described end to end.131132## 4. Transaction shapes for the next phase133134All of them are obtained with `POST /advanced/stepTransaction` (body = the chosen step, unchanged) or `GET /quote`.135136**EVM** (seen live from `/quote`, Base → Solana):137`transactionRequest = { from, to, chainId, data, value, gasPrice, gasLimit }`138- `to` = the LiFiDiamond of the source chain (table above) — pin it; refuse anything else.139- `data` = a facet call, e.g. selector `0x17917a4e`, with a `BridgeData` struct (`transactionId`, `bridge`, `integrator`,140  `referrer`, `sendingAssetId`, `receiver`, `minAmount`, `destinationChainId`, `hasSourceSwaps`, `hasDestinationCall`) followed141  by bridge-specific data and, when there are source swaps, `SwapData[]` (`callTo`, `approveTo`, `sendingAssetId`,142  `receivingAssetId`, `fromAmount`, `callData`, `requiresDeposit`; `src/Libraries/LibSwap.sol`). ABI: <https://github.com/lifinance/contracts/blob/main/src/Interfaces/ILiFi.sol>.143- `value` = the input when it is the chain coin (plus any native bridge fee), else `0x0`.144- **Approval** (ERC-20 input): `approve(estimate.approvalAddress, fromAmount)` where `approvalAddress` is the Diamond. Exact145  amount, never unlimited. USDT on Ethereum needs the allowance reset to 0 first when one already exists.146- What an isolated signer can verify by itself: chain id, `to` = pinned Diamond, `value`, the allowance; inside `data`, the147  **generic** part of `BridgeData` — `sendingAssetId`, `minAmount` (= amount bridged), `destinationChainId`, and `receiver`148  when the destination is EVM. What it cannot: the receiver on a non-EVM destination (`receiver` is an EVM `address`, so the149  real recipient is encoded in the bridge-specific part, different for every facet), the **minimum output on the destination** (lives in150  the bridge's own payload or off-chain intent), and the swap calldata (`SwapData.callData` is opaque DEX-aggregator calldata;151  only `callTo` can be checked against an allowlist). This is why the quotes are `opaque`.152153**TRON** (Tron overview): `transactionRequest.data` = `0x` + `raw_data_hex` of an unsigned `TriggerSmartContract`;154`customData.tronTransaction` = the TronWeb object (`txID`, `raw_data`, `raw_data_hex`); `to` = the Diamond155`TU3ymitEKCWQFtASkEeHaPb8NfZcJtCHLt`; `value` = call value in SUN (hex); `gasLimit` = `fee_limit` in SUN, "a safety ceiling,156not the expected cost". The txID is the SHA-256 of `raw_data`, so the signer can recompute it from `data` alone. TRC-20 input:157a separate `approve(estimate.approvalAddress, amount)` transaction, confirmed first. Valid until `raw_data.expiration`.158159**Solana** (Solana tx execution page): `transactionRequest` contains only `data`, "base64 encoded Solana transaction data"160(a serialized transaction; `svmTransactionVersion` says legacy or v0; `svmMaxSupportedTransactionVersion` and161`svmPriorityFeeLevel` are request parameters). No approval. The docs name no program id: the programs are those of the bridge162or of Jupiter. A signer would have to decompile the message and allowlist every program and every writable account —163nothing here is verifiable without that.164165**Bitcoin** (Bitcoin tx example): `transactionRequest = { to, data, value }` — `to` is a **vault / depositor address of the166bridge**, `value` the amount in satoshis, `data` a **memo** that "specifies the bridge-specific tx data to execute and167internal LI.FI details for the tracking" and must be carried byte for byte in an OP_RETURN output. Fixed output order:1681. amount to the depositor address, 2. OP_RETURN memo, 3. refund output to the sender (mandatory for Chainflip, whose "stuck169transactions cannot be refunded manually"), 4. integrator fee outputs if any. Only the inputs may be chosen by the wallet. The170signer can verify outputs, amounts and the change/refund address; it cannot verify that the memo names the right recipient171without a per-bridge memo parser (THORChain-style `=:ETH.USDC:0x…` is parseable; Relay's and Chainflip's are opaque).172The vault address rotates and is not pinnable.173174**Sui**: the Sui page of the docs is a stub ("same-chain swaps on Sui and bridging between Sui and major EVM chains and175Solana"); the transaction format is not documented there. Not verified. To be read from a live `stepTransaction` answer in176the execution phase (expected: a serialized programmable transaction block).177178## 5. Status, completion and refunds179180`GET /v1/status?txHash=<source tx hash | destination tx hash | transactionId>&fromChain=<id>&toChain=<id>&bridge=<tool>`181(<https://docs.li.fi/api-reference/check-the-status-of-a-cross-chain-transfer>). `fromChain` is required in practice for TRON182("Tron hashes can't be told apart from Bitcoin hashes by format"). Same-chain swaps: `fromChain = toChain`.183184| status | substatus | meaning |185|---|---|---|186| `NOT_FOUND` | — | not indexed or not mined yet. "A persistent 404 or NOT_FOUND isn't terminal evidence of failure" and does not authorize sending again |187| `PENDING` | `WAIT_SOURCE_CONFIRMATIONS`, `WAIT_DESTINATION_TRANSACTION`, `BRIDGE_NOT_AVAILABLE`, `CHAIN_NOT_AVAILABLE`, `REFUND_IN_PROGRESS`, `UNKNOWN_ERROR` | in progress |188| `DONE` | `COMPLETED` | delivered |189| `DONE` | `PARTIAL` | "Only partial transfer completed (common for across, hop, stargate, amarok)". The docs say no more; in practice this is a route whose destination swap did not run, leaving the recipient with the bridged token instead of the requested one — to be confirmed per bridge |190| `DONE` | `REFUNDED` | the bridge returned the funds |191| `FAILED` | `NOT_PROCESSABLE_REFUND_NEEDED`, `OUT_OF_GAS`, `SLIPPAGE_EXCEEDED`, `INSUFFICIENT_ALLOWANCE`, `INSUFFICIENT_BALANCE`, `EXPIRED`, `UNKNOWN_ERROR`, `REFUNDED` | failed |192| `INVALID` | — | the hash does not belong to the tool given |193194Polling recommended by the docs: every 10 s for the first minute, then 30 s, then 60 s, with a cap.195196**Refunds are those of the bridge, not of LI.FI.** A source transaction that reverts moves nothing. After the source197transaction is mined, what happens to a transfer that cannot complete depends on the bridge (automatic refund on the source198chain, refund to a refund address given at deposit, or delivery of the intermediate token). The route does not say which,199so cross-chain quotes carry `refundable: null`; same-chain swaps are one atomic transaction (`atomic: true`). Two cases200matter for the wallet: if `PARTIAL` leaves the bridged token with the recipient, that token may be one that is **not in the registry** (the wallet would hold it without201showing it) — the execution phase should either restrict routes to intermediates in the registry or pass202`allowDestinationCall: false`; and `NOT_PROCESSABLE_REFUND_NEEDED` needs a manual claim with the bridge.203204## 6. Limits, key, terms205206- **Rate limits** (<https://docs.li.fi/api-reference/rate-limits>, confirmed by the `ratelimit-*` headers): without a key,207  `/quote` and `/advanced/routes` **75 requests per two hours** each (per IP), `/advanced/stepTransaction` 50 per two hours,208  other endpoints 100 per minute. With a key (`x-lifi-api-key` header, from the Partner Portal): 100 requests per minute by209  default. One quote costs one request, so the keyless limit is a demonstration budget, not a production one: **a key is210  needed before this provider is enabled for users** (the adapter sends `LIFI_API_KEY` when set; `needsKey` stays false211  because the endpoint answers without one). The runtime's local limit should be set well under 75 per two hours meanwhile.212- **Key**: server-side only ("Don't expose the key in client-side code").213- **Terms** (<https://li.fi/legal/terms-and-conditions/>, last updated 4 September 2025): cover the website, the SDK/widget and214  the smart contracts; they do not mention the API or its limits. The user warrants not being sanctioned, not being in a215  comprehensively sanctioned jurisdiction, and **not being a United States citizen, resident or person in the United States**.216  An integrator that offers the integration to others must tell its users that it is independent of LI.FI. LI.FI may ask an217  integrator to stop. To be reviewed by the owner before any execution is offered.218219## 7. Verified / not verified220221Verified live on 2026-11-10: chain ids and Diamond addresses (API and GitHub deployment files agree); request and response222shape on 11 pairs across EVM, Solana, Bitcoin, TRON, Sui, HyperEVM (fixtures); rate-limit headers; 404 and no-route answers;223quoted rates within 1 % of CoinGecko on eight pairs of about 30 USD (smoke script); the EVM `transactionRequest` shape (one224`/quote` call, nothing signed).225226Not verified: any execution; the Sui, Solana and Bitcoin transaction payloads (documentation only, Sui not documented); the227per-bridge contracts and their refund rules; the decoding of `BridgeData` against real calldata; behaviour of228`stepTransaction` when the fresh quote is worse than the route; the meaning of `PARTIAL` per bridge.229
Files main
Menu

Theme