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

# Lend and borrow with Yield.xyz

> Learn how to supply collateral, borrow, repay and withdraw on lending protocols using the Enclave MPC API and the Yield.xyz integration.

Portal's Enclave MPC API lets your users lend and borrow on Aave V3, Morpho Blue, SparkLend and Lista through the Yield.xyz integration. This guide covers discovering lending markets, building supply / borrow / repay / withdraw actions, signing and submitting the resulting transactions, and tracking positions.

## Overview

The lend & borrow functionality allows you to:

* **Discover** the lending integrations (protocols) and the markets your project has enabled, with live rates, LTVs, liquidation thresholds and liquidity
* **Supply** tokens as collateral and **borrow** against them
* **Repay** debt and **withdraw** collateral, or toggle a supplied asset as collateral
* **Track** positions (supply and debt balances, health factor, LTV and available borrow) and liquidation history

To summarize: a user supplies collateral, can borrow up to the market's `maxLtv` of its value, pays a floating borrow rate with no term, and is liquidated (partially, with a penalty) only if the position's `healthFactor` drops below `1`. The [integration page](/integrations/Yield/yield-xyz-borrow#how-lending-and-borrowing-works) explains the mechanics and why you might offer it.

All endpoints live under `https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow` and return `{ "data": ... }`. Chain ids are [CAIP-2](/resources/chain-id-formatting) everywhere (`eip155:8453` for Base).

## Prerequisites

Before using lend & borrow operations, ensure you have:

* A properly initialized Portal client (see [Create a client](./create-a-client))
* An active wallet with the required token(s) **and gas** on the target network (see [Create a wallet](./create-a-wallet))
* Yield.xyz integration enabled in your Portal Dashboard (see [Yield.xyz Borrow](/integrations/Yield/yield-xyz-borrow))

<Warning>
  Lending integrations are live on **mainnets only**: there are no Sepolia, Base Sepolia or Amoy markets, so `POST /api/v3/clients/me/fund` cannot be used here. Test with small amounts on an inexpensive chain such as Base. The examples below use Aave V3 USDC on Base.
</Warning>

## Discovering Integrations

Use the `GET /api/v3/clients/me/integrations/yield-xyz-borrow/integrations` endpoint to list the lending protocols, the networks each supports and the actions each exposes (including the argument schema per action).

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/list-integrations).

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/integrations' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

Integration ids look like `aave-borrow`, `morpho-blue-borrow`, `sparklend-borrow` and `lista-borrow`; use the ids the endpoint returns rather than a fixed list. Use `GET .../integrations/{integrationId}` for one integration.

## Discovering Markets

Use the `GET /api/v3/clients/me/integrations/yield-xyz-borrow/markets` endpoint to list lending markets. Pass `scope=all`: the default (`scope=enabled`) returns only markets enabled for your Yield.xyz project, and the Yield.xyz dashboard does not yet support enabling lending markets, so the default currently returns an empty list.

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/list-lending-markets).

```bash theme={null}
# Aave V3 markets on Base
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/markets?scope=all&integrationId=aave-borrow&network=eip155:8453' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'

# Browse every market Yield.xyz supports
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/markets?scope=all&limit=50' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

Each market carries the fields you need to render a lending UI: `loanToken`, `collateralTokens[]` (with `maxLtv`, `liquidationThreshold`, `liquidationPenalty`, `supplyRate`), `borrowRate`, `availableLiquidity`, `utilizationRate`, `isBorrowEnabled`, `minLoan` and the fee settings. Treat market ids as opaque values returned by `GET /markets` (for example `aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` for USDC on Base); they embed the Yield.xyz network slug, not the CAIP-2 chain id, so do not build them yourself.

<Note>
  An empty list without `scope=all` is expected until Yield.xyz adds lending markets to its dashboard. `scope` only affects what `GET /markets` lists; actions can be built for any market id. Many markets hold little liquidity, so filter on `availableLiquidity` before showing a market to users.
</Note>

## Supplying Collateral

To supply, use the `POST /api/v3/clients/me/integrations/yield-xyz-borrow/actions` endpoint to create the action and get the transaction(s) to sign. You'll then sign each with the Enclave MPC API and report the hash back.

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/execute-a-borrow-action).

### Step 1: Create the Supply Action

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "integrationId": "aave-borrow",
  "action": "supply",
  "address": "0xYourAddress",
  "args": {
    "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "amount": "5"
  }
}'
```

<Note>
  Pool-based protocols (Aave) require `tokenAddress` on `supply` and `withdraw`: the market's `loanToken.address` to supply the loan asset, or one of `collateralTokens[].token.address` to supply a different collateral. Isolated-market protocols (Morpho Blue) infer it from `marketId`, and on them `supply` only posts collateral: the loan token is borrowed or repaid, never supplied, so passing it as `tokenAddress` returns `400` (`tokenAddress is not a valid collateral token for this market`). `GET .../integrations/{integrationId}` returns the exact JSON schema for every action's `args`.
</Note>

```json theme={null}
// Example response (trimmed)
{
  "data": {
    "id": "act_...",
    "integrationId": "aave-borrow",
    "action": "supply",
    "address": "0xYourAddress",
    "status": "CREATED",
    "hasNextStep": false,
    "currentStep": 1,
    "totalSteps": 1,
    "transactions": [
      {
        "id": "73729c4d-...",
        "network": "eip155:8453",
        "type": "APPROVAL",
        "status": "CREATED",
        "address": "0xYourAddress",
        "signingFormat": "EVM_TRANSACTION",
        "signablePayload": "{\"from\":\"0xYourAddress\",\"gasLimit\":\"56240\",\"to\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"data\":\"0x095ea7b3...\"}"
      },
      {
        "id": "5925b16e-...",
        "network": "eip155:8453",
        "type": "SUPPLY",
        "status": "CREATED",
        "address": "0xYourAddress",
        "signingFormat": "EVM_TRANSACTION",
        "signablePayload": "{\"from\":\"0xYourAddress\",\"gasLimit\":\"420000\",\"to\":\"0xA238Dd80C259a72e81d7e4664a9801593F98d1c5\",\"data\":\"0x617ba037...\"}"
      }
    ],
    "metadata": {
      "currentHealthFactor": null,
      "predictedHealthFactor": null,
      "currentLtv": "0",
      "predictedLtv": "0",
      "predictedTotalSupplyUsd": "5.00",
      "predictedTotalDebtUsd": "0"
    }
  }
}
```

<Note>
  Take note of `data.id` (the action id), each transaction's `id`, and `hasNextStep`. A step can contain several transactions (here the ERC-20 approval and the supply), which you sign and submit **in order**, waiting for each to confirm. Some actions add further steps (`hasNextStep: true`), fetched with the step endpoint below. Yield.xyz builds the transactions even if the wallet does not hold the tokens yet; the balance check happens on-chain.
</Note>

<Note>
  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 (the wrapped token's `deposit()`, with the amount as `value`) before the approval. Sign it like any other transaction, keeping its `value`. Withdrawing returns the **wrapped** token, not the native one; if your users expect the native token back, call `withdraw(uint256)` on the wrapped token contract after the withdraw confirms.
</Note>

### Step 2: Sign and Broadcast with the Enclave MPC API

`signablePayload` for `EVM_TRANSACTION` is a JSON string of the unsigned transaction. Parse it and pass the fields to the Enclave MPC API `eth_sendTransaction` method, which signs **and** broadcasts, returning the transaction hash.

```bash theme={null}
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "share": "[share]",
  "method": "eth_sendTransaction",
  "params": { "from": "0xYourAddress", "to": "0x8335...", "data": "0x095ea7b3...", "value": "0x0" },
  "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/8453",
  "chainId": "eip155:8453"
}'
```

<Tip>
  Remove the gas parameters from the parsed payload if you want Portal to estimate gas for you. If you prefer to broadcast yourself, use `eth_signTransaction` instead and send the signed raw transaction as `signedPayload` in the next step, and Yield.xyz will broadcast it.
</Tip>

### Step 3: Submit the Transaction Hash

Report the hash to Yield.xyz with `POST /api/v3/clients/me/integrations/yield-xyz-borrow/transactions/{transactionId}/submit` so it can track confirmation and unlock the next step.

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/submit-signed-transaction).

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/transactions/[transactionId]/submit \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "transactionHash": "0x..."
}'
```

The response includes the transaction `status` and an explorer `link`.

### Step 4: Fetch the Next Step

When the action has `hasNextStep: true`, wait for the submitted transaction to confirm, then call `POST /api/v3/clients/me/integrations/yield-xyz-borrow/actions/{id}/step` to get the next transaction(s) and repeat steps 2–3. You can check progress at any time with `GET .../actions/{id}`.

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/get-next-step-transactions).

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions/[actionId]/step \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

The action is complete when `status` is `SUCCESS` and `hasNextStep` is `false`.

## Borrowing

Borrowing uses the same action 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. You can also combine both in one flow with `supplyAndBorrow`.

```bash theme={null}
# Borrow 2 USDC against your collateral
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "integrationId": "aave-borrow",
  "action": "borrow",
  "address": "0xYourAddress",
  "args": {
    "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "amount": "2"
  }
}'

# Or supply collateral and borrow in one action
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "integrationId": "aave-borrow",
  "action": "supplyAndBorrow",
  "address": "0xYourAddress",
  "args": {
    "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "collateralTokenAddress": "0x4200000000000000000000000000000000000006",
    "collateralAmount": "0.01",
    "borrowAmount": "10"
  }
}'
```

<Note>
  Every action response carries `metadata.predictedHealthFactor` and `metadata.predictedLtv`. Show these to the user before they sign: a health factor at or below `1` means the position can be liquidated. Either 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. The exact `args` each action accepts (`amount`, `amountRaw`, `repayAll`, `tokenAddress`, `targetLtv` and so on) are described per integration in `GET .../integrations/{integrationId}`.
</Note>

## Checking Positions

Retrieve a user's position on an integration + network with the `GET /api/v3/clients/me/integrations/yield-xyz-borrow/positions` endpoint.

For complete API documentation, see the [API reference](/api-reference/yieldxyz-borrow/get-positions).

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/positions?integrationId=aave-borrow&network=eip155:8453&address=0xYourAddress' \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]'
```

```json theme={null}
// Example response (trimmed)
{
  "data": {
    "address": "0xYourAddress",
    "integrationId": "aave-borrow",
    "network": "eip155:8453",
    "totalSuppliedUsd": "5.00",
    "totalCollateralUsd": "5.00",
    "totalBorrowedUsd": "2.00",
    "netWorthUsd": "3.00",
    "healthFactor": "1.93",
    "currentLtv": "0.40",
    "availableToBorrowUsd": "1.85",
    "netApy": "0.021",
    "supplyBalances": [
      {
        "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "tokenSymbol": "USDC",
        "balance": "5.000123",
        "balanceUsd": "5.00",
        "apy": "0.038",
        "isCollateral": true,
        "pendingActions": [
          { "type": "withdraw", "label": "Withdraw", "args": { "marketId": "aave-borrow-base-0x8335..." } }
        ]
      }
    ],
    "debtBalances": [
      {
        "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "tokenSymbol": "USDC",
        "balance": "2.000041",
        "balanceUsd": "2.00",
        "apy": "0.051",
        "pendingActions": [
          { "type": "repay", "label": "Repay", "args": { "marketId": "aave-borrow-base-0x8335..." } }
        ]
      }
    ]
  }
}
```

Each balance lists `pendingActions`, the actions currently available for that balance, with pre-filled `args` you can pass straight to `POST .../actions`. As with action metadata, `healthFactor` and `availableToBorrowUsd` can be `null` for some markets; treat `null` as unknown.

## 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"`. Both follow the same sign → submit → step flow.

```bash theme={null}
# Repay everything
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "integrationId": "aave-borrow",
  "action": "repay",
  "address": "0xYourAddress",
  "args": {
    "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "repayAll": true
  }
}'

# Withdraw collateral
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/actions \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "integrationId": "aave-borrow",
  "action": "withdraw",
  "address": "0xYourAddress",
  "args": {
    "marketId": "aave-borrow-base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "amount": "5"
  }
}'
```

## Action History and Liquidations

List a user's past actions with `GET /api/v3/clients/me/integrations/yield-xyz-borrow/actions?address=0xYourAddress` (filter by `integrationId`, `action` or `status`), and their liquidation history with `GET .../positions/liquidations?integrationId=morpho-blue-borrow&network=eip155:1&address=0xYourAddress` (Morpho Blue integrations only).

For complete API documentation, see the [actions](/api-reference/yieldxyz-borrow/list-actions) and [liquidations](/api-reference/yieldxyz-borrow/get-liquidation-history) references.

## Transaction Processing

Lend & borrow actions can require multiple transactions across multiple steps. Process them sequentially: sign each with the Enclave MPC API, submit its hash, wait for on-chain confirmation (e.g. `eth_getTransactionReceipt`), then request the next step.

```bash theme={null}
# 1. Create the action and read data.transactions (and data.hasNextStep)
# 2. For each transaction that is not yet CONFIRMED/SKIPPED/FAILED (fresh ones are CREATED), in array order:
#    a. Parse signablePayload (JSON string) into { from, to, data, value }
#    b. Sign + broadcast with the Enclave MPC API
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{
  "share": "[share]",
  "method": "eth_sendTransaction",
  "params": { "from": "0xYourAddress", "to": "0x...", "data": "0x...", "value": "0x0" },
  "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/8453",
  "chainId": "eip155:8453"
}'

#    c. Submit the hash
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/yield-xyz-borrow/transactions/[transactionId]/submit \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{ "transactionHash": "0x..." }'

#    d. Wait for confirmation
curl --request POST \
  --url https://api.portalhq.io/rpc/v1/eip155/8453 \
  --header 'Authorization: Bearer [clientApiKey|clientSessionToken]' \
  --header 'Content-Type: application/json' \
  --data '{ "id": 1, "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": ["0xTransactionHash"] }'

# 3. If hasNextStep is true, POST .../actions/[actionId]/step and repeat from 2
```

<Tip>
  For account abstraction enabled Portal clients, `eth_sendTransaction` returns a *user operation hash*. Resolve it with `GET /api/v3/clients/me/chains/{chain}/transactions/{userOpHash}` and submit `evmUserOperation.receipt.hash` (the on-chain **transaction hash**) to Yield.xyz, never the user operation hash. Use the smart-account address (the client's `eip155` address) as `address` in every Borrow call.
</Tip>

## Handling Errors

Yield.xyz Borrow errors are returned as Portal errors:

* `400`: invalid request (the message names the field), an unsupported chain id, a request Yield.xyz rejected (its message is passed through), or a Yield.xyz API key that is missing or was rejected
* `404`: unknown market, integration, action or transaction id
* `429`: Yield.xyz rate limit hit; back off and retry
* `503`: Yield.xyz is unreachable or failed upstream
* `500` with `id: INTEGRATION_RESPONSE_SCHEMA_DRIFT`: Yield.xyz changed a response shape and Portal did not forward it. Treat it as a temporary outage of that feature; see [Error codes](/resources/error-codes)

Response enumerations (action and transaction statuses, action types) may gain values over time; treat values you do not recognize as opaque rather than failing.

## Best Practices

1. **Show the predicted health factor and LTV** from `metadata` before the user signs a borrow or withdraw
2. **Process transactions sequentially** and wait for confirmation before requesting the next step
3. **Use `repayAll`** when closing a position so accrued interest does not leave dust
4. **Pass `scope=all` and filter** `GET /markets` to the markets your UI offers, for example by `availableLiquidity`
5. **Handle `INTEGRATION_RESPONSE_SCHEMA_DRIFT`** as a feature outage rather than a user error
6. **Keep gas on the wallet**: every step is an on-chain transaction

## Supported Networks

Lending integrations run on mainnets only; the exact list per protocol is returned by `GET /api/v3/clients/me/integrations/yield-xyz-borrow/integrations` (`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.

## Next Steps

* Set up the integration: [Yield.xyz Borrow](/integrations/Yield/yield-xyz-borrow)
* Learn about [signing Ethereum transactions](./sign-ethereum-transactions)
* Check out the [Yield.xyz Borrow API reference](/api-reference/yieldxyz-borrow/get-positions)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.