Skip to main content
Portal’s Flutter SDK provides comprehensive yield opportunities capabilities through the portal.yieldIntegrations.yieldxyz API. This guide covers discovering yield opportunities, entering positions, managing existing positions, and exiting yield opportunities.

Overview

The yield functionality allows users to:
  • Discover available yield opportunities across different protocols and networks
  • Enter yield positions by depositing tokens into yield opportunities
  • Manage existing positions (claim rewards, voting, etc.)
  • Exit yield positions to withdraw aggregated tokens and rewards
  • Track yield balances and historical yield actions

Prerequisites

Before using yield operations, ensure you have:
  • A properly initialized Portal client
  • An active wallet with the required token(s) on the target network (see Create a wallet)
  • Yield.xyz integration enabled in your Portal Dashboard (see Yield.xyz Integration)

Discovering Yield Opportunities

Use the discover method to find available yield opportunities. For complete API documentation, see the Yield.xyz API reference.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final response = await portal.yieldIntegrations.yieldxyz.discover(
  request: YieldXyzGetYieldsRequest(
    offset: 0,
    limit: 10,
    network: 'eip155:11155111', // Sepolia network
    // ... other parameters
  ),
);

final yieldOpportunities = response.items;

// Process and display yield opportunities
for (final opp in yieldOpportunities.whereType<YieldOpportunity>()) {
  print('${opp.name}: ${opp.rewardRate}% ${opp.rewardRateType.name}');
}
Popular, high-quality USDC yield options with no lockups or limits:
  • USDC Aave V3 Lending: base-usdc-aave-v3-lending
  • USDC Fluid Vault: base-usdc-fusdc-0xf42f5795d9ac7e9d757db633d693cd548cfd9169-4626-vault
  • USDC Spark Savings Vault: ethereum-usdc-spusdc-0x28b3a8fb53b741a8fd78c0fb9a6b2393d896a43d-4626-vault

Entering Yield Positions

To enter a yield position, first discover the specific yield, then use the enter method. For complete API documentation, see the Yield.xyz enter yield reference. For the example below, we will use the yield opportunity with the ID "ethereum-sepolia-link-aave-v3-lending". Fund your Portal client with the required LINK token to enter the position.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final userAddress = await portal.getAddress('eip155:11155111');

final enterResponse = await portal.yieldIntegrations.yieldxyz.enter(
  yieldId: 'ethereum-sepolia-link-aave-v3-lending',
  address: userAddress, // Optional - defaults to current wallet address
  amount: '1', // 1 LINK token
);

final transactions = enterResponse.transactions;

// Process transactions, this is described in the "Transaction Processing" section below
await processTransactions(transactions, 'eip155:11155111');

Checking Yield Balances

Retrieve current yield positions and balances. For complete API documentation, see the Yield.xyz get balances reference.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final userAddress = await portal.getAddress('eip155:11155111');

final response = await portal.yieldIntegrations.yieldxyz.getBalances(
  queries: [
    YieldXyzGetBalancesQuery(
      address: userAddress,
      network: 'eip155:11155111', // Sepolia testnet
    ),
  ],
);

final yieldPositions = response.items;

// Process and display yield positions information
for (final item in yieldPositions.whereType<YieldBalance>()) {
  for (final balance in item.balances.whereType<YieldBalanceItem>()) {
    print('${balance.amount} ${balance.token.symbol} (earning: ${balance.isEarning})');
  }
}

Exiting Yield Positions

Use the exit method to withdraw from yield positions. For complete API documentation, see the Yield.xyz exit yield reference.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final userAddress = await portal.getAddress('eip155:11155111');

final exitResponse = await portal.yieldIntegrations.yieldxyz.exit(
  yieldId: 'ethereum-sepolia-link-aave-v3-lending',
  address: userAddress, // Optional - defaults to current wallet address
  amount: '0.001',
);

final transactions = exitResponse.transactions;

// Process transactions, this is described in the "Transaction Processing" section below
await processTransactions(transactions, 'eip155:11155111');

Managing Yield Positions

If your Portal client has entered into a yield balance, they may have a yield balance that has an available pendingActions. You can use the manage method to perform actions on existing yield positions. For example, if the balance has a pendingAction of WITHDRAW or CLAIM_REWARDS, you can use the manage method to withdraw or claim rewards from the yield balance. For complete API documentation, see the Yield.xyz manage yield reference.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final userAddress = await portal.getAddress('eip155:11155111');

final manageResponse = await portal.yieldIntegrations.yieldxyz.manage(
  yieldId: 'ethereum-sepolia-link-aave-v3-lending',
  address: userAddress,
  action: 'WITHDRAW', // Replace with the balance's `pendingAction` item's `type` value.
  passthrough: 'eyJhZGRyZXNzZXMiOnsiYWRkcmVzcyI6ImNvc21vczF5ZXk...', // Replace with the balance's `pendingAction` item's `passthrough` value.
);

final transactions = manageResponse.transactions;

