> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge & Swap with Li.Fi

> Learn how to bridge and swap tokens across multiple chains using Portal's iOS SDK with Li.Fi integration.

Portal's iOS SDK provides comprehensive cross-chain bridging and swapping capabilities through the `portal.trading.lifi` API. This guide covers getting quotes, finding routes, executing swaps and bridges, and tracking transaction status.

## Overview

The Li.Fi functionality allows you to:

* **Get quotes** for bridging or swapping tokens across chains
* **Find routes** to discover the best paths for your cross-chain transfers
* **Execute swaps and bridges** by signing and submitting transactions
* **Track transaction status** for cross-chain transfers

## Prerequisites

Before using Li.Fi operations, ensure you have:

* A properly initialized Portal client
* An active wallet with the required token(s) on the source network (see [Create a wallet](./create-a-wallet))
* Li.Fi integration enabled in your Portal Dashboard (see [Li.Fi Integration](../../../integrations/Trading/lifi))

## Getting a Quote

Use the `getQuote` method to get a quote for bridging or swapping tokens across chains.

```swift theme={null}
do {
    let userAddress = try await portal.getAddress("eip155:1")

    let request = LifiQuoteRequest(
        fromChain: "eip155:1",
        toChain: "eip155:137",
        fromToken: "ETH",
        toToken: "USDC",
        fromAddress: userAddress,
        fromAmount: "1000000000000" // 0.000001 ETH in wei
    )

    let response = try await portal.trading.lifi.getQuote(request: request)
    if let rawResponse = response.data?.rawResponse {
        // Process quote response
        if let estimate = rawResponse.estimate {
            print("From amount: \(estimate.fromAmount)")
            print("To amount: \(estimate.toAmount)")
            print("Execution duration: \(estimate.executionDuration)s")
        }

        // Sign and submit the transaction if transactionRequest is available
        if let transactionRequest = rawResponse.transactionRequest {
            try await executeTransaction(transactionRequest, chainId: request.fromChain)
        }
    }
} catch {
    print("Error getting quote: \(error)")
}
```

The response includes a `transactionRequest` object with the transaction details you'll need to sign and submit.

## Finding Routes

Use the `getRoutes` method to discover available routes for your cross-chain transfer.

```swift theme={null}
do {
    let userAddress = try await portal.getAddress("eip155:1")

    let request = LifiRoutesRequest(
        fromChainId: "eip155:1",
        fromAmount: "1000000000000", // 0.000001 ETH in wei
        fromTokenAddress: "ETH",
        toChainId: "eip155:137",
        toTokenAddress: "USDC",
        fromAddress: userAddress
    )

    let response = try await portal.trading.lifi.getRoutes(request: request)
    if let rawResponse = response.data?.rawResponse {
        let routes = rawResponse.routes

        // Find recommended route
        let recommendedRoute = routes.first { route in
            route.tags?.contains("RECOMMENDED") ?? false
        } ?? routes.first

        if let route = recommendedRoute {
            print("Selected route: \(route.id)")
            print("Steps: \(route.steps.count)")
            print("From: \(route.fromAmountUSD) USD")
            print("To: \(route.toAmountUSD) USD")

            // Process route steps
            try await processRouteSteps(route.steps, fromChainId: request.fromChainId)
        }
    }
} catch {
    print("Error getting routes: \(error)")
}
```

The response includes an array of routes with estimates, fees, and gas costs. Routes may be tagged as `RECOMMENDED`, `CHEAPEST`, or `FASTEST`.

## Getting Route Step Details

Use the `getRouteStep` method to get detailed transaction information for a specific route step, including an unsigned transaction that you can then sign and submit to an RPC provider (the `transactionRequest` field).

```swift theme={null}
func getStepTransactionDetails(step: LifiStep) async throws -> LifiStep? {
    let stepRequest = step as LifiStepTransactionRequest

    let response = try await portal.trading.lifi.getRouteStep(request: stepRequest)

    if let rawResponse = response.data?.rawResponse {
        return rawResponse
    }

    return nil
}
```

The response includes a `transactionRequest` object with the unsigned transaction that you can sign and submit.

## Executing Swaps and Bridges

After getting a quote or route step details, extract the transaction details from the `transactionRequest` object and sign the transaction. Extract the `from`, `to`, `value`, and `data` fields to sign and submit the transaction.

### Approving ERC-20 Tokens

If your `fromToken` is an ERC-20, the Li.Fi router cannot move it on your behalf until you grant an on-chain allowance. Skip this step when the `fromToken` is the chain's native asset (its `address` is `0x0000000000000000000000000000000000000000`).

Build the approval transaction with the `portal.delegations.approve(request:)` method, then sign each transaction it returns with the same `eth_sendTransaction` flow used to sign the swap. Call this helper after obtaining a quote and before calling `executeTransaction`:

