> ## 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 React Native 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](../../../resources/integrations/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).

```typescript theme={null}
import type { YieldXyzGetYieldsRequest } from '@portal-hq/core'

const request: YieldXyzGetYieldsRequest = {
  offset: 0,
  limit: 10,
  network: 'eip155:11155111',
  // Optional: type, token, provider, hasCooldownPeriod, hasWarmupPeriod, search, sort
}

const response = await portal.yield.yieldxyz.discover(request)
const yieldOpportunities = response.data?.rawResponse?.items
```

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

```typescript theme={null}
import type { YieldXyzEnterRequest } from '@portal-hq/core'

const addresses = await portal.addresses
const userAddress = addresses?.eip155

if (!userAddress) {
  throw new Error('No EVM address found')
}

const enterRequest: YieldXyzEnterRequest = {
  yieldId: 'ethereum-sepolia-link-aave-v3-lending',
  address: userAddress,
  amount: '1',
  // Optional: arguments (validatorAddress, inputToken, etc.)
}

const enterResponse = await portal.yield.yieldxyz.enter(enterRequest)
const transactions = enterResponse.data?.rawResponse?.transactions

await processTransactions(transactions)
```

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

```typescript theme={null}
import type { YieldXyzGetBalancesRequest } from '@portal-hq/core'

const addresses = await portal.addresses
const userAddress = addresses?.eip155

if (!userAddress) {
  throw new Error('No EVM address found')
}

const balanceRequest: YieldXyzGetBalancesRequest = {
  queries: [
    {
      address: userAddress,
      network: 'eip155:11155111',
      // Optional: yieldId
    },
  ],
}

const balanceResponse = await portal.yield.yieldxyz.getBalances(balanceRequest)
const yieldPositions = balanceResponse.data?.rawResponse?.items

yieldPositions?.forEach((position) => {
  position.balances.forEach((balance) => {
    // Check balance.pendingActions for available management actions
  })
})
```

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

  ```typescript theme={null}
  const balanceRequest: YieldXyzGetBalancesRequest = {
    queries: [
      {
        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).

```typescript theme={null}
import type { YieldXyzExitRequest } from '@portal-hq/core'

const addresses = await portal.addresses
const userAddress = addresses?.eip155

if (!userAddress) {
  throw new Error('No EVM address found')
}

const exitRequest: YieldXyzExitRequest = {
  yieldId: 'ethereum-sepolia-link-aave-v3-lending',
  address: userAddress,
  amount: '0.001',
  // Optional: arguments (validatorAddress, etc.)
}

const exitResponse = await portal.yield.yieldxyz.exit(exitRequest)
const transactions = exitResponse.data?.rawResponse?.transactions

await processTransactions(transactions)
```

## 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 between steps when configured, and report hashes to Yield.xyz.

Specify the position with **`yieldId`**, or with **`chain`** (CAIP-2) + **`token`** (must match your Portal yield defaults). Both methods require **`amount`** and **`address`**. Optionally set **`arguments`**.

### Signatures

```typescript theme={null}
deposit(
  params: YieldDepositParams,
  options?: YieldSubmitOptions
): Promise<YieldDepositResult>

withdraw(
  params: YieldWithdrawParams,
  options?: YieldSubmitOptions
): Promise<YieldWithdrawResult>
```

### Essential parameters

| Name                             | Required | Description                                      |
| -------------------------------- | -------- | ------------------------------------------------ |
| `yieldId` *or* `chain` + `token` | Yes      | Non-empty `yieldId` wins over `chain` / `token`. |
| `amount`                         | Yes      | Amount to deposit or withdraw.                   |
| `address`                        | Yes      | Wallet address for the action.                   |
| `arguments`                      | No       | Protocol-specific Yield.xyz arguments.           |

Second argument `YieldSubmitOptions`:

| Name                     | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onProgress`             | No       | Callback fired at each step per transaction: `signing`, `submitted`, `confirming`, `confirmed`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `signAndSendTransaction` | No       | Per-call signer. Receives the unsigned transaction payload and the network string; must return the submitted tx hash. Takes priority over the instance-level setter. When omitted, Portal's built-in MPC signer is used automatically.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `waitForConfirmation`    | No       | Per-call confirmation override. Called after each broadcast with `(txHash, network)`. **MUST return `true` for confirmed success.** The behavior for each return value is:<br />• **`true`** → Transaction confirmed. Execution continues.<br />• **`false`** → Transaction **failed on-chain** (e.g., reverted with receipt status `0x0`). Execution stops immediately with `status: 'FAILED'`.<br />• **Throws or times out** → Execution stops safely with `status: 'PARTIAL_SUCCESS'` (or `'FAILED'` if no prior confirmations).<br /><br />When omitted, `portal.waitForConfirmation` polls `eth_getTransactionReceipt` via `gatewayConfig` with the same strict behavior. |
| `evmPollerOptions`       | No       | Tune the built-in EVM receipt poller (`pollIntervalMs`, `timeoutMs`) when the default `portal.waitForConfirmation` is active. Has no effect when `waitForConfirmation` is provided.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

