> ## 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 Flutter SDK with Li.Fi integration.

Portal's Flutter 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.

```dart theme={null}
import 'package:portal_flutter/portal_flutter.dart';
import 'dart:convert';

try {
  final userAddress = await portal.getAddress('eip155:1');

  final request = LifiQuoteRequest(
    fromChainId: 'eip155:8453', // Base Mainnet
    toChainId: 'eip155:42161', // Arbitrum
    fromTokenAddress: 'ETH',
    toTokenAddress: 'USDC',
    fromAddress: userAddress,
    fromAmount: '100000000000000', // 0.0001 ETH (in wei)
  );

  final response = await portal.trading.lifi.getQuote(request);

  final route = response.route;
  if (route != null) {
    // Process quote response
    final steps = route.steps;
    for (final step in steps) {
      if (step?.estimate != null) {
        print('From amount: ${step!.estimate!.fromAmount}');
        print('To amount: ${step.estimate!.toAmount}');
        print('Execution duration: ${step.estimate!.executionDuration}s');
      }

      // Sign and submit the transaction if transactionRequest is available
      if (step?.transactionRequest != null) {
        await executeTransaction(step!.transactionRequest!, request.fromChainId);
      }
    }
  }
} on PortalException catch (e) {
  print('Error getting quote: ${e.message}');
}
```

The response includes a route with steps, each containing a `transactionRequest` JSON string 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.

```dart theme={null}
import 'package:portal_flutter/portal_flutter.dart';

try {
  final userAddress = await portal.getAddress('eip155:1');

  final request = LifiRoutesRequest(
    fromChainId: 'eip155:8453', // Base Mainnet
    fromAmount: '100000000000000', // 0.0001 ETH (in wei)
    fromTokenAddress: 'ETH',
    toChainId: 'eip155:42161', // Arbitrum
    toTokenAddress: 'USDC',
    fromAddress: userAddress,
  );

  final response = await portal.trading.lifi.getRoutes(request);

  final routes = response.routes;

  // Find recommended route
  final recommendedRoute = routes.firstWhere(
    (route) => route?.tags.contains('RECOMMENDED') ?? false,
    orElse: () => routes.isNotEmpty ? routes.first : null,
  );

  if (recommendedRoute != null) {
    print('Selected route: ${recommendedRoute.id}');
    print('Steps: ${recommendedRoute.steps.length}');
    print('From: ${recommendedRoute.fromAmountUSD} USD');
    print('To: ${recommendedRoute.toAmountUSD} USD');

    // Process route steps
    await processRouteSteps(recommendedRoute.steps, request.fromChainId);
  }
} on PortalException catch (e) {
  print('Error getting routes: ${e.message}');
}
```

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

```dart theme={null}
Future<LifiStepTransactionResponse?> getStepTransactionDetails(LifiStep step) async {
  try {
    final request = LifiStepTransactionRequest(step: step);
    final response = await portal.trading.lifi.getRouteStep(request);
    return response;
  } on PortalException catch (e) {
    print('Error getting step details: ${e.message}');
    return null;
  }
}
```

The response includes a `transactionRequest` JSON string 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` JSON string and sign the transaction. Parse the JSON and extract the `to`, `value`, and `data` fields to sign and submit the transaction.

### Signing and Submitting Transactions

```dart theme={null}
import 'dart:convert';

