Skip to content

Repository files navigation

@lifi/wdk-protocol-swidge-lifi

Powered by WDK License: Apache 2.0

WDK Swidge module for LI.FI — lets any WDK wallet account swap and bridge tokens across chains via LI.FI using the ISwidgeProtocol interface.

Implements ISwidgeProtocol from @tetherto/wdk-wallet >= 1.0.0-beta.9. By implementing SwidgeProtocol, this module automatically satisfies both ISwapProtocol and IBridgeProtocol — one module, every route LI.FI supports.

Install

npm install @lifi/wdk-protocol-swidge-lifi

Peer dependencies (install the ones you need):

npm install @tetherto/wdk-wallet-evm          # EOA accounts
npm install @tetherto/wdk-wallet-evm-erc-4337  # ERC-4337 smart accounts (optional)

Usage

Quote and bridge USDT from Ethereum to Arbitrum

import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm'
import { LifiSwidgeProtocol } from '@lifi/wdk-protocol-swidge-lifi'

const account = new WalletAccountEvm(seedPhrase, "0'/0/0", {
  provider: 'https://mainnet.infura.io/v3/YOUR_KEY'
})

const protocol = new LifiSwidgeProtocol(account, {
  integrator: 'your-integrator-id',  // optional
  order: 'RECOMMENDED'
})

// Non-binding quote
const quote = await protocol.quoteSwidge({
  fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT on Ethereum
  toToken:   '0xdAC17F958D2ee523a2206206994597C13D831ec7', // same — resolved to symbol cross-chain
  toChain: 'arbitrum',
  fromTokenAmount: 10_000_000n // 10 USDT (6 decimals)
})

console.log(`Receive ~${quote.toTokenAmount} USDT on Arbitrum`)

// Execute
const result = await protocol.swidge({
  fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
  toToken:   '0xdAC17F958D2ee523a2206206994597C13D831ec7',
  toChain: 'arbitrum',
  fromTokenAmount: 10_000_000n
})

console.log(`Bridge tx: ${result.hash}`)

// Poll status
let { status } = await protocol.getSwidgeStatus(result.id, { fromChain: 1, toChain: 42161 })

Same-chain swap

const result = await protocol.swidge({
  fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
  toToken:   '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
  // toChain omitted → same chain as wallet
  fromTokenAmount: 10_000_000n
})

Discovery (no account needed)

const protocol = new LifiSwidgeProtocol(undefined, {
  provider: 'https://mainnet.infura.io/v3/YOUR_KEY'
})

const chains = await protocol.getSupportedChains()
const tokens = await protocol.getSupportedTokens({ fromChain: 1 })

Configuration

new LifiSwidgeProtocol(account, {
  // WDK fee caps (basis points of input amount)
  maxNetworkFeeBps: 100,       // reject if gas > 1% of input (USD-based for cross-token)
  maxProtocolFeeBps: 50,       // reject if LI.FI fee > 0.5% of input (source token units)

  // LI.FI options
  integrator: 'my-app',        // integrator identifier header
  apiKey: 'sk-...',            // API key for higher rate limits — server-side only
  order: 'RECOMMENDED',        // 'RECOMMENDED' | 'FASTEST' | 'CHEAPEST'
  allowBridges: ['stargate'],  // whitelist specific bridges
  denyBridges: ['across'],     // blacklist specific bridges
  allowDestinationCall: true,  // include routes with a destination-chain call/swap (LI.FI default)
  allowNativeValue: true,      // set false to reject routes that require native token value (gasless)

  // Reliability (defaults shown — modeled on the LI.FI SDK)
  timeout: 30_000,             // ms per API request attempt
  retries: 1,                  // extra attempts on 5xx / 429 / network errors / timeouts; 0 disables
  retryDelay: 500,             // base backoff in ms, doubled per attempt, capped at 5000

  // Security (opt-in)
  trustedContracts: true,      // require quote tx target + approval address to be known LI.FI contracts
})

Fee caps can also be overridden per call:

await protocol.swidge(options, { maxProtocolFeeBps: 20 })

Quote-first flows: minAmountOut

swidge() always fetches a fresh quote at execution time, so in a quote-first UI the route the user accepted can differ from the route that executes if the market moves in between. Pass the displayed quote's toTokenAmountMin as the minAmountOut option (WDK-standard SwidgeOptions field since @tetherto/wdk-wallet 1.0.0-beta.14): if the fresh execution quote's worst-case output falls below it, swidge() throws before any approval or transaction is sent, and the app can re-quote and ask the user again.

const quote = await protocol.quoteSwidge(options)
// ... user reviews and accepts the displayed quote ...
const result = await protocol.swidge({ ...options, minAmountOut: quote.toTokenAmountMin })

minAmountOut is enforced at execution only — quoteSwidge() ignores it and always returns the quoted amounts, so the app can display the fresh numbers when re-quoting. It is not forwarded to LI.FI. When omitted, behavior is unchanged.

By default the module mirrors LI.FI's own routing behavior: no bridges are denied, routes with a destination-chain call are allowed (allowDestinationCall is only forwarded when explicitly set), and quotes whose transaction carries native token value are executed as-is. Setting allowDestinationCall: false excludes routes that need a destination-chain call, such as a swap after bridging, avoiding PARTIAL outcomes where the bridge succeeds but the destination swap cannot complete and the user receives an intermediary token.