### Return value

| Field                     | Description                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hashes`                  | Submitted transaction hashes, in order. Contains hashes for all transactions that were submitted, regardless of final outcome.                                                                                                                                                                                                                                        |
| `yieldId`                 | Resolved yield id.                                                                                                                                                                                                                                                                                                                                                    |
| `status`                  | **Operation result status:**<br />• `SUCCESS` — All transactions confirmed successfully (or no `waitForConfirmation` configured).<br />• `PARTIAL_SUCCESS` — Some transactions confirmed, but execution stopped before completing all steps (e.g., timeout).<br />• `FAILED` — A transaction failed on-chain (receipt status `0x0`) or confirmation returned `false`. |
| `chain` / `token`         | Set when you passed `chain` + `token`.                                                                                                                                                                                                                                                                                                                                |
| `yieldOpportunityDetails` | Action metadata from Yield.xyz.                                                                                                                                                                                                                                                                                                                                       |

### Handling Results

Always check the `status` field to determine the outcome:

```typescript theme={null}
const result = await portal.yield.yieldxyz.deposit(params, options)

switch (result.status) {
  case 'SUCCESS':
    console.log('All transactions confirmed:', result.hashes)
    // Safe to show success UI
    break
    
  case 'PARTIAL_SUCCESS':
    console.warn('Some transactions completed:', result.hashes)
    // Check yield balance to determine actual position state
    // May need user action to complete or retry remaining steps
    break
    
  case 'FAILED':
    console.error('Transaction failed on-chain:', result.hashes[result.hashes.length - 1])
    // Show error UI, check transaction receipt for revert reason
    break
}
```

**Important:** A non-empty `hashes` array does NOT guarantee success. Always check `status`.

**Strict confirmation semantics:**

Yield operations use **strict confirmation**. Only `waitForConfirmation(...) === true` is considered success. The behavior for each confirmation result is:

* **`true`** → Transaction confirmed successfully. Execution continues to next step.
* **`false`** → Transaction **failed on-chain** (e.g., receipt `status: "0x0"` indicates revert). Execution **stops immediately** and returns `status: 'FAILED'`.
* **Throws or times out** → Execution stops safely and returns `status: 'PARTIAL_SUCCESS'` (if some transactions were confirmed) or `status: 'FAILED'` (if no transactions completed).

The result always includes:

* `hashes` — Transaction hashes submitted up to the point execution stopped.
* `status` — One of `SUCCESS`, `PARTIAL_SUCCESS`, or `FAILED`.

There is no optimistic continuation after a failure.

**How reverted transactions are detected:**

When using the default `portal.waitForConfirmation` (or a custom implementation), the SDK detects on-chain failures by checking the transaction receipt:

```typescript theme={null}
// Example: built-in behavior
const receipt = await eth_getTransactionReceipt(txHash)

if (receipt.status === '0x1') {
  // Success: waitForConfirmation returns true
  return true
}

if (receipt.status === '0x0') {
  // Revert: waitForConfirmation returns false
  // SDK immediately stops execution and sets status: 'FAILED'
  return false
}
```

This ensures that a reverted transaction (`status: "0x0"`) is **never** reported as `SUCCESS`.

### Example (`deposit` with progress and poller tuning)

```typescript theme={null}
import { Portal } from '@portal-hq/core'

const portal = new Portal({
  apiKey: 'YOUR_PORTAL_CLIENT_API_KEY',
  backup: {
    /* ... */
  },
  gatewayConfig: { 'eip155:11155111': 'https://YOUR_RPC_URL' },
})