// Process transactions, this is described in the "Transaction Processing" section below
await processTransactions(transactions, 'eip155:11155111');
The passthrough parameter is required and must come from a balance’s pending action. Call getBalances() first, find a YieldBalanceItem with non-empty pendingActions, and use the passthrough value from the relevant pending action. The SDK throws a PortalException with code MISSING_PASSTHROUGH if it is null or empty.

Getting Historical Actions

Retrieve the history of yield actions for an address. For complete API documentation, see the Yield.xyz get actions reference.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final userAddress = await portal.getAddress('eip155:11155111');

final response = await portal.yieldIntegrations.yieldxyz.getHistoricalActions(
  address: userAddress,
);

final pastActions = response.items;

// Process and display past yield actions
for (final action in pastActions.whereType<YieldXyzHistoricalActionItem>()) {
  print('${action.intent.name}: ${action.status.name} - ${action.amount} (${action.createdAt})');
}

Transaction Processing

Yield operations can require multiple transactions. Process them sequentially, submit each, track it, and wait for on-chain confirmation (e.g. using eth_getTransactionReceipt) before proceeding to the next. For complete API documentation, see the Yield.xyz submit transaction hash reference and get transaction details reference.
For account abstraction enabled Portal clients, use eth_getUserOperationReceipt instead of eth_getTransactionReceipt since signing returns a user operation hash, not a transaction hash.Extract the transaction hash from response.result.receipt.transactionHash and use that transaction hash when calling portal.yieldIntegrations.yieldxyz.track().
import 'dart:convert';
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

Future<void> processTransactions(
  List<YieldXyzActionTransaction?> transactions,
  String chainId,
) async {
  final sorted = transactions
      .whereType<YieldXyzActionTransaction>()
      .toList()
    ..sort((a, b) => a.stepIndex.compareTo(b.stepIndex));

  for (final tx in sorted) {
    if (tx.unsignedTransaction != null &&
        tx.status == YieldXyzActionTransactionStatus.created) {
      final success = await signAndSubmitAndConfirm(tx, chainId);
      if (!success) break;
    }
  }
}

Future<bool> signAndSubmitAndConfirm(
  YieldXyzActionTransaction transaction,
  String chainId,
) async {
  final unsignedTxJson = transaction.unsignedTransaction;
  if (unsignedTxJson == null) return false;

  // Parse the unsigned transaction JSON string
  final txParams = jsonDecode(unsignedTxJson) as Map<String, dynamic>;

  try {
    // Sign and send the transaction
    final txHash = await portal.sendTransaction(
      chainId: chainId,
      to: txParams['to'] as String,
      value: txParams['value'] as String? ?? '0x0',
      data: txParams['data'] as String?,
      // Portal handles gas estimation automatically
    );

    // Track the transaction with the yield system
    await portal.yieldIntegrations.yieldxyz.track(
      transaction.id,
      txHash,
    );

    // Wait for transaction confirmation
    return await waitForReceipt(txHash: txHash, chainId: chainId);
  } catch (e) {
    print('Error signing and submitting transaction: $e');
    return false;
  }
}

Future<bool> waitForReceipt({
  required String txHash,
  required 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>?;
        if (receipt != null) {
          final status = receipt['status'] as String?;
          if (status == '0x1') return true;  // Transaction succeeded
          if (status == '0x0') return false; // Transaction reverted
        }
      }
    } catch (e) {
      // Continue waiting if request fails
      continue;
    }
  }

  return false; // Timeout
}

Best Practices

  1. Always check yield availability before attempting to enter positions
  2. Process transactions sequentially as yield operations often require multiple steps and are dependent on previous transactions being mined successfully
  3. Handle network errors gracefully and provide user feedback
  4. Monitor transaction status and provide progress updates to users
  5. Validate user balances before initiating yield operations
  6. Check for pending actions in balances before calling the manage method to avoid conflicting or duplicate operations
  7. Test on testnets first (e.g., Sepolia) before moving to mainnet to validate flows and configurations in a safe environment

Supported Networks

The yield functionality supports various networks including:
  • Monad (eip155:143)
  • Monad Testnet (eip155:10143)
  • Arbitrum (eip155:42161)
  • Avalanche C (eip155:43114)
  • Base (eip155:8453)
  • Base Sepolia (eip155:84532)
  • Celo (eip155:42220)
  • Core (eip155:1116)
  • Ethereum (eip155:1)
  • Ethereum Sepolia (eip155:11155111)
  • Fantom (eip155:250)
  • Gnosis (eip155:100)
  • Harmony (eip155:1666600000)
  • Hyperevm (eip155:999)
  • Katana (eip155:747474)
  • Linea (eip155:59144)
  • Moonriver (eip155:1285)
  • Optimism (eip155:10)
  • Optimism Sepolia (eip155:11155420)
  • Plasma (eip155:9745)
  • Polygon (eip155:137)
  • Polygon Amoy (eip155:80002)
  • Sonic (eip155:146)
  • Unichain (eip155:130)
  • Viction (eip155:88)
  • zkSync (eip155:324)
  • Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)
  • Solana Devnet (solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1)
  • Stellar (stellar:pubnet)
  • Stellar Testnet (stellar:testnet)
  • Tron (tron:mainnet)

Next Steps