Skip to main content
Portal’s React Native SDK lets your users lend and borrow on Aave V3, Morpho Blue, SparkLend and Lista through the portal.borrow.yieldxyz API. This guide covers discovering lending markets, building supply, borrow, repay and withdraw actions, and signing, submitting and tracking them with the Portal wallet.

Overview

With portal.borrow.yieldxyz you can:
  • Discover the lending integrations (protocols) and their markets, with live rates, LTVs, liquidation thresholds and liquidity
  • Supply collateral, borrow against it, repay debt and withdraw collateral
  • Execute an action in one call: the SDK signs each transaction with the Portal wallet, submits it to Yield.xyz, waits for Yield.xyz to confirm it and fetches the next step until the action succeeds
  • Track positions (supply and debt balances, health factor, LTV and available borrow), past actions and liquidations
The Yield.xyz Borrow integration page explains how lending and borrowing work: collateral, maxLtv, the health factor and liquidations. Every method except executeAction and signAndSubmitAction resolves with the Portal API response, wrapped as { data }; those two resolve with the execution result itself. Chain ids are CAIP-2 everywhere (eip155:8453 for Base).

Prerequisites

Before using lend and borrow operations, ensure you have:
  • A properly initialized Portal client (see Getting started)
  • An active wallet with the required token(s) and gas on the target network (see Create a wallet)
  • Yield.xyz integration enabled in your Portal Dashboard (see Yield.xyz Borrow)
  • A gatewayConfig entry for every chain you sign on. The default gatewayConfig covers the built-in chains, including Ethereum, Base, Arbitrum One, Polygon and Monad. Markets on other chains, such as Optimism, Avalanche or BNB Chain, need an entry you add yourself. A custom gatewayConfig is used exactly as supplied, so start from buildDefaultGatewayConfig (see Custom RPC configuration).
Lending integrations are live on mainnets only: there are no Sepolia, Base Sepolia or Amoy markets. Every signed action moves real funds, so test with small amounts on an inexpensive chain such as Base. The examples below use Aave V3 USDC on Base.
The examples in this guide use a portal instance and the wallet’s EVM address:
For account abstraction enabled clients, this is the smart-account address. Pass it as address in every Borrow call.
On React Native, a request that fails at the network level (with no HTTP status) can resolve with an empty response instead of rejecting, leaving data undefined. Check data before using the result of a read or createAction. signAndSubmitAction handles this itself; executeAction throws a plain Error (see Handling errors).

Discovering integrations

getIntegrations lists the lending protocols, the networks each supports and the actions each exposes. getIntegration returns one integration, including the JSON schema of every action’s args.
Integration ids look like aave-borrow, morpho-blue-borrow, sparklend-borrow and lista-borrow; use the ids getIntegrations returns rather than a fixed list. networks holds CAIP-2 ids for the chains Portal supports and Yield.xyz network names for the others; only the CAIP-2 entries can be passed as network.

Discovering markets

getMarkets lists lending markets and getMarket returns one by id. Pass scope: 'all': when scope is omitted, Yield.xyz lists only the markets enabled for your project, and the Yield.xyz dashboard does not yet support enabling lending markets, so that list is empty.
getMarkets resolves with { data: { total, offset, limit, items } }, where each item is a YieldXyzBorrowMarket with loanToken, collateralTokens[] (maxLtv, liquidationThreshold, liquidationPenalty, supplyRate), borrowRate, availableLiquidity, utilizationRate, isBorrowEnabled, minLoan and the fee settings. See the API reference for every field. Treat market ids as opaque values returned by getMarkets: they embed the Yield.xyz network slug, not the CAIP-2 chain id, so do not build them yourself. Many markets hold little liquidity, so filter on availableLiquidity before showing a market to users.

Supplying collateral

Every lending action takes two calls:
  1. createAction builds the action and returns its unsigned transactions and the predicted impact on the position. Nothing is signed.
  2. signAndSubmitAction signs each transaction with the Portal wallet, submits it to Yield.xyz and tracks the action until it succeeds (see Signing and execution).
createAction takes: The most common args: getIntegration returns the exact schema of each action’s args for an integration. createAction resolves with { data: action }. The action carries its id, its status, the transactions of the current step in signing order, hasNextStep, currentStep and totalSteps, and metadata with the predicted predictedHealthFactor, predictedLtv, predictedTotalSupplyUsd and predictedTotalDebtUsd.
Isolated-market protocols (Morpho Blue) infer the token from marketId, and on them supply only posts collateral: the loan token is borrowed or repaid, never supplied, so passing it as tokenAddress is rejected. When the collateral is a wrapped native token (for example WMON on Monad) and the wallet holds the native token instead, Yield.xyz adds a WRAP transaction before the approval; the SDK signs it like any other transaction. Yield.xyz builds the transactions even when the wallet does not hold the tokens yet: the balance check happens on-chain.