async function depositWithProgress() {
  const addresses = await portal.addresses
  const address = addresses?.eip155
  if (!address) throw new Error('No EVM address')

  const result = await portal.yield.yieldxyz.deposit(
    {
      chain: 'eip155:11155111',
      token: 'ETH',
      amount: '0.0000001',
      address,
    },
    {
      onProgress: (e) => {
        console.log(
          `[deposit] ${e.step} step ${e.index + 1}/${e.total}`,
          e.hash ?? '',
        )
      },
      // Optional: tune the EVM receipt poller for this call only
      evmPollerOptions: { pollIntervalMs: 4000, timeoutMs: 300_000 },
    },
  )

  console.log('Status:', result.status)
  console.log('Hashes:', result.hashes)
  
  // Always check status to determine outcome
  if (result.status === 'SUCCESS') {
    console.log('Deposit completed successfully!')
  } else if (result.status === 'PARTIAL_SUCCESS') {
    console.warn('Deposit partially completed. Check yield balance.')
  } else {
    console.error('Deposit failed on-chain.')
  }
  
  return result
}
```

Use `withdraw` the same way with identical parameter shapes.

### Example (custom confirmation logic)

```typescript theme={null}
const result = await portal.yield.yieldxyz.deposit(
  {
    chain: 'eip155:11155111',
    token: 'ETH',
    amount: '0.0000001',
    address,
  },
  {
    // Optional: provide your own post-broadcast wait for this call only.
    // MUST return true for success; false for on-chain failure (e.g. revert).
    // Returning false stops execution immediately with status: 'FAILED'.
    waitForConfirmation: async (txHash, network) => {
      return portal.waitForConfirmation(txHash, network)
    },
  },
)
```

### Example (custom signer)

```typescript theme={null}
const result = await portal.yield.yieldxyz.deposit(
  {
    chain: 'eip155:11155111',
    token: 'ETH',
    amount: '0.0000001',
    address,
  },
  {
    // Optional: override the MPC signer for this call only.
    // Receives the unsigned transaction payload and network string; return the tx hash.
    signAndSendTransaction: async (transaction, network) => {
      const txHash = await myCustomSigner.signAndSend(transaction, network)
      return txHash
    },
  },
)
```

## Get Validators

Use `getValidators()` to fetch the validator addresses for a specific `yieldId`.\
These addresses are used for token approval flows.

```typescript theme={null}
getValidators(yieldId: string): Promise<YieldXyzValidator[]>
```

### React Native Example

```typescript theme={null}
import { Portal } from '@portal-hq/core'

async function getValidators(portal: Portal) {
  try {
    const yieldId = 'monad-testnet-mon-native-staking'
    const validators = await portal.yield.getValidators(yieldId)

    console.log(`Validators for ${yieldId}:`, validators)
  } catch (error) {
    console.error('getValidators failed:', error)
  }
}
```

The SDK throws an error if the response does not contain a valid validators array.

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

```typescript theme={null}
import type { YieldXyzManageYieldRequest, YieldXyzActionType } from '@portal-hq/core'

const addresses = await portal.addresses
const userAddress = addresses?.eip155

if (!userAddress) {
  throw new Error('No EVM address found')
}

const balanceResponse = await portal.yield.yieldxyz.getBalances({
  queries: [{ address: userAddress, network: 'eip155:11155111' }],
})

const pendingAction = balanceResponse.data?.rawResponse?.items?.[0]?.balances?.[0]?.pendingActions?.[0]

if (pendingAction) {
  const manageRequest: YieldXyzManageYieldRequest = {
    yieldId: 'ethereum-sepolia-link-aave-v3-lending',
    address: userAddress,
    action: pendingAction.type as YieldXyzActionType,
    passthrough: pendingAction.passthrough,
    // Optional: arguments (amount, etc.)
  }

  const manageResponse = await portal.yield.yieldxyz.manage(manageRequest)
  const transactions = manageResponse.data?.rawResponse?.transactions

  await processTransactions(transactions)
}
```

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

```typescript theme={null}
import type { YieldXyzGetHistoricalActionsRequest } from '@portal-hq/core'

const addresses = await portal.addresses
const userAddress = addresses?.eip155

if (!userAddress) {
  throw new Error('No EVM address found')
}