```swift theme={null}
let nativeTokenAddress = "0x0000000000000000000000000000000000000000"

func approveErc20IfNeeded(
    quote: LifiQuoteResponse,
    fromAmount: String,
    fromChainId: String
) async throws {
    guard let action = quote.data?.rawResponse?.action,
          let estimate = quote.data?.rawResponse?.estimate,
          let fromToken = action.fromToken,
          fromToken.address != nativeTokenAddress
    else {
        // Native asset — no approval needed.
        return
    }

    // Convert the raw fromAmount into the token's primary denomination
    // (e.g. raw "10000" with 6 decimals → "0.01"). Use a Decimal-aware
    // conversion to preserve precision for large values.
    let amount = formatUnits(fromAmount, decimals: fromToken.decimals)

    let request = ApproveDelegationRequest(
        chain: fromChainId,
        token: fromToken.address,
        delegateAddress: estimate.approvalAddress,
        amount: amount
    )

    let response = try await portal.delegations.approve(request: request)

    guard let transactions = response.transactions else { return }

    for tx in transactions {
        var txDict: [String: String] = [
            "from": tx.from,
            "to": tx.to
        ]
        if let data = tx.data { txDict["data"] = data }
        if let value = tx.value { txDict["value"] = value }

        let txResponse = try await portal.request(
            chainId: fromChainId,
            method: .eth_sendTransaction,
            params: [txDict],
            options: nil
        )

        if let txHash = txResponse.result as? String {
            _ = await waitForConfirmation(txHash: txHash, chainId: fromChainId)
        }
    }
}
```

<Note>
  This step only applies when the `fromToken` is an ERC-20. Native-asset swaps (ETH, MATIC, etc.) skip it. For more on the delegations API, see the [Manage Token Delegations](./delegations) guide.
</Note>

### Signing and Submitting Transactions

```swift theme={null}
func executeTransaction(_ transactionRequest: AnyCodable, chainId: String) async throws {
    // Extract transaction parameters
    guard let txParams = transactionRequest.value as? [String: Any],
          let from = txParams["from"] as? String,
          let to = txParams["to"] as? String
    else {
        throw NSError(domain: "LiFi", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid transaction parameters"])
    }

    // Extract value (default to 0x0 if not present)
    var value = "0x0"
    if let valueString = txParams["value"] as? String {
        value = valueString
    }

    // Extract data
    let data = txParams["data"] as? String ?? "0x"

    // Create transaction
    let ethTransaction = ETHTransactionParam(
        from: from,
        to: to,
        value: value,
        data: data
    )

    // Sign and send
    let sendResponse = try await portal.request(
        chainId,
        withMethod: .eth_sendTransaction,
        andParams: [ethTransaction]
    )

    if let txHash = sendResponse.result as? String {
        print("Transaction submitted: \(txHash)")

        // Wait for on-chain confirmation
        let confirmed = await waitForConfirmation(txHash: txHash, chainId: chainId)
        if confirmed {
            print("Transaction confirmed")
        }
    }
}
```

<Note>
  The `transactionRequest` from Li.Fi may include `gasPrice` and `gasLimit` fields. You can remove these if you want Portal to estimate the gas for you, or include them if you want to use Li.Fi's estimates.
</Note>

### Processing Multiple Route Steps

For routes with multiple steps, process them sequentially:

```swift theme={null}
func processRouteSteps(_ steps: [LifiStep], fromChainId: String) async throws {
    for (index, step) in steps.enumerated() {
        print("Processing step \(index + 1)/\(steps.count): \(step.tool)")

        // 1. Get transaction details for this step
        guard let stepWithTx = try await getStepTransactionDetails(step: step),
              let transactionRequest = stepWithTx.transactionRequest
        else {
            throw NSError(domain: "LiFi", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get transaction details"])
        }

        // 2. Sign and submit the transaction
        try await executeTransaction(transactionRequest, chainId: fromChainId)

        print("Step \(index + 1) completed")
    }
}
```

### Waiting for Transaction Confirmation

```swift theme={null}
func waitForConfirmation(
    txHash: String,
    chainId: String,
    maxAttempts: Int = 30,
    delaySeconds: UInt64 = 2
) async -> Bool {
    for _ in 0..<maxAttempts {
        try? await Task.sleep(nanoseconds: delaySeconds * 1_000_000_000)

        do {
            let response = try await portal.request(
                chainId,
                withMethod: .eth_getTransactionReceipt,
                andParams: [txHash]
            )

            if let receipt = response.result as? [String: Any],
               let status = receipt["status"] as? String {
                if status == "0x1" {
                    return true  // Transaction succeeded
                } else if status == "0x0" {
                    return false // Transaction reverted
                }
            }
        } catch {
            continue
        }
    }

    return false // Timeout
}
```

## Tracking Transaction Status

Use the `getStatus` method to track the status of your cross-chain transfer.

```swift theme={null}
func trackLiFiStatus(txHash: String, fromChain: String) async throws {
    let request = LifiStatusRequest(
        txHash: txHash,
        fromChain: fromChain
    )

    let response = try await portal.trading.lifi.getStatus(request: request)

    if let rawResponse = response.data?.rawResponse {
        print("Status: \(rawResponse.status.rawValue)")

        if let txId = rawResponse.transactionId {
            print("Transaction ID: \(txId)")
        }

        if let explorerLink = rawResponse.lifiExplorerLink {
            print("Explorer: \(explorerLink)")
        }

        // Check if complete
        if rawResponse.status == .done {
            print("Transfer completed successfully!")
        } else if rawResponse.status == .failed {
            print("Transfer failed")
        }
    }
}
```