Borrowing

Borrowing uses the same flow with action: 'borrow'. Supplied assets on Aave are collateral by default; on other integrations, or if you disabled it, run an enableCollateral action first. Integrations that offer supplyAndBorrow, which supplies collateral and borrows in one action, list it in getIntegration with the args it accepts.
Show metadata.predictedHealthFactor and metadata.predictedLtv to the user before they sign: a health factor at or below 1 means the position can be liquidated. predictedHealthFactor can be null when Yield.xyz does not compute it for a market (Morpho Blue on Monad returns null even with open debt); treat null as unknown, never as safe.

Checking positions

getPositions returns the wallet’s position on one integration and network.
The position holds the totals (totalSuppliedUsd, totalCollateralUsd, totalBorrowedUsd, netWorthUsd), healthFactor, currentLtv, availableToBorrowUsd, netApy, and the supplyBalances and debtBalances. For isolated-market protocols such as Morpho Blue, the account-level healthFactor and availableToBorrowUsd are always null: read each supply balance’s positionState instead, which carries that market’s healthFactor, availableToBorrowUsd, currentLtv and liquidationThreshold. A healthFactor is also null when there is no debt; treat any other null as unknown. Each balance lists pendingActions, the actions available for it now, with pre-filled args you can pass to createAction:

Repaying and withdrawing

Repay debt with action: 'repay' (pass repayAll: true to clear the position, which avoids dust from accrued interest), then withdraw collateral with action: 'withdraw'.
Withdrawing a wrapped native collateral (for example WMON) returns the wrapped token. If your users expect the native token back, unwrap it after the withdraw confirms.

Signing and execution

How an action is executed

signAndSubmitAction(action, options?) executes an action step by step:
  1. It reads the action back from Yield.xyz. The action’s current state decides what still needs signing; the action you pass gives the signing order.
  2. For each transaction of the current step, in order, it signs and broadcasts the transactions awaiting a signature (CREATED or WAITING_FOR_SIGNATURE) with eth_sendTransaction on the transaction’s own chain, using the payload Yield.xyz lists at signing time. It submits the on-chain transaction hash to Yield.xyz, then polls until Yield.xyz reports the transaction CONFIRMED or SKIPPED before signing the next one. A transaction in any other status is waited on until it can be signed or is confirmed. The SDK never re-signs a transaction Yield.xyz has already recorded, but a transaction broadcast without its hash being returned is not recorded yet, so a blind retry can broadcast it again (see Resuming a failed execution).
  3. When the step is confirmed and hasNextStep is true, it fetches the next step with stepAction and repeats.
  4. It resolves when the action reaches SUCCESS. Any failure throws a YieldXyzBorrowExecutionError.
Before signing a transaction, the SDK checks that its signingFormat is EVM_TRANSACTION (the only format Borrow signs), that its network is an EIP-155 chain with a gatewayConfig entry, and that the action, the transaction and the payload’s from were all built for the Portal wallet’s EVM address (portal.addresses). Only from, to, data, gasLimit (sent as gas) and value are forwarded: the signer sets the nonce and fees. Each transaction goes through Portal’s signing flow. Unless autoApprove is true, approve each request in your portal_signingRequested handler (see Add custom signature hooks). A rejected request stops the execution with SIGNATURE_REJECTED. Without a portal_signingRequested handler, the request throws before anything is signed, so the execution stops with SIGNING_FAILED. For account abstraction enabled clients, eth_sendTransaction returns a user operation hash. The SDK waits for the bundle transaction and submits its on-chain hash to Yield.xyz; progress events and results carry both hashes.

Executing in one call

executeAction(params, options?) calls createAction and then signAndSubmitAction. Use the two-call flow above when you want to show the predicted health factor before the user signs.
When createAction fails, executeAction throws the Portal API error as it is, or a plain Error when createAction resolved with an empty response: no action exists yet, so there is nothing to resume.

Options

Both methods take the same YieldXyzBorrowExecuteOptions:

Progress events

onProgress receives a YieldXyzBorrowExecutionProgress with the step, the actionId and the fields that apply to that step: index counts the transactions this call signed or submitted before this one, across steps.

Return value

Both methods resolve with a YieldXyzBorrowExecutionResult:

Using your own signer

Pass signAndSendTransaction to sign with your own logic, for example to add a confirmation step. It receives the eth_sendTransaction params (from, to, data, gas and value, as hex quantities) and the CAIP-2 chain id, and resolves with the hash, or with undefined when the request is declined. The SDK still checks that every transaction was built for the Portal wallet’s EVM address. A signer that signs through portal.request, like this one, still needs a gatewayConfig entry for the chain: without one, the request throws before anything is sent.

Resuming a failed execution

A failed execution throws a YieldXyzBorrowExecutionError that carries the actionId, the transactionId and action: the latest known state of the action, with its transactions in signing order. Persist it and resume with signAndSubmitAction(error.action, { broadcastHashes }). Prefer error.action (or the action createAction returned) over getAction, which may list the transactions in a different order. When the error carries a broadcastHash, that transaction was already broadcast and Yield.xyz may not have recorded it yet. Pass it in broadcastHashes, keyed by transaction id, so the SDK resolves and submits it instead of signing it again. The exception is USER_OPERATION_FAILED: that user operation was already processed and did not succeed, so drop its entry and let the retry sign a new transaction.
Not every failure should be retried as is:

Handling errors

Three kinds of error reach your app:
  • Portal API errors, thrown by the read methods and createAction, and carried as cause by execution errors. On React Native they are HttpErrors with the HTTP status, the stable error id, details and the API’s errorMessage. getYieldXyzBorrowApiError(error) returns the API error whether it was thrown directly or wrapped.
  • YieldXyzBorrowExecutionError, thrown by signAndSubmitAction and executeAction. Check it with YieldXyzBorrowExecutionError.is(error) and branch on reason.
  • A plain Error, thrown by executeAction when createAction resolves with an empty response (see the note at the top of this guide). It is neither of the above, and nothing was signed: create the action again.
The Portal API answers with 400 (invalid request, unsupported chain id, a request Yield.xyz rejected, or a missing or rejected Yield.xyz API key), 404, 429, 503, or 500 with id: INTEGRATION_RESPONSE_SCHEMA_DRIFT when Yield.xyz changed a response shape. See Errors on the integration page. While executing, the SDK retries rate-limited (429) submissions and step requests and tolerates up to five consecutive transient failures while reading the action back. After an ambiguous submission failure, it resubmits the transaction hash once, and only if the action shows Yield.xyz did not record it. Signing is never retried. YieldXyzBorrowExecutionErrorReason values: The error also carries broadcastHash (the hash the signer returned, set once the transaction was broadcast), transactionHash (the on-chain hash, once known), userOperationHash and cause.

History and liquidations

getActions lists the wallet’s actions, filtered by integrationId, action, status or statuses, and getAction reads one action. getLiquidations returns a position’s liquidation history (Morpho Blue integrations only).
Both list methods take offset and limit (at most 100) and resolve with a page: { data: { total, offset, limit, items } }.

Low-level APIs

signAndSubmitAction covers signing, submitting and stepping. If you drive the transactions yourself, use the low-level methods with the Enclave MPC API transaction flow as the reference:
Each transaction’s signablePayload is the JSON-encoded unsigned transaction. For account abstraction enabled clients, submit the on-chain transaction hash, never the user operation hash.

Best practices

  1. Show the predicted health factor and LTV from metadata before the user signs a borrow or withdraw: build with createAction, then execute with signAndSubmitAction
  2. Sign soon after building: the transactions carry the gas limit Yield.xyz estimated when the action was built, so create a new action if the user waits long before signing
  3. Persist error.action and broadcastHashes so a failed execution can be resumed without signing a transaction twice
  4. Use repayAll when closing a position so accrued interest does not leave dust
  5. Pass scope: 'all' and filter markets to the ones your UI offers, for example by availableLiquidity
  6. Handle INTEGRATION_RESPONSE_SCHEMA_DRIFT as a feature outage rather than a user error
  7. Keep gas on the wallet: every transaction is on-chain

Supported networks

Lending integrations run on mainnets only; the exact list per protocol is returned by getIntegrations (networks, as CAIP-2). Aave V3 covers the major EVM chains (Ethereum eip155:1, Base eip155:8453, Arbitrum eip155:42161, Optimism eip155:10, Polygon eip155:137, Avalanche eip155:43114, and more); Morpho Blue covers Ethereum, Base, Monad (eip155:143) and other EVM chains; SparkLend is Ethereum-only; Lista runs on BNB Chain (eip155:56) and Ethereum. Signing needs a gatewayConfig entry for the transaction’s chain. The default configuration covers the built-in chains, among them Ethereum, Base, Arbitrum One, Polygon and Monad; add any other chain, such as Optimism, Avalanche or BNB Chain, yourself.

Next Steps