const request: YieldXyzGetHistoricalActionsRequest = {
  address: userAddress,
  // Optional: offset, limit, status, intent, type, yieldId
}

const response = await portal.yield.yieldxyz.getHistoricalActions(request)
const pastActions = response.data?.rawResponse?.items
```

## Transaction processing (low-level `enter` / `exit` / `manage`)

If you use **`deposit()` or `withdraw()`**, skip this section—the SDK already sequences transactions, waits between steps, and calls Yield.xyz tracking.

For **manual** flows that return raw transactions from `enter`, `exit`, or `manage`: process steps in order, and **wait for inclusion before sending the next transaction** (multi-step actions depend on prior txs mining; the high-level methods use `portal.waitForConfirmation` or your override between steps).

Use `portal.waitForConfirmation(txHash, network)` instead of reimplementing receipt polling. After the wait step, call `portal.yield.yieldxyz.track(transactionId, txHash)`.

<Tip>
  `deposit()` / `withdraw()` strip stale planning **`nonce`** from JSON EVM `unsignedTransaction` values before signing. If you implement manual signing, refresh or omit `nonce` the same way the signer expects, or you may see nonce collisions across steps.
</Tip>

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>

```typescript theme={null}
import type { YieldXyzActionTransaction } from '@portal-hq/core'

async function processTransactions(
  transactions: YieldXyzActionTransaction[] | undefined
): Promise<void> {
  if (!transactions?.length) return

  const sorted = [...transactions].sort((a, b) => a.stepIndex - b.stepIndex)

  for (const tx of sorted) {
    if (tx.unsignedTransaction && tx.status === 'CREATED') {
      const success = await signAndSubmitAndConfirm(tx)
      // Stop processing if transaction failed (returns false when receipt.status === '0x0')
      if (!success) break
    }
  }
}

async function signAndSubmitAndConfirm(
  transaction: YieldXyzActionTransaction
): Promise<boolean> {
  const txParams =
    typeof transaction.unsignedTransaction === 'string'
      ? JSON.parse(transaction.unsignedTransaction)
      : transaction.unsignedTransaction

  const sendResult = await portal.request(
    'eth_sendTransaction',
    [
      {
        from: txParams.from || '',
        to: txParams.to || '',
        value: txParams.value || '0x0',
        data: txParams.data || '0x',
      },
    ],
    transaction.network
  )

  const txHash = sendResult.result as string
  if (!txHash) return false

  await portal.yield.yieldxyz.track(transaction.id, txHash)

  return await waitForReceipt(txHash, transaction.network)
}

async function waitForReceipt(
  txHash: string,
  chainId: string,
  maxAttempts: number = 30,
  delayMs: number = 2000
): Promise<boolean> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await new Promise((resolve) => setTimeout(resolve, delayMs))

    const receiptResponse = await portal.request(
      'eth_getTransactionReceipt',
      [txHash],
      chainId
    )

    const receipt = receiptResponse.result as Record<string, any> | null
    // Receipt status 0x1 = success
    if (receipt?.status === '0x1') return true
    // Receipt status 0x0 = reverted (on-chain failure)
    if (receipt?.status === '0x0') return false
  }

  // Timeout reached without confirmation
  return false
}
```

### Getting Transaction Details

You can retrieve details about a specific transaction using the `getTransaction` method:

```typescript theme={null}
const transactionId = 'transaction-id-from-enter-or-exit-response'

const txResponse = await portal.yield.yieldxyz.getTransaction(transactionId)
const transaction = txResponse.data?.rawResponse
```

## Best Practices

1. **Always check the `status` field** in deposit/withdraw results to determine operation outcome (`SUCCESS`, `PARTIAL_SUCCESS`, or `FAILED`)
2. **Always check yield availability** before attempting to enter positions
3. **Prefer `deposit()` / `withdraw()`** when you want Portal to handle signing, confirmation between steps, and tracking—avoid copying receipt-poll loops from older examples
4. **Process low-level transactions sequentially** when using `enter` / `exit` / `manage` directly; later steps depend on earlier transactions being mined
5. **Handle network errors gracefully** and provide user feedback
6. **Monitor transaction status** and provide progress updates to users (`onProgress` on high-level methods)
7. **Validate user balances** before initiating yield operations
8. **Check for pending actions** in balances before calling the manage method
9. **Test on testnets first** (e.g., Sepolia) before moving to mainnet

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