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

# Earn with Yield.xyz

> Learn how to discover, enter, manage, and exit yield opportunities.

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](./create-a-wallet))
* Yield.xyz integration enabled in your Portal Dashboard (see [Yield.xyz Integration](/integrations/Yield/yield-xyz))

## Discovering Yield Opportunities

Use the `discover` method to find available yield opportunities.

For complete API documentation, see the [Yield.xyz API reference](https://docs.yield.xyz/reference/yieldscontroller_getyields).

```swift theme={null}
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)")
}
```

<Tip>
  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`
</Tip>

## 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](https://docs.yield.xyz/reference/actionscontroller_enteryield).

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.

```swift theme={null}
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](https://docs.yield.xyz/reference/yieldscontroller_getaggregatebalances).

```swift theme={null}
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)")
}
```

<Note>
  We recommend always specifying a `yieldId` on each balance query. When `yieldId` is provided, Yield.xyz can resolve balances directly, so you don't need to call the `track` endpoint after entering or exiting positions.

  ```swift theme={null}
  let balanceRequest = YieldXyzGetBalancesRequest(
      queries: [
          YieldXyzBalanceQuery(
              address: userAddress,
              network: "eip155:11155111",
              yieldId: "ethereum-sepolia-link-aave-v3-lending"
          )
      ]
  )
  ```
</Note>

## Exiting Yield Positions

Use the `exit` method to withdraw from yield positions.

For complete API documentation, see the [Yield.xyz exit yield reference](https://docs.yield.xyz/reference/actionscontroller_exityield).

```swift theme={null}
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)")
}
```

## High-Level Methods

Use `deposit` and `withdraw` when you want one call for the full flow: resolve the yield, build the action, sign and send each transaction in order, wait for confirmation between steps, and report each hash back to Yield.xyz.

Both are `async throws` and take the same parameter shape — `YieldWithdrawParams` and `YieldWithdrawResult` are type aliases for the deposit types.

### Signatures

```swift theme={null}
func deposit(params: YieldDepositParams, options: YieldSubmitOptions?) async throws -> YieldDepositResult
func withdraw(params: YieldWithdrawParams, options: YieldSubmitOptions?) async throws -> YieldWithdrawResult
```

Convenience overloads drop the options argument entirely:

```swift theme={null}
func deposit(params: YieldDepositParams) async throws -> YieldDepositResult
func withdraw(params: YieldWithdrawParams) async throws -> YieldWithdrawResult
```

### Essential parameters

`YieldDepositParams`:

| Parameter   | Type                      | Required | Description                                                              |
| ----------- | ------------------------- | -------- | ------------------------------------------------------------------------ |
| `target`    | `YieldActionTarget`       | Yes      | Which yield to act on. See below.                                        |
| `amount`    | `String`                  | Yes      | Amount to deposit or withdraw. Merged into the action arguments.         |
| `arguments` | `YieldXyzEnterArguments?` | No       | Protocol-specific inputs, such as `validatorAddress` for native staking. |

There is **no `address` parameter.** The wallet address is resolved from your `Portal` instance for whichever chain the yield resolves to. If no wallet exists for that chain, the call throws `YieldXyzError.addressUnavailable`.

`YieldActionTarget` is an enum with two cases, so you cannot accidentally supply both forms or neither:

```swift theme={null}
public enum YieldActionTarget {
  case yieldId(String)
  case chainAndToken(chain: String, token: String)
}
```

| Case                           | Behavior                                                                                                                                          |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.yieldId("…")`                | Acts on that yield directly, skipping the defaults lookup.                                                                                        |
| `.chainAndToken(chain:token:)` | Resolves the yield from your Portal yield defaults. `chain` must be a **full CAIP-2 id** (`"eip155:1"`); `token` must match the defaults exactly. |

<Warning>
  `.chainAndToken` requires full CAIP-2. A bare `"1"` throws `YieldXyzError.invalidChainId`. This differs from Li.Fi's `tradeAsset`, which forwards whatever chain format you give it — the two APIs shipped in the same release but do not accept the same values.
</Warning>

`YieldSubmitOptions`:

| Option                | Type                               | Default | Description                                                                                                                  |
| --------------------- | ---------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `onProgress`          | `((YieldSubmitProgress) -> Void)?` | `nil`   | Fired per transaction with `.signing`, `.submitted`, `.confirming`, `.confirmed`.                                            |
| `pollIntervalSeconds` | `Int`                              | `4`     | **Seconds** between confirmation polls. Clamped to a minimum of `1`.                                                         |
| `timeoutSeconds`      | `Int`                              | `900`   | **Seconds** to wait for a transaction to confirm before treating it as uncertain. Clamped to at least `pollIntervalSeconds`. |

<Note>
  These are **seconds**, not milliseconds. The Android SDK uses `pollIntervalMs` and `timeoutMs` for the same concepts, so a value copied across platforms will be wrong by a factor of 1000.
</Note>

There is no per-call signer or confirmation override on iOS — signing always goes through the Portal MPC signer.

### Return value

`YieldDepositResult`:

| Field                     | Type                      | Description                                                                                                                                                                         |
| ------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hashes`                  | `[String]`                | Submitted transaction hashes, in order. Contains every hash that was submitted, regardless of final outcome.                                                                        |
| `yieldId`                 | `String`                  | The resolved yield id.                                                                                                                                                              |
| `status`                  | `YieldSubmitResultStatus` | `.success`, `.partialSuccess`, or `.failed`. See below.                                                                                                                             |
| `chain`                   | `String?`                 | Set only when you targeted via `.chainAndToken`.                                                                                                                                    |
| `token`                   | `String?`                 | Set only when you targeted via `.chainAndToken`.                                                                                                                                    |
| `yieldOpportunityDetails` | `YieldOpportunityDetails` | Action metadata — `yieldId`, `intent`, `type`, `executionPattern`, `status`, `amount`, `amountUsd`. This is action-level detail, not the full opportunity; use `discover` for that. |

### Handling Results

| Status            | Meaning                                                                                                                       |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `.success`        | Every transaction confirmed, or the action required no confirmations.                                                         |
| `.partialSuccess` | Execution stopped before all transactions confirmed — typically a confirmation timeout. Earlier transactions may have landed. |
| `.failed`         | A transaction was confirmed as failed on-chain. Execution stopped immediately at that point.                                  |

```swift theme={null}
let result = try await portal.yield.yieldxyz.deposit(params: params)

switch result.status {
case .success:
    print("All transactions confirmed: \(result.hashes)")
case .partialSuccess:
    print("Stopped before completing: \(result.hashes) — check the yield balance")
case .failed:
    print("Transaction failed on-chain: \(result.hashes.last ?? "none")")
}
```

<Warning>
  A non-empty `hashes` array does **not** mean success. Hashes are recorded as transactions are submitted, before their outcome is known. Always branch on `status`.
</Warning>

### Example (deposit with progress)

```swift theme={null}
import PortalSwift

do {
    let result = try await portal.yield.yieldxyz.deposit(
        params: YieldDepositParams(
            target: .yieldId("ethereum-sepolia-link-aave-v3-lending"),
            amount: "0.001"
        ),
        options: YieldSubmitOptions(
            onProgress: { progress in
                print("[deposit] \(progress.step.rawValue) \(progress.index + 1)/\(progress.total) \(progress.hash ?? "")")
            },
            pollIntervalSeconds: 4,
            timeoutSeconds: 300
        )
    )

    print("Status: \(result.status.rawValue)")
    print("Hashes: \(result.hashes)")
} catch let error as YieldXyzError {
    print("Deposit failed: \(error.errorDescription ?? "unknown")")
}
```

### Example (targeting by chain and token)

```swift theme={null}
let result = try await portal.yield.yieldxyz.deposit(
    params: YieldDepositParams(
        target: .chainAndToken(chain: "eip155:11155111", token: "ETH"),
        amount: "0.0000001"
    )
)

print("Resolved yield: \(result.yieldId)")
print("Chain: \(result.chain ?? "") Token: \(result.token ?? "")")
```

`chain` and `token` on the result are populated only when you target this way.

### Example (withdraw)

`withdraw` takes identical parameter shapes:

```swift theme={null}
let result = try await portal.yield.yieldxyz.withdraw(
    params: YieldWithdrawParams(
        target: .yieldId("ethereum-sepolia-link-aave-v3-lending"),
        amount: "0.001"
    )
)
```

### Errors

`deposit`, `withdraw`, and `getValidators` throw `YieldXyzError`:

| Case                              | When                                                                               |
| --------------------------------- | ---------------------------------------------------------------------------------- |
| `portalNotInitialized`            | No Portal instance available for signing. Cannot occur on `portal.yield.yieldxyz`. |
| `emptyYieldId`                    | An empty `yieldId` was supplied.                                                   |
| `invalidChainId(String)`          | `chain` was not a full CAIP-2 id.                                                  |
| `noYieldForChainToken(String)`    | No default yield matches that chain and token pair.                                |
| `yieldNotFound(String)`           | The yield id does not exist.                                                       |
| `addressUnavailable(String)`      | No wallet address for the resolved chain. Create or load a wallet first.           |
| `noTransactions`                  | The action response contained no transactions.                                     |
| `missingTransactionField(String)` | A transaction was missing a required field.                                        |
| `unsupportedNetwork(String)`      | The action resolved to a network the high-level flow cannot sign for.              |
| `invalidSignResponse`             | Signing returned an unusable response.                                             |
| `transactionFailed(String)`       | A transaction failed on-chain. Carries the hash.                                   |
| `noValidators(String)`            | `getValidators` found none for that yield.                                         |
| `apiError(String)`                | The backend returned an error payload. Carries the message.                        |

All cases conform to `LocalizedError`, so `errorDescription` gives a readable message.

## Get Validators

Fetches the validator addresses for a native-staking yield. These are used for approval flows and for populating `arguments.validatorAddress`.

```swift theme={null}
func getValidators(yieldId: String) async throws -> [YieldXyzValidator]
```

Available on both the namespace and the provider — `portal.yield.getValidators(yieldId:)` is a passthrough to `portal.yield.yieldxyz.getValidators(yieldId:)`.

```swift theme={null}
let validators = try await portal.yield.getValidators(yieldId: "monad-testnet-mon-native-staking")

for validator in validators {
    print(validator.address, validator.name ?? "unnamed")
}
```

Only `address` is non-optional on `YieldXyzValidator`. Everything else — `name`, `logoURI`, `website`, `rewardRate`, `provider`, `commission`, `tvlUsd`, `votingPower`, `preferred`, `minimumStake`, `status`, and the rest — depends on the protocol and may be absent.

Throws `YieldXyzError.noValidators` when the response contains no validators, and `YieldXyzError.apiError` when the backend returns an error payload.

### Low-level additions

Two API methods back the high-level flow and are available directly on `portal.api.yieldxyz`:

```swift theme={null}
func getYieldDefaults(includeOpportunities: Bool?) async throws -> YieldXyzGetDefaultsResponse
func getYieldValidators(yieldId: String) async throws -> YieldXyzGetValidatorsResponse
```

`getYieldDefaults` returns `data` as a dictionary keyed **`"{caip2}:{TOKEN}"`** — for example `"eip155:1:USDC"`. That key is exactly what `.chainAndToken` resolves against, so this is how you discover valid chain and token pairs. Pass `includeOpportunities: true` to populate each entry's `opportunity` field.

<Note>
  Unlike the other Yield.xyz responses, the defaults payload is **not** wrapped in `rawResponse` — read it from `data` directly.
</Note>

`YieldXyzEnterArguments` also gained a builder helper for replacing just the amount:

```swift theme={null}
let updated = existingArguments.withAmount("0.5")
```

## 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](https://docs.yield.xyz/reference/actionscontroller_manageyield).

```swift theme={null}
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](https://docs.yield.xyz/reference/actionscontroller_getactions).

```swift theme={null}
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 (low-level enter / exit / manage)

If you use [`deposit`](#high-level-methods) or [`withdraw`](#high-level-methods), skip this section — the SDK already sequences transactions, waits for confirmation between steps, and reports hashes to Yield.xyz.

This section applies to manual flows built on `enter`, `exit`, or `manage`.

<Tip>
  When `deposit` and `withdraw` sign an EVM transaction they rebuild it from the yield action's `unsignedTransaction`, copying `to`, `from`, `value`, `data` and the fee fields (`gasLimit`/`gas`, `maxFeePerGas`, `maxPriorityFeePerGas`, `gasPrice`) but **deliberately omitting `nonce`**, so the MPC signer fetches the pending nonce at signing time. If you sign manually, drop the planning `nonce` the same way — reusing it across a multi-transaction action causes nonce collisions.
</Tip>

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](https://docs.yield.xyz/reference/transactionscontroller_submittransactionhash) and [get transaction details reference](https://docs.yield.xyz/reference/transactionscontroller_gettransaction).

<Tip>
  For account abstraction enabled Portal clients, use `eth_getUserOperationReceipt` instead of `eth_getTransactionReceipt` to wait for confirmation, since signing returns a *user operation hash*, not a transaction hash.

  If you don't specify a `yieldId` on your balance queries, you'll need to call `track` after each transaction so Yield.xyz can attribute the position. Pass the **transaction hash** (extracted from `response.result.receipt.transactionHash` for AA clients), not the user operation hash.
</Tip>

```swift theme={null}
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(
            chainId: transaction.network,
            method: .eth_sendTransaction,
            params: [ethTransaction],
            options: RequestOptions(signatureApprovalMemo: "Yield transaction")
        )

        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: chainId,
                method: .eth_getTransactionReceipt,
                params: [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
}
```

## Enum Handling

### Unknown Values

Yield.xyz aggregates many protocols and onboards new ones regularly. Before 7.3.0, a response containing an enum value the SDK did not recognize failed to decode and the entire call threw — a single new value from an upstream provider could break `discover` or `getBalances` outright.

As of 7.3.0, unrecognized values decode to `.unknown` instead of failing. Your app keeps working when Yield.xyz onboards a new protocol, action type, or reward schedule, without waiting for an SDK upgrade.

If you arrived here from a compiler error, this is the fix:

```swift theme={null}
// Before 7.3.0 this compiled. Under 7.3.0 it does not — the compiler reports
// "switch must be exhaustive" because `.unknown` is unhandled.
switch component.yieldSource {
case .staking: showStakingBadge()
case .lending: showLendingBadge()
// … remaining cases
}

// 7.3.0 — add a case for values the SDK does not recognize.
switch component.yieldSource {
case .staking: showStakingBadge()
case .lending: showLendingBadge()
// … remaining cases
case .unknown: showGenericBadge()
}
```

<Tip>
  Prefer an explicit `case .unknown` over `default:`. With `default:`, the next case Portal adds falls into it silently; with an explicit `.unknown`, you get a compiler error and a chance to handle the new value deliberately.
</Tip>

### Affected enums

Thirteen public enums gained `.unknown` in 7.3.0, grouped here by the type that declares them so you can find the one your compiler flagged:

**From `YieldXyzGetYieldsResponse`:** `YieldXyzRateType`, `YieldXyzSource`, `YieldXyzRewardSchedule`, `YieldXyzRewardClaiming`, `YieldXyzArgumentFieldName`, `YieldXyzArgumentFieldType`

**From `YieldXyzEnterYieldResponse`:** `YieldXyzActionIntent`, `YieldXyzActionType`, `YieldXyzActionExecutionPattern`, `YieldXyzActionStatus`, `YieldXyzActionTransactionStatus`, `YieldXyzActionTransactionType`

**From `YieldXyzGetYieldsRequest`:** `YieldXyzMechanicsType`

All thirteen conform to a new public protocol, `YieldXyzUnknownTolerantEnum`, which supplies the tolerant `Codable` decoding and an `unknownValue` fallback. You never implement or reference it directly — it exists so these enums share one decoding path, and it may show up in autocomplete or a stack trace.

The reach extends past `discover`. `YieldXyzActionIntent`, `YieldXyzActionType`, `YieldXyzActionExecutionPattern`, and `YieldXyzActionStatus` are all fields on `YieldOpportunityDetails`, which is returned by the high-level [`deposit` and `withdraw`](#high-level-methods) methods. Code reading `result.yieldOpportunityDetails.status` is affected — note that these four fields are optional, so an exhaustive `switch` needs to handle `nil` as well as `.unknown`:

```swift theme={null}
switch result.yieldOpportunityDetails.status {
case .SUCCESS: markComplete()
case .PROCESSING, .WAITING_FOR_NEXT, .CREATED: keepPolling()
case .FAILED, .CANCELED, .STALE: markFailed()
case .unknown: showRawStatus()
case nil: showPending()
}
```

### Handling `.unknown` in your code

1. Treat `.unknown` as "display it, don't act on it". Render a neutral label rather than hiding the row, so users still see their position.
2. Don't branch business logic on `.unknown`. If a decision depends on knowing the exact source or status, read the raw response value rather than inferring from the enum.
3. Log occurrences along with the `yieldId`. A rising `.unknown` rate is a signal to upgrade the SDK.

### New `lending` source

`YieldXyzSource` gained a `lending` case in 7.3.0. You encounter this enum on each entry of a discovered opportunity's `rewardRate.components` array, as `component.yieldSource`. The full case list as of 7.3.0:

```swift theme={null}
public enum YieldXyzSource: String, YieldXyzUnknownTolerantEnum {
    case staking
    case restaking
    case protocol_incentive
    case points
    case lending
    case lending_interest
    case mev
    case real_world_asset_yield
    case validator_commission
    case unknown
}
```

The raw string values match the case names exactly — `.protocol_incentive` encodes as `"protocol_incentive"` — so no case has a separate raw value to map.

<Note>
  `lending` and `lending_interest` are separate cases and Yield.xyz uses both. Treat them as distinct values rather than assuming one supersedes the other.
</Note>

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

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

* Learn about [managing wallet lifecycle states](./manage-wallet-lifecycle-states)
* Explore [transaction simulation](./evaluate-a-transaction)
* Check out [Portal API methods](./portal-api-methods)