Future<String?> executeTransaction(String transactionRequestJson, String chainId) async {
  try {
    // Parse the transactionRequest JSON string
    final txParams = jsonDecode(transactionRequestJson) as Map<String, dynamic>;

    final to = txParams['to'] as String;
    final data = txParams['data'] as String?;

    // Extract value (default to 0x0 if not present)
    var value = '0x0';
    if (txParams['value'] is String) {
      value = txParams['value'] as String;
    } else if (txParams['value'] is int) {
      value = '0x${(txParams['value'] as int).toRadixString(16)}';
    }

    // Sign and send
    final txHash = await portal.sendTransaction(
      chainId: chainId,
      to: to,
      data: data,
      value: value,
    );

    print('Transaction submitted: $txHash');

    // Wait for on-chain confirmation
    final confirmed = await waitForConfirmation(txHash, chainId);
    if (confirmed) {
      print('Transaction confirmed');
    }

    return txHash;
  } on PortalException catch (e) {
    print('Error executing transaction: ${e.message}');
    return null;
  }
}
```

<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:

```dart theme={null}
Future<bool> processRouteSteps(List<LifiStep?> steps, String fromChainId) async {
  for (var index = 0; index < steps.length; index++) {
    final step = steps[index];
    if (step == null) continue;

    print('Processing step ${index + 1}/${steps.length}: ${step.tool}');

    // 1. Get transaction details for this step
    final stepResponse = await getStepTransactionDetails(step);
    final transactionRequest = stepResponse?.transactionRequest;

    if (transactionRequest == null) {
      print('Failed to get transaction details for step ${index + 1}');
      return false;
    }

    // 2. Sign and submit the transaction
    await executeTransaction(transactionRequest, fromChainId);

    print('Step ${index + 1} completed');
  }

  return true;
}
```

### Waiting for Transaction Confirmation

```dart theme={null}
Future<bool> waitForConfirmation(
  String txHash,
  String chainId, {
  int maxAttempts = 30,
  Duration delay = const Duration(seconds: 2),
}) async {
  for (var i = 0; i < maxAttempts; i++) {
    await Future.delayed(delay);

    try {
      final response = await portal.request(
        chainId: chainId,
        method: 'eth_getTransactionReceipt',
        params: [txHash],
      );

      if (response.result != null) {
        final receipt = jsonDecode(response.result!) as Map<String, dynamic>;
        final status = receipt['status'] as String?;
        if (status == '0x1') {
          return true; // Transaction succeeded
        } else if (status == '0x0') {
          return false; // Transaction reverted
        }
      }
    } catch (e) {
      continue;
    }
  }

  return false; // Timeout
}
```

## Tracking Transaction Status

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

```dart theme={null}
Future<void> trackLiFiStatus(String txHash, String fromChain) async {
  try {
    final request = LifiStatusRequest(
      txHash: txHash,
      fromChain: fromChain,
    );

    final response = await portal.trading.lifi.getStatus(request);

    print('Status: ${response.status}');

    if (response.transactionId != null) {
      print('Transaction ID: ${response.transactionId}');
    }

    if (response.lifiExplorerLink != null) {
      print('Explorer: ${response.lifiExplorerLink}');
    }

    // Check if complete
    switch (response.status) {
      case LifiTransferStatus.done:
        print('Transfer completed successfully!');
        break;
      case LifiTransferStatus.failed:
        print('Transfer failed');
        break;
      default:
        print('Transfer in progress...');
    }
  } on PortalException catch (e) {
    print('Error getting status: ${e.message}');
  }
}
```

### Polling for Cross-Chain Completion

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

```dart theme={null}
Future<bool> pollForCompletion(
  String txHash,
  String fromChain, {
  int maxAttempts = 300,
  Duration pollInterval = const Duration(seconds: 2),
}) async {
  for (var attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      final request = LifiStatusRequest(
        txHash: txHash,
        fromChain: fromChain,
      );

      final response = await portal.trading.lifi.getStatus(request);

      print('Polling (${attempt + 1}/$maxAttempts): ${response.status}');

      switch (response.status) {
        case LifiTransferStatus.done:
          return true;
        case LifiTransferStatus.failed:
          return false;
        default:
          break; // Continue polling
      }
    } catch (e) {
      // Continue polling on error
    }

    await Future.delayed(pollInterval);
  }

  return false; // Timeout
}
```

## Example Flow

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

```dart theme={null}
import 'package:portal_flutter/portal_flutter.dart';
import 'dart:convert';

try {
  // 1. Get user address
  final userAddress = await portal.getAddress('eip155:1');

  // 2. Get a quote
  final quoteRequest = LifiQuoteRequest(
    fromChainId: 'eip155:8453', // Base Mainnet
    toChainId: 'eip155:42161', // Arbitrum
    fromTokenAddress: 'ETH',
    toTokenAddress: 'USDC',
    fromAddress: userAddress,
    fromAmount: '100000000000000', // 0.0001 ETH (in wei)
  );

  final quoteResponse = await portal.trading.lifi.getQuote(quoteRequest);

  final route = quoteResponse.route;
  if (route == null || route.steps.isEmpty) {
    print('No quote available');
    return;
  }

  // 3. Get the first step's transaction request
  final firstStep = route.steps.first;
  String? transactionRequestJson = firstStep?.transactionRequest;

  // If transactionRequest is not directly available, fetch step details
  if (transactionRequestJson == null && firstStep != null) {
    final stepRequest = LifiStepTransactionRequest(step: firstStep);
    final stepResponse = await portal.trading.lifi.getRouteStep(stepRequest);
    transactionRequestJson = stepResponse.transactionRequest;
  }

  if (transactionRequestJson == null) {
    print('No transaction request available');
    return;
  }

  // 4. Parse and execute the transaction
  final txParams = jsonDecode(transactionRequestJson) as Map<String, dynamic>;

  final to = txParams['to'] as String;
  final data = txParams['data'] as String?;

  var value = '0x0';
  if (txParams['value'] is String) {
    value = txParams['value'] as String;
  } else if (txParams['value'] is int) {
    value = '0x${(txParams['value'] as int).toRadixString(16)}';
  }

  final txHash = await portal.sendTransaction(
    chainId: quoteRequest.fromChainId,
    to: to,
    data: data,
    value: value,
  );

  print('Transaction submitted: $txHash');

  // 5. Track status for cross-chain completion
  final completed = await pollForCompletion(
    txHash,
    quoteRequest.fromChainId,
  );

  if (completed) {
    print('Bridge completed successfully!');
  } else {
    print('Bridge failed or timed out');
  }
} on PortalException catch (e) {
  print('Error: ${e.message}');
}
```

## 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 [performing swaps](./perform-swaps)