### Polling for Cross-Chain Completion

For cross-chain transfers, poll the status endpoint until the transfer completes:

```swift theme={null}
func pollForCompletion(
    txHash: String,
    fromChain: String,
    maxAttempts: Int = 300,
    pollIntervalSeconds: UInt64 = 2
) async -> Bool {
    for attempt in 0..<maxAttempts {
        do {
            let request = LifiStatusRequest(
                txHash: txHash,
                fromChain: fromChain
            )

            let response = try await portal.trading.lifi.getStatus(request: request)

            if let rawResponse = response.data?.rawResponse {
                print("Polling (\(attempt + 1)/\(maxAttempts)): \(rawResponse.status.rawValue)")

                if rawResponse.status == .done {
                    return true
                } else if rawResponse.status == .failed {
                    return false
                }
            }
        } catch {
            // Continue polling on error
        }

        try? await Task.sleep(nanoseconds: pollIntervalSeconds * 1_000_000_000)
    }

    return false // Timeout
}
```

## Example Flow

Here's a complete example of executing a cross-chain bridge:

```swift theme={null}
do {
    // 1. Get user address
    let userAddress = try await portal.getAddress("eip155:1")

    // 2. Get a quote
    let quoteRequest = LifiQuoteRequest(
        fromChain: "eip155:1",
        toChain: "eip155:137",
        fromToken: "ETH",
        toToken: "USDC",
        fromAddress: userAddress,
        fromAmount: "1000000000000" // 0.000001 ETH in wei
    )

    let quoteResponse = try await portal.trading.lifi.getQuote(request: quoteRequest)

    guard let quote = quoteResponse.data?.rawResponse,
          let transactionRequest = quote.transactionRequest
    else {
        print("No quote available")
        return
    }

    // 3. Approve the fromToken if it's an ERC-20 (no-op for native assets)
    try await approveErc20IfNeeded(
        quote: quoteResponse,
        fromAmount: quoteRequest.fromAmount,
        fromChainId: quoteRequest.fromChain
    )

    // 4. Extract transaction parameters
    guard let txParams = transactionRequest.value as? [String: Any],
          let from = txParams["from"] as? String,
          let to = txParams["to"] as? String
    else {
        print("Invalid transaction parameters")
        return
    }

    let value = txParams["value"] as? String ?? "0x0"
    let data = txParams["data"] as? String ?? "0x"

    // 5. Sign and submit the transaction
    let ethTransaction = ETHTransactionParam(
        from: from,
        to: to,
        value: value,
        data: data
    )

    let sendResponse = try await portal.request(
        quoteRequest.fromChain,
        withMethod: .eth_sendTransaction,
        andParams: [ethTransaction]
    )

    guard let txHash = sendResponse.result as? String else {
        print("Failed to submit transaction")
        return
    }

    print("Transaction submitted: \(txHash)")

    // 6. Track status for cross-chain completion
    let completed = await pollForCompletion(
        txHash: txHash,
        fromChain: quoteRequest.fromChain
    )

    if completed {
        print("Bridge completed successfully!")
    } else {
        print("Bridge failed or timed out")
    }
} catch {
    print("Error: \(error)")
}
```

## Best Practices

1. **Compare quotes/routes** before signing and submitting the transaction(s) to find the best option for your use case
2. **Process steps sequentially** for multi-step routes, ensuring each step completes before starting the next
3. **Handle network errors gracefully** and provide user feedback
4. **Monitor transaction status** for cross-chain transfers, as they may take longer than single-chain transactions
5. **Validate user balances** before initiating swaps or bridges

## Supported Networks

Portal's Li.Fi integration supports the following mainnet networks:

* Monad (`eip155:143`)
* Ethereum (`eip155:1`)
* Optimism (`eip155:10`)
* BSC (`eip155:56`)
* Gnosis (`eip155:100`)
* Unichain (`eip155:130`)
* Polygon (`eip155:137`)
* Sonic (`eip155:146`)
* Mantle (`eip155:5000`)
* Base (`eip155:8453`)
* Arbitrum (`eip155:42161`)
* Celo (`eip155:42220`)
* Avalanche (`eip155:43114`)
* Linea (`eip155:59144`)
* Berachain (`eip155:80094`)
* Katana (`eip155:747474`)
* Solana (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`)
* Bitcoin (`bip122:000000000019d6689c085ae165831e93-p2wpkh`)

For the complete list of networks Li.Fi supports across its ecosystem, refer to the [Li.Fi documentation](https://docs.li.fi). If you need a chain that isn't listed above, contact Portal support.

<Note>
  **Testnets are not supported.**
</Note>

## Next Steps

* Learn about [signing transactions](./sign-a-transaction)
* Explore [sending tokens](./send-tokens)
* Check out [Portal API methods](./portal-api-methods)
