Skip to main content
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)
  • Li.Fi integration enabled in your Portal Dashboard (see Li.Fi Integration)

Getting a Quote

Use the getQuote method to get a quote for bridging or swapping tokens across chains.
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.
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).
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.

Signing and Submitting Transactions

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")
        }
    }
}
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.

Processing Multiple Route Steps

For routes with multiple steps, process them sequentially:
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

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.
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:
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:
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. 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"
    
    // 4. 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)")
    
    // 5. 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

Li.Fi supports a wide range of networks for bridging and swapping. Common networks include:
  • Monad (eip155:143)
  • Ethereum (eip155:1)
  • Polygon (eip155:137)
  • Base (eip155:8453)
  • Arbitrum (eip155:42161)
  • Optimism (eip155:10)
  • Avalanche (eip155:43114)
  • And many more…
For the complete list of supported networks, refer to the Li.Fi documentation.

Next Steps