> ## 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 Web 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}
try {
  const discoverRequest: YieldXyzGetYieldsRequest = {
    limit: 10,
    offset: 0,
    network: 'eip155:11155111', // Sepolia network
    // ... other parameters
  };

  const result = await portal.yield.yieldXyz.discover(discoverRequest);

  // manage Yield.xyz result
  console.log(result);
} catch (err) {
  // Handle Yield.xyz discover error
  console.error(err);
}
```

<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}
try {
  const userAddress = await portal.getEip155Address(); // or getSolanaAddress() if using a Solana yield

  const enterRequest: YieldXyzEnterRequest = {
    yieldId: 'ethereum-sepolia-link-aave-v3-lending',
    address: userAddress,
    arguments: {
      amount: '1', // 1 LINK Token
    },
  };

  const enterResult = portal.yield.yieldXyz.enter(enterRequest);

  const transactions = enterResult.data.rawResponse.transactions;

  // Process transactions, this is described in the "Transaction Processing" section below
  processTransactions(transactions);
} catch (err) {
  // Handle Yield.xyz enter error
  console.error(err);
}
```

## 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}
try {
  const address = await portal.getEip155Address(); // or getSolanaAddress() if using a Solana yield

  const getBalancesRequest: YieldXyzGetBalancesRequest = {
    queries: [
      {
        address,
        network: 'eip155:11155111',
      },
    ],
  };
  const result = await portal.yield.yieldXyz.getBalances(getBalancesRequest);

  // Process and display yield positions information
  console.log(result);
} catch (err) {
  // Handle Yield.xyz Get balances error
  console.error(err);
}
```

<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 getBalancesRequest: YieldXyzGetBalancesRequest = {
    queries: [
      {
        address,
        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}
try {
  const address = await portal.getEip155Address(); // or getSolanaAddress() if using a Solana yield

  const exitRequest: YieldXyzExitRequest = {
    yieldId: exitYieldId,
    address,
    arguments: {
      amount: exitYieldAmount || '0.001',
    },
  };
  const result = await portal.yield.yieldXyz.exit(exitRequest);

  const transactions = result.data.rawResponse.transactions;

  // Process transactions, this is described in the "Transaction Processing" section below
  processTransactions(transactions);
} catch (err) {
  // Handle Yield.xyz Exit error
  console.error(err);
}
```

## 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}
try {
  const address = await portal.getEip155Address(); // or getSolanaAddress() if using a Solana yield

  const manageRequest: YieldXyzManageYieldRequest = {
    yieldId,
    address,
    action: yieldAction as any,
    passthrough: yieldPassthrough,
  };

  const result = await portal.yield.yieldXyz.manage(manageRequest);

  const transactions = result.data.rawResponse.transactions;

  // Process transactions, this is described in the "Transaction Processing" section below
  processTransactions(transactions);
} catch (err) {
  // Handle Yield.xyz Manage error
  console.error(err);
}
```

## 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}
try {
  const address = await portal.getEip155Address(); // or getSolanaAddress() if using a Solana yield

  const historicalActionsRequest: YieldXyzGetHistoricalActionsRequest = {
    address,
    limit: 10,
    offset: 0,
  };

  const result = await portal.yield.yieldXyz.getHistoricalActions(historicalActionsRequest);

  // process historical actions result
} catch (err) {
  // Handle Yield.xyz Historical Actions error
  console.error(err);
}
```

## 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](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}
async function processTransactions(transactions?: YieldXyzActionTransaction[]): Promise<void> {
  const sorted = transactions?.sort((a, b) => a.stepIndex - b.stepIndex) ?? [];
  for (const tx of sorted) {
    if (tx.unsignedTransaction != null && tx.status === 'CREATED') {
      const ok = await signAndSubmitAndConfirm(tx);
      if (!ok) break;
    }
  }
}

async function signAndSubmitAndConfirm(transaction: YieldXyzActionTransaction): Promise<boolean> {
  const unsignedTxJson = transaction.unsignedTransaction;
  if (typeof unsignedTxJson !== 'string') return false;

  const txParams = JSON.parse(unsignedTxJson) as Record<string, any>;

  const ethTransaction = {
    from: (txParams.from as string) ?? '',
    to: (txParams.to as string) ?? '',
    value: (txParams.value as string) ?? '0x0',
    data: (txParams.data as string) ?? '0x',
    gas: null, // Let Portal handle gas estimation
    gasPrice: null,
    maxFeePerGas: null,
    maxPriorityFeePerGas: null,
  };

  const txHash = await portal.request({
    chainId: transaction.network,
    method: 'eth_sendTransaction',
    params: [ethTransaction],
  });

  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,
  desiredConfirmations: number = 2,
  pollIntervalMs: number = 2000,
  timeoutSeconds: number = 600
): Promise<boolean> {
  const startTime = Date.now();
  let minedBlock = null; // will be set once mined
  let confirmations = 0;

  while (true) {
    if (timeoutSeconds && (Date.now() - startTime) / 1000 > timeoutSeconds) {
      throw new Error(`Timeout waiting for ${desiredConfirmations} confirmations`);
    }

    try {
      if (!minedBlock) {
        const receiptRes = await axios.post(
          getRpcUrl(chainId), // Create a method to obtain the RPC URL for the network of the yield
          {
            jsonrpc: '2.0',
            id: 1,
            method: getRpcReceiptMethod(chainId), // i.e. 'eth_getTransactionReceipt' for non-abstracted eth accounts
            params: [txHash],
          }
        );

        const receipt = receiptRes.data.result; // If the account is abstracted, it should be receiptRes?.data?.result?.receipt.

        if (!receipt) {
          await new Promise(r => setTimeout(r, pollIntervalMs));
          continue;
        }

        // i.e. if in eth-based, it should check receipt.status === '0x0'.
        if (checkIfStatusFailed(receipt, chainId)) {
          throw new Error(`Transaction ${txHash} reverted`);
        }

        minedBlock = parseInt(receipt.blockNumber, 16);
        // fall through to Phase 2 on the same loop iteration if possible
      }

      // Phase 2: already mined → now we only need latest block number
      const latestBlockRes = await axios.post(
        getRpcUrl(chainId), // Create a method to obtain the RPC URL for the network of the yield
        {
          jsonrpc: '2.0',
          id: 2,
          method: getRpcLatestBlockMethod(chainId),
          params: [],
        }
      );

      /*
       * If on eth, then the getConfirmations function would do this:
       *   const latestBlock = parseInt(latestBlockRes.data.result, 16)
       *   return latestBlock - minedBlock + 1
       */
      confirmations = getConfirmations(latestBlockRes, minedBlock);

      if (confirmations >= desiredConfirmations) {
        return true;
      }
    } catch (err) {
      console.error('Polling error:', err.message);
    }

    await new Promise(r => setTimeout(r, pollIntervalMs));
  }
}
```

## 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](./simulate-a-transaction)
* Check out [Portal API methods](./portal-api-methods)