Gasless integrations

Wallets that cannot spend native tokens (e.g. ERC-4337 accounts with a paymaster) should opt into gasless mode explicitly:

import LifiSwidgeProtocol, { NATIVE_VALUE_BRIDGE_DENY_LIST } from '@lifi/wdk-protocol-swidge-lifi'

new LifiSwidgeProtocol(account, {
  denyBridges: NATIVE_VALUE_BRIDGE_DENY_LIST, // filter out native-fee bridges at quote time
  allowNativeValue: false                     // reject any remaining native-value quote before execution
})

NATIVE_VALUE_BRIDGE_DENY_LIST is the maintained list of bridges known to require native token value in the source transaction (glacis, stargateV2, stargateV2Bus, squid, arbitrum, and gasZipBridge). With allowNativeValue: false, swidge() rejects any quote whose transactionRequest.value is greater than zero before sending approvals or the bridge transaction.

Reliability

All LI.FI API calls go through a central request layer modeled on the LI.FI SDK:

  • Timeouts — every request is aborted after timeout ms (default 30s) instead of hanging.
  • Retries — transient failures (5xx, network errors, timeouts) are retried with exponential backoff. 429 responses honor the Retry-After header when present.
  • Error classification — HTTP statuses map to typed errors: 409 → LifiSlippageError (stale quote, request a new one), 429 → LifiRateLimitError, timeouts → LifiTimeoutError, persistent network failures → LifiNetworkError.

When polling getSwidgeStatus(), a LifiStatusError with err.lifiStatus === 'NOT_FOUND' means the transaction is not indexed yet — treat it as still pending (see examples/bridge-usdt.js).

Security

The transaction data returned by the LI.FI quote API is always structurally validated (valid target address, hex calldata, parseable amounts) before being forwarded to the wallet.

Setting trustedContracts additionally requires the transaction target and approval address to be known LI.FI contracts, rejecting with LifiUntrustedContractError before any approval is granted:

// Built-in allowlist: per-chain LI.FI Diamond deployments + Permit2
new LifiSwidgeProtocol(account, { trustedContracts: true })

// Extend the built-ins with additional trusted addresses per chain ID
new LifiSwidgeProtocol(account, { trustedContracts: { 137: '0x...' } })

This check is opt-in to match LI.FI SDK behavior and because some chains use non-canonical Diamond deployments. The built-in allowlist covers the canonical address and known exceptions (e.g. zkSync Era); verify against LI.FI deployments before enabling on uncommon chains.

Supported chains

All chains supported by LI.FI — call getSupportedChains() for the live list. Common string aliases for toChain: ethereum, arbitrum, base, optimism, polygon, bsc, avalanche, scroll, zksync, and 60+ more. Raw numeric chain IDs are also accepted.

Status mapping

LI.FI status Substatus SwidgeStatus
PENDING pending
DONE COMPLETED completed
DONE PARTIAL partial
DONE REFUNDED refunded
DONE NOT_PROCESSABLE_REFUND_NEEDED refund-pending
FAILED failed
any requiredActions non-empty action-required

Fee mapping

LI.FI cost type SwidgeFeeType Legacy field
gasCosts[].type === 'SEND' network fee
feeCosts[] protocol bridgeFee

Each fee uses its LI.FI cost token's chainId when available. This identifies the chain where the fee token is charged or denominated, which may differ from the chain where the underlying execution or gas consumption occurs. If LI.FI does not identify the token chain, the optional chain field is omitted rather than inferred from the route.

Error types

Class When thrown
LifiConfigurationError Missing or invalid provider/config
LifiQuoteError LI.FI quote or token API error
LifiExecutionError Fee cap exceeded before any transaction
LifiStatusError LI.FI status API error or unknown id
LifiReadOnlyAccountError swidge() called with read-only or absent account
LifiUnsupportedChainError Unknown chain name string passed as toChain
LifiTimeoutError API request exceeded the configured timeout
LifiNetworkError Network-level failure persisted after all retries
LifiRateLimitError 429 from the API after retries were exhausted
LifiSlippageError 409 from the quote API — stale quote, request a new one (subclass of LifiQuoteError)
LifiValidationError Invalid user input or malformed API response, thrown before execution
LifiUntrustedContractError Quote target/approval address not in the trustedContracts allowlist (subclass of LifiExecutionError)

All errors extend LifiProtocolError which extends Error.

TypeScript

Type declarations are generated from JSDoc and shipped in types/. Consumers get full typings for the protocol class, config, and all error classes. After changing public JSDoc, regenerate with:

npm run build:types

Publishing

This package does not bundle or transpile its runtime JavaScript. It ships ESM source files directly from index.js, src/, and bare.js, plus generated type declarations in types/. Before handing off a release, run:

npm run lint
npm test
npm run build:types
npm audit --omit=dev
npm publish --dry-run

The actual npm release command is:

npm publish --access public

Support

  • Integration questions: open an issue on GitHub or reach the LI.FI developer relations team at help.li.fi/hc/en-us.
  • WDK-specific questions: use your assigned Tether partner Slack channel.

Security

See SECURITY.md for the vulnerability disclosure process.

License

Apache 2.0 — see LICENSE.

About

No description, website, or topics provided.

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages