Portal’s iOS SDK provides comprehensive yield opportunities capabilities through the portal.yield.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.
let request = YieldXyzGetYieldsRequest(
offset: 0,
limit: 10,
network: "eip155:11155111" // Sepolia network
// ... other parameters
)
do {
let response = try await portal.yield.yieldxyz.discover(request: request)
if let rawResponse = response.data?.rawResponse {
let yieldOpportunities = rawResponse.items
// Process and display yield opportunities
}
} catch {
// Handle error
print("Error discovering yields: \(error)")
}
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.
do {
let userAddress = try await portal.getAddress()
let enterRequest = YieldXyzEnterRequest(
yieldId: "ethereum-sepolia-link-aave-v3-lending",
address: userAddress,
arguments: YieldXyzEnterArguments(
amount: "1" // 1 LINK token
)
)
let enterResponse = try await portal.yield.yieldxyz.enter(request: enterRequest)
if let rawResponse = enterResponse.data?.rawResponse {
let transactions = rawResponse.transactions
// Process transactions, this is described in the "Transaction Processing" section below
try await processTransactions(transactions)
}
} catch {
// Handle error
print("Error entering yield position: \(error)")
}
Checking Yield Balances
Retrieve current yield positions and balances.
For complete API documentation, see the Yield.xyz get balances reference.
do {
let userAddress = try await portal.getAddress()
let balanceRequest = YieldXyzGetBalancesRequest(
queries: [
YieldXyzBalanceQuery(
address: userAddress,
network: "eip155:11155111" // Sepolia testnet
)
]
)
let response = try await portal.yield.yieldxyz.getBalances(request: balanceRequest)
if let rawResponse = response.data?.rawResponse {
let yieldPositions = rawResponse.items
// Process and display yield positions information
}
} catch {
// Handle error
print("Error getting balances: \(error)")
}
Exiting Yield Positions
Use the exit method to withdraw from yield positions.
For complete API documentation, see the Yield.xyz exit yield reference.
do {
let userAddress = try await portal.getAddress()
let exitRequest = YieldXyzExitRequest(
yieldId: "ethereum-sepolia-link-aave-v3-lending",
address: userAddress,
arguments: YieldXyzEnterArguments(amount: "0.001")
)
let exitResponse = try await portal.yield.yieldxyz.exit(request: exitRequest)
if let rawResponse = exitResponse.data?.rawResponse {
let transactions = rawResponse.transactions
// Process transactions, this is described in the "Transaction Processing" section below
try await processTransactions(transactions)
}
} catch {
// Handle error
print("Error exiting yield position: \(error)")
}
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.
do {
let userAddress = try await portal.getAddress()
let manageRequest = YieldXyzManageYieldRequest(
yieldId: "ethereum-sepolia-link-aave-v3-lending",
address: userAddress,
action: YieldActionType.WITHDRAW, // Replace with the balance's `pendingAction` item's `type` value.
passthrough: "eyJhZGRyZXNzZXMiOnsiYWRkcmVzcyI6ImNvc21vczF5ZXk..." // Replace with the balance's `pendingAction` item's `passthrough` value.
)
let manageResponse = try await portal.yield.yieldxyz.manage(request: manageRequest)
if let rawResponse = manageResponse.data?.rawResponse {
let transactions = rawResponse.transactions
// Process transactions, this is described in the "Transaction Processing" section below
try await processTransactions(transactions)
}
} catch {
// Handle error
print("Error managing yield position: \(error)")
}
Getting Historical Actions
Retrieve the history of yield actions for an address.
For complete API documentation, see the Yield.xyz get actions reference.
do {
let userAddress = try await portal.getAddress()
let request = YieldXyzGetHistoricalActionsRequest(address: userAddress)
let response = try await portal.yield.yieldxyz.getHistoricalActions(request: request)
if let rawResponse = response.data?.rawResponse {
let pastActions = rawResponse.items
// Process and display past yield actions
}
} catch {
// Handle error
print("Error getting historical actions: \(error)")
}
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.yield.yieldxyz.track().
func processTransactions(_ transactions: [YieldActionTransaction]) async throws {
let sorted = transactions.sorted { $0.stepIndex < $1.stepIndex }
for tx in sorted {
if tx.unsignedTransaction != nil && tx.status == YieldActionTransactionStatus.CREATED {
let success = await signAndSubmitAndConfirm(transaction: tx)
if !success { break }
}
}
}
func signAndSubmitAndConfirm(transaction: YieldActionTransaction) async -> Bool {
guard let unsignedTxJson = transaction.unsignedTransaction as? String else {
return false
}
// Parse the unsigned transaction JSON string
guard let jsonData = unsignedTxJson.data(using: .utf8),
let txParams = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
else {
return false
}
// Create ETHTransactionParam from the parsed JSON
let ethTransaction = ETHTransactionParam(
from: txParams["from"] as? String ?? "",
to: txParams["to"] as? String ?? "",
value: txParams["value"] as? String ?? "0x0",
data: txParams["data"] as? String ?? "0x"
// Portal handles gas estimation automatically
)
do {
// Sign and send the transaction
let sendResponse = try await portal.request(
transaction.network,
withMethod: .eth_sendTransaction,
andParams: [ethTransaction]
)
guard let txHash = sendResponse.result as? String else {
return false
}
// Track the transaction with the yield system
_ = try await portal.yield.yieldxyz.track(
transactionId: transaction.id,
txHash: txHash
)
// Wait for transaction confirmation
return await waitForReceipt(txHash: txHash, chainId: transaction.network)
} catch {
print("Error signing and submitting transaction: \(error)")
return false
}
}
func waitForReceipt(
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 innerResponse = response.result as? EthTransactionResponse,
let status = innerResponse.result?.status {
if status == "0x1" {
return true // Transaction succeeded
} else if status == "0x0" {
return false // Transaction reverted
}
}
} catch {
// Continue waiting if request fails
continue
}
}
return false // Timeout
}
Best Practices
- Always check yield availability before attempting to enter positions
- Process transactions sequentially as yield operations often require multiple steps and are dependent on previous transactions being mined successfully
- Handle network errors gracefully and provide user feedback
- Monitor transaction status and provide progress updates to users
- Validate user balances before initiating yield operations
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