BorrowKit
Borrowing API
The Borrowing API is a unified layer for interacting with on-chain lending/borrowing protocols. It provides:
- Normalized market data (rates, liquidity, collateral parameters)
- A consistent action model (supply, borrow, repay, withdraw, collateral toggles)
- Unified account state (positions, debt, health factor, borrow power)
- Transaction construction (unsigned payloads ready for wallet signing)
- A consistent action lifecycle that absorbs protocol quirks like ERC-20 approvals
Request and response shapes are identical across protocols and networks. Whether the underlying venue is Aave on Ethereum or Morpho on Base, your integration code does not change.
What the Borrowing API Does
Discoverability and capability introspection
- List supported integrations (protocol + product type)
- See which networks each integration supports
- See which actions are supported and the argument schema per action
Market discovery (normalized market DTOs)
- Browse markets across protocols and chains with a consistent
MarketDto - Compare rates, liquidity, utilization, and risk parameters using stable fields across venues
Portfolio and risk state (positions)
- Read supplied collateral, borrowed debt, and aggregated portfolio metrics:
healthFactorcurrentLtvavailableToBorrowUsdliquidationThreshold(per collateral)netApyand per-position APYs
Transaction building (intent → signable payloads)
- Convert an intent like "supply 10,000 USDC to market X" into the on-chain transactions required:
- ERC-20 approvals (when allowance is insufficient)
- The protocol call itself (supply, borrow, repay, withdraw, etc.)
- Returns unsigned, signable payloads for client-side signing
Action lifecycle
A consistent state machine across all integrations:
create action → sign + submit each tx in transactions[] → poll → SUCCESS / FAILED
What's Supported
Supported Protocol Integrations
| Provider | Integration ID | Name | Market Type | Networks |
|---|---|---|---|---|
| Morpho | morpho-blue-borrow | Morpho Blue Borrow | Isolated | Ethereum, Arbitrum, Avalanche, Base, BNB Chain, Gnosis, Linea, Optimism, Polygon, Sonic |
| Spark | spark-borrow | Spark Borrow | Pool | Ethereum |
| Aave | aave-borrow | Aave V3 Borrow | Pool | Ethereum, Optimism, Arbitrum, Polygon, Avalanche, Base, BNB Chain, Gnosis, Sonic, Linea, Plasma |
| Lista | lista-borrow | Lista Lending Borrow | Isolated | BNB Chain, Ethereum |
The authoritative chain list is whatever
GET /v1/integrationsreturns at runtime.
Market types: Pool vs Isolated
- Pool markets (Aave, Spark) — shared liquidity across many assets in a single pool. A user's supplied assets and debts all live under the same pool context, and supplied assets can be toggled as collateral.
- Isolated markets (Morpho Blue) — each (collateral asset, loan asset) pair is a standalone market with isolated risk and liquidity. In isolated markets, supply is collateral by definition and does not earn interest.
Intended Flow of Actions
1. Discover markets
GET /v1/markets
2. (Optional) Read user state
GET /v1/positions
3. Create an action
POST /v1/actions → returns ActionDto with transactions[]
4. Sign and submit each transaction in transactions[], in order:
- sign tx.signablePayload
- POST /v1/transactions/{tx.id}/submit { signedPayload }
- poll GET /v1/actions/{id} until the just-submitted tx is CONFIRMED
(action.status will return to CREATED) before signing the next one
5. Poll the action until terminal
GET /v1/actions/{id} → status=SUCCESS (or FAILED)
6. (Async flows only) If hasNextStep is true on the action, poll until
status=WAITING_FOR_NEXT, then POST /v1/actions/{id}/step.
A single action can require multiple transactions
For most actions (e.g., supply, repay), the protocol requires an ERC-20 approval before the main call. The API returns both the APPROVAL and the main transaction together in transactions[] on the original POST /v1/actions response.
totalStepsis1andhasNextStepisfalse— there is no "next step" to fetch.- Sign and submit each transaction in
transactions[]in order (approval first, then the main call). - If allowance is already sufficient, the
APPROVALis omitted fromtransactions[]. - Do not call
/stepto fetch the next transaction — they are already in the original response.
Knowing when it's safe to submit the next transaction
The submit endpoint does not block on prior transactions in the same action. If you submit SUPPLY before APPROVAL is confirmed on-chain, it will revert with ERC20: insufficient allowance. You must wait for APPROVAL to confirm before submitting SUPPLY (same applies to APPROVAL → REPAY).
There is no per-transaction GET endpoint; poll the action and read individual transaction statuses out of ActionDto.transactions[]:
APPROVAL.status: CREATED → not yet submitted
BROADCASTED → submitted, awaiting on-chain confirmation
CONFIRMED → safe to submit the next tx
FAILED → bail; create a new action
Recommended polling loop:
1. Submit APPROVAL → POST /v1/transactions/{approvalTxId}/submit
2. Loop:
GET /v1/actions/{actionId}
approval = action.transactions.find(t => t.type === 'APPROVAL')
if approval.status === 'CONFIRMED' → break
if approval.status === 'FAILED' → bail
sleep 2–5s
3. Submit SUPPLY → POST /v1/transactions/{supplyTxId}/submit
4. Loop until action.status === 'SUCCESS' (or 'FAILED')
Cadence: the broadcasted-transaction tracker polls the chain on a ~5 second tick, so polling our API faster than that buys you nothing. Use 2–5s with exponential backoff up to ~10s for slow chains.
Per-transaction status: CREATED → BROADCASTED → CONFIRMED (or FAILED).
Action status: progresses CREATED → PROCESSING → SUCCESS (or FAILED), but for multi-tx single-step flows it can ping-pong:
CREATED ← initial state, first tx ready to submit
↓ submit APPROVAL
PROCESSING ← APPROVAL broadcast, awaiting confirmation
↓ APPROVAL confirmed, SUPPLY still CREATED
CREATED ← action transitions back; SUPPLY is now ready to submit
↓ submit SUPPLY
PROCESSING ← SUPPLY broadcast
↓ SUPPLY confirmed
SUCCESS ← terminal
The action returning to CREATED after each in-flight tx confirms is the signal that the next pre-built transaction in transactions[] is ready for signing. Do not treat this as WAITING_FOR_NEXT — WAITING_FOR_NEXT is reserved for genuinely async multi-step flows where a future step has to be built server-side (see When /step is used).
Advanced escape hatch: if your signer can pre-sign both transactions with explicit incremental nonces (APPROVAL = nonce N, SUPPLY = nonce N+1), you can submit both immediately — the mempool will sequence them. Most integrators should not rely on this; just wait for CONFIRMED.
When /step is used
/step is usedPOST /v1/actions/:id/step is only used for genuinely async multi-step flows where the next transaction cannot be constructed until an earlier transaction has confirmed on-chain — for example, cross-chain bridges or delayed withdrawals. None of the currently supported integrations (aave-borrow, spark-borrow, morpho-blue-borrow, lista-borrow) use this pattern; they always return hasNextStep: false.
Integration Options
Option A — Drop-in Widget
The Yield.xyz Borrowing Widget is a drop-in lending and borrowing frontend you can embed in any application. It's built with React and designed to feel native inside wallets, exchanges, and fintech apps.
Under the hood the widget uses the Borrowing API to power the full lifecycle: discovery, markets, positions, action construction, signing, broadcast, and status polling.
Best for: wallets, exchanges, and fintech apps that want a turnkey UI.
Option B — Direct API Integration
Integrate natively while keeping full UI control.
Best for: custom UX or product-specific abstractions.
Key Concepts
Health Factor
healthFactor = sum(collateralUsd_i * liquidationThreshold_i) / totalBorrowedUsd
> 1— safe= 1— at liquidation threshold< 1— eligible for liquidationnull— no debt (health factor is undefined whentotalBorrowedUsd = 0)
Available on:
PositionDto.healthFactor— current health factorActionMetadataDto.predictedHealthFactor— preview of post-action health factor (returned onPOST /v1/actions)
LTV and liquidation threshold
- Max LTV (
maxLtv) — maximum borrowable as a fraction of collateral value. - Liquidation threshold (
liquidationThreshold) — LTV at which the position becomes liquidatable. Typically higher thanmaxLtvto provide a buffer. - Liquidation penalty (
liquidationPenalty) — bonus paid to liquidators out of seized collateral.
Supply fees
Some markets are routed through a partner-specific wrapper contract that charges a fee on the supplyCollateral operation. The fee is deducted on-chain when the user supplies, and a deposit of grossAmount results in grossAmount × (1 − feeBps/10000) reaching the underlying protocol as collateral.
Currently supported on: morpho-blue-borrow. Aave and Spark pool markets have no supply fee and always return zero values.
Reading the fee on a market
Every market in GET /v1/markets carries:
| Field | Type | Meaning |
|---|---|---|
supplyCollateralFeeBps | string | Fee rate in basis points ("50" = 0.5%). Range "0"–"500" enforced on-chain. |
feeWrapperAddress | string | null | Wrapper address charging the fee for this project, or null when no wrapper is configured. |
feeWrapperAddress is the authoritative signal for whether a wrapper is configured for a given project. supplyCollateralFeeBps can be "0" even when a wrapper is configured (the wrapper owner can dial the fee to zero at any time).
Reading the fee on a supply action
POST /v1/actions for a supply returns the fee in metadata:
| Field | Type | Meaning |
|---|---|---|
feeAmount | string | Fee in raw units of the supplied token (e.g. "500000" = 0.50 USDC for a 100 USDC supply). |
feeBps | number | Fee rate in basis points at action-creation time. |
Both fields are always present on supply actions; they are "0" / 0 when no wrapper is configured or the wrapper currently charges no fees.
Two ways to size a supply action
- Explicit collateral. Pass
amount(oramountRaw) — this is the gross amount you want to deposit. The wrapper takes its cut on-chain; the user netsamount × (1 − feeBps/10000)as collateral.metadata.feeAmounttells you exactly how much will be skimmed. - Borrow-derived. Pass
borrowAmount+targetLtv. The API solves for the collateral that produces the requested borrow at the target LTV, grossed up for the supply fee, and returns it asmetadata.effectiveCollateralAmount. The action's on-chain supply uses that amount.
grossCollateral = (borrowAmount × loanPriceUsd) ÷ (targetLtv × collateralPriceUsd × (1 − feeBps/10000))
targetLtv must satisfy 0 < targetLtv ≤ market.maxLtv — values above maxLtv are rejected with 400 Bad Request.
Predicted health factor / LTV under a supply fee
metadata.predictedHealthFactor and metadata.predictedLtv reflect the net on-chain position after the wrapper has taken its fee. A supply of 1 cbBTC against a 100 bps wrapper is treated as 0.99 cbBTC of collateral when computing both metrics, matching what Morpho records on-chain.
Approval target
When a wrapper is configured for a Morpho supply, the ERC-20 APPROVAL transaction targets the wrapper address, not Morpho directly. The flow is otherwise unchanged: [APPROVAL, SUPPLY], signed and submitted in order.
Rates
- Supply rate — APY earned on supplied assets (decimal, e.g.
"0.0312"= 3.12%). - Borrow rate — APR paid on borrowed assets (decimal).
- Rates are utilization-driven: higher utilization → higher borrow rates and higher supply yields.
All rates and APYs in the API are returned as decimals (e.g.
"0.0567"= 5.67%), not percentages.
Minimum loan size (Lista Lending)
Lista Lending (lista-borrow) markets enforce a per-market minimum loan floor — a USD-denominated minimum (≈ $15 worth of the loan asset, converted to the loan token at its oracle price). It constrains two actions:
- Borrow — the resulting total debt must be at least the minimum. A borrow that leaves debt below the floor is rejected with
400 Bad Request. - Partial repay — the remaining debt must be either zero or at least the minimum. A partial repay that would leave a dust position below the floor is rejected with
400 Bad Request; userepayAll: trueto close the position instead.
The other integrations (aave-borrow, spark-borrow, morpho-blue-borrow) do not enforce a minimum loan size.
API Endpoints
Integrations
List integrations
GET /v1/integrations
Returns all available lending/borrowing integrations with supported actions and argument schemas.
Response: IntegrationDto[]
[
{
"id": "aave-borrow",
"providerId": "aave",
"name": "Aave V3 Borrow",
"networks": ["ethereum", "arbitrum", "optimism", "polygon", "avalanche", "base", "bsc", "gnosis", "sonic", "linea", "plasma"],
"metadata": {
"description": "Aave is a decentralized non-custodial liquidity protocol where users can participate as suppliers or borrowers.",
"externalLink": "https://aave.com",
"logoURI": "https://assets.stakek.it/providers/aave.svg"
},
"actions": [
{
"id": "supply",
"label": "Supply",
"schema": {
"type": "object",
"properties": {
"marketId": { "type": "string" },
"amount": { "type": "string" },
"amountRaw": { "type": "string" }
},
"required": ["marketId"]
}
}
]
}
]Get integration by ID
GET /v1/integrations/:integrationId
Markets
List markets (paginated)
GET /v1/markets
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
scope | string | No | enabled | enabled returns only markets enabled for your project. all returns all markets across all providers. |
integrationId | string | No | — | Filter by integration |
network | string | No | — | Filter by network |
offset | number | No | 0 | Pagination offset |
limit | number | No | 25 (max 100) | Page size |
Response: PaginatedResponseDto<MarketDto>
{
"items": [
{
"id": "aave-v3-ethereum-usdc",
"integrationId": "aave-borrow",
"network": "ethereum",
"type": "pool",
"poolAddress": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"loanToken": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC",
"name": "USD Coin",
"decimals": 6,
"logoURI": "https://assets.stakek.it/tokens/usdc.svg"
},
"collateralTokens": [
{
"token": {
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH",
"name": "Wrapped Ether",
"decimals": 18,
"logoURI": "https://assets.stakek.it/tokens/weth.svg"
},
"priceUsd": "2750.00",
"maxLtv": "0.80",
"liquidationThreshold": "0.85",
"liquidationPenalty": "0.05",
"supplyRate": "0.0312"
}
],
"borrowRate": "0.0487",
"totalSupply": "1250000000.00",
"totalSupplyRaw": "1250000000000000",
"totalBorrow": "890000000.00",
"totalBorrowRaw": "890000000000000",
"availableLiquidity": "360000000.00",
"availableLiquidityRaw": "360000000000000",
"utilizationRate": "0.712",
"loanTokenPriceUsd": "1.00",
"isBorrowEnabled": true,
"supplyCollateralFeeBps": "0",
"feeWrapperAddress": null
}
],
"total": 142,
"offset": 0,
"limit": 25
}For isolated markets (Morpho Blue),
collateralTokensalways has exactly one entry. For pool markets (Aave, Spark), it lists all eligible collateral assets for the pool.
Get market by ID
GET /v1/markets/:marketId
Positions
Get user positions
GET /v1/positions
Returns the user's full supply + borrow state for an integration on a network.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
integrationId | string | Yes | Integration identifier |
network | string | Yes | Network identifier |
address | string | Yes | User wallet address |
Response: PositionDto
{
"address": "0x1234...",
"integrationId": "aave-borrow",
"network": "ethereum",
"totalSuppliedUsd": "10000.00",
"totalCollateralUsd": "10000.00",
"totalBorrowedUsd": "8750.00",
"netWorthUsd": "1250.00",
"healthFactor": "1.37",
"currentLtv": "0.72",
"availableToBorrowUsd": "1250.00",
"netApy": "0.0089",
"supplyBalances": [
{
"marketId": "aave-v3-ethereum-usdc",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"tokenSymbol": "USDC",
"balance": "10000.000000",
"balanceRaw": "10000000000",
"balanceUsd": "10000.00",
"apy": "0.0312",
"isCollateral": true,
"pendingActions": [
{ "type": "withdraw", "label": "Withdraw USDC", "args": { "marketId": "aave-v3-ethereum-usdc" } }
]
}
],
"debtBalances": [
{
"marketId": "aave-v3-ethereum-weth",
"tokenAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"tokenSymbol": "WETH",
"balance": "2.500000000000000000",
"balanceRaw": "2500000000000000000",
"balanceUsd": "8750.00",
"apy": "0.0523",
"pendingActions": [
{ "type": "repay", "label": "Repay WETH", "args": { "marketId": "aave-v3-ethereum-weth" } }
]
}
]
}Notes:
totalSuppliedUsdis the value of all supplied assets.totalCollateralUsdis the subset enabled as collateral (only these contribute to borrow power).healthFactorisnullwhen the user has no debt.- All APY/rate fields are decimals (
"0.0312"= 3.12%).
Actions
Create an action
POST /v1/actions → 201 Created
Creates a lending/borrowing action and returns the unsigned transaction(s) for signing.
Request body: ActionRequestDto
| Field | Type | Required | Description |
|---|---|---|---|
integrationId | string | Yes | Integration identifier |
action | string | Yes | Action type (see Action types) |
address | string | Yes | User wallet address |
args | object | Yes | Action arguments (see below) |
Action args
| Field | Type | Required | Notes |
|---|---|---|---|
marketId | string | Yes | Target market identifier (from GET /v1/markets) |
amount | string | One of * | Human-readable amount (e.g. "1.5") |
amountRaw | string | One of * | Base units (e.g. "1500000" for 1.5 USDC) |
repayAll | boolean | repay only | Full-debt repay using the protocol's full-repay semantics. Only valid for repay. Mutually exclusive with amount / amountRaw. |
tokenAddress | string | No | Token address to supply / borrow / repay / withdraw |
collateralTokenAddress | string | Pool protocols only | Required when providing collateralAmount on pool-based protocols (Aave). Inferred from marketId for isolated-market protocols (Morpho Blue). |
collateralAmount | string | No | Optional collateral amount, human-readable. Used by borrow (post collateral before borrowing) and repay (withdraw collateral after repaying). |
collateralAmountRaw | string | No | Same as above in base units |
borrowAmount | string | supply only † | Desired borrow amount in human-readable units of the market's loan token. Provide together with targetLtv to have the API derive the collateral amount to supply (with the supply fee already grossed in). Mutually exclusive with amount / amountRaw. |
targetLtv | string | supply only † | Target loan-to-value as a decimal ("0.6" = 60%). Must satisfy 0 < targetLtv ≤ market.maxLtv — values above maxLtv return 400. Provide together with borrowAmount. |
- Provide
amountoramountRaw(orrepayAll: truefor full-debt repay). Never combine them. - †
borrowAmountandtargetLtvmust be provided together and only onsupplyactions. They cannot be combined withamount/amountRaw. The API returns the derived collateral asmetadata.effectiveCollateralAmountand uses it as the supply amount on the transaction.
Examples
Supply (explicit collateral amount):
{
"integrationId": "aave-borrow",
"address": "0x1234...",
"action": "supply",
"args": { "marketId": "aave-v3-ethereum-usdc", "amount": "10000" }
}Supply (derive collateral from desired borrow + target LTV):
{
"integrationId": "morpho-blue-borrow",
"address": "0x1234...",
"action": "supply",
"args": {
"marketId": "morpho-blue-ethereum-cbbtc-usdc",
"borrowAmount": "50000",
"targetLtv": "0.75"
}
}Borrow:
{
"integrationId": "aave-borrow",
"address": "0x1234...",
"action": "borrow",
"args": { "marketId": "aave-v3-ethereum-weth", "amount": "2.5" }
}Full-debt repay:
{
"integrationId": "morpho-blue-borrow",
"address": "0x1234...",
"action": "repay",
"args": { "marketId": "morpho-blue-borrow-ethereum-wbtc-usdt-...", "repayAll": true }
}Response: ActionDto
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"integrationId": "aave-borrow",
"action": "supply",
"address": "0x1234...",
"status": "CREATED",
"currentStep": 1,
"totalSteps": 1,
"hasNextStep": false,
"transactions": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"network": "ethereum",
"chainId": "1",
"type": "APPROVAL",
"status": "CREATED",
"address": "0x1234...",
"signingFormat": "EVM_TRANSACTION",
"signablePayload": "{\"to\":\"0xA0b8...\",\"data\":\"0x095ea7b3...\"}"
},
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"network": "ethereum",
"chainId": "1",
"type": "SUPPLY",
"status": "CREATED",
"address": "0x1234...",
"signingFormat": "EVM_TRANSACTION",
"signablePayload": "{\"to\":\"0x8787...\",\"data\":\"0x617ba037...\"}"
}
],
"metadata": {
"currentHealthFactor": "1.82",
"predictedHealthFactor": "1.37",
"currentLtv": "0.55",
"predictedLtv": "0.72",
"liquidationThreshold": "0.85",
"predictedTotalSupplyUsd": "20000.00",
"predictedTotalDebtUsd": "8750.00",
"feeAmount": "0",
"feeBps": 0
},
"createdAt": "2026-02-23T12:00:00.000Z"
}Both the
APPROVALand the main transaction (here,SUPPLY) are returned together. Sign and submit them in order.APPROVALis omitted when the user's allowance is already sufficient.
Get action
GET /v1/actions/:id
Returns the current state of an action. Use for polling status transitions.
List actions (paginated)
GET /v1/actions
| Parameter | Type | Required | Description |
|---|---|---|---|
address | string | No | Filter by wallet |
integrationId | string | No | Filter by integration |
action | string | No | Filter by action type |
status | string | No | Filter by single status |
statuses | string[] | No | Filter by multiple statuses (CSV or repeated param, e.g. ?statuses=SUCCESS,FAILED). Takes precedence over status when both are provided. |
offset | number | No | Default 0 |
limit | number | No | Default 25, max 100 |
Response: PaginatedResponseDto<ActionDto>
Advance to next step (async flows only)
POST /v1/actions/:id/step
For async multi-step actions (cross-chain bridges, delayed withdrawals), retrieves the next transaction(s) once the previous step is confirmed on-chain. Only call this when hasNextStep is true. None of the currently supported integrations use this pattern.
Transactions
Submit a signed transaction
POST /v1/transactions/:transactionId/submit
Submit either a signedPayload (the API broadcasts) or a transactionHash (you've already broadcast). Provide exactly one.
Request body: SubmitTransactionDto
| Field | Type | Description |
|---|---|---|
signedPayload | string | Signed transaction payload (max 1 MB) |
transactionHash | string | 0x + 64 hex chars |
Response: SubmitTransactionResponseDto
{
"transactionHash": "0xabc123...",
"link": "https://etherscan.io/tx/0xabc123...",
"status": "BROADCASTED"
}Reference Tables
Action types
| Action | Description |
|---|---|
supply | Deposit assets into the protocol. In pool markets (Aave/Spark) earns supply APY and is optionally enabled as collateral. In isolated markets (Morpho Blue) is collateral by definition and does not earn APY. |
borrow | Borrow assets against supplied collateral |
repay | Repay borrowed debt (partial or full via repayAll: true) |
withdraw | Withdraw supplied assets |
enableCollateral | Enable a supplied asset as collateral (pool markets only) |
disableCollateral | Disable a supplied asset as collateral (pool markets only) |
Transaction types
| Type | Description |
|---|---|
APPROVAL | ERC-20 approval (pre supply / repay) |
SUPPLY | Deposit into the protocol |
BORROW | Borrow from the protocol |
REPAY | Repay debt |
WITHDRAW | Withdraw supplied assets |
ENABLE_COLLATERAL | Enable collateral usage |
DISABLE_COLLATERAL | Disable collateral usage |
Transaction statuses
| Status | Meaning |
|---|---|
CREATED | Ready for signing |
BLOCKED | Waiting on a prior tx in the same action |
WAITING_FOR_SIGNATURE | Sent to signer |
SIGNED | Signed by user |
BROADCASTED | Broadcast submitted, awaiting inclusion |
PENDING | Awaiting confirmations |
CONFIRMED | Confirmed on-chain (terminal) |
FAILED | Failed on-chain (terminal) |
SKIPPED | Not needed (e.g., allowance already sufficient) |
Action statuses
| Status | Meaning | Terminal |
|---|---|---|
CREATED | At least one transaction in transactions[] is CREATED and ready for signing. Can re-appear within a multi-tx single-step flow once the previously broadcast tx confirms. | No |
PROCESSING | At least one transaction has been broadcast and is awaiting confirmation. | No |
WAITING_FOR_NEXT | Async multi-step flows only. The current step is confirmed and the next step has to be built server-side via POST /v1/actions/:id/step. None of the current borrow integrations use this. | No |
STALE | A transaction has been broadcast for > 30 minutes without confirmation. | No |
SUCCESS | All transactions confirmed. | Yes |
FAILED | An on-chain transaction failed. | Yes |
CANCELED | The action was canceled. | Yes |
End-to-end Examples
Supply → Borrow
1) GET /v1/markets?integrationId=aave-borrow&network=ethereum
2) POST /v1/actions
{
"integrationId": "aave-borrow",
"action": "supply",
"address": "0x...",
"args": { "marketId": "aave-v3-ethereum-usdc", "amount": "10000" }
}
→ ActionDto with totalSteps=1, hasNextStep=false
→ transactions = [APPROVAL, SUPPLY] (APPROVAL omitted if allowance is sufficient)
3) For each tx in transactions[], in order:
- sign tx.signablePayload
- POST /v1/transactions/{tx.id}/submit { signedPayload }
4) Poll GET /v1/actions/{id} until status=SUCCESS
5) POST /v1/actions
{
"integrationId": "aave-borrow",
"action": "borrow",
"address": "0x...",
"args": { "marketId": "aave-v3-ethereum-weth", "amount": "2.5" }
}
→ transactions = [BORROW]
6) Sign + submit, poll until SUCCESS
7) GET /v1/positions?integrationId=aave-borrow&network=ethereum&address=0x...
Repay → Withdraw
1) POST /v1/actions
{
"integrationId": "aave-borrow",
"action": "repay",
"address": "0x...",
"args": { "marketId": "aave-v3-ethereum-weth", "repayAll": true }
}
→ transactions = [APPROVAL, REPAY] (APPROVAL omitted if allowance is sufficient)
2) Sign + submit each tx in transactions[], in order
Poll until SUCCESS
3) POST /v1/actions
{
"integrationId": "aave-borrow",
"action": "withdraw",
"address": "0x...",
"args": { "marketId": "aave-v3-ethereum-usdc", "amount": "10000" }
}
→ transactions = [WITHDRAW]
4) Sign + submit, poll until SUCCESS
Monetization
Yield.xyz's borrow and lending integrations expose multiple monetization paths. The right choice depends on the protocol — some support direct fee capture via wrapper contracts, others monetize through protocol-level revenue share or curated liquidity.
Direct Fee Capture
A single wrapper contract per chain charges configurable fees inside the user's transactions. The same wrapper exposes two independent fee surfaces — borrow fees on borrowed amounts and supply fees on collateral deposited. Each has its own bps getter on the contract, can be set to zero independently, and is capped at 500 bps (5%) on-chain.
Revenue is immediate, fully attributable, and does not depend on the underlying protocol's own fee mechanics. The user's position stays on their EOA.
Currently supported on: morpho-blue-borrow. Aave and Spark pool markets have no fee wrapper today and always return zero values.
Supply Fees
A deposit fee charged on collateral supplied via supplyCollateral. A user supplying 1 cbBTC against a 50 bps wrapper deposits 1 cbBTC and ends up with 0.995 cbBTC of on-chain collateral — the 0.005 cbBTC is captured as fee.
Flow:
- 2-tx: ERC-20 approval to the wrapper →
wrapper.supplyCollateral(...). No EIP-712 variant; the wrapper holds the allowance for exactly the duration of the call. - Approval target is the wrapper address, not Morpho. The API returns the correct spender so no client-side branching needed.
Key properties:
- Fee basis: gross collateral submitted. Range 0–500 bps, capped on-chain.
- Independent of the borrow fee:
supplyCollateralFeeBpsandborrowFeeBpsare separate per-operation rates on the same wrapper — either can be zero while the other is active. - Predicted risk metrics are post-fee.
predictedHealthFactorandpredictedLtvreflect the net on-chain position, matching what Morpho records after the wrapper takes its cut. - Two ways to size a supply action. Pass
amount/amountRawas the gross collateral and readmetadata.feeAmountto know what will be skimmed; or passborrowAmount+targetLtvand let the API gross up the collateral so the post-fee position hits the requested LTV.
API surface:
| Field | Where | Meaning |
|---|---|---|
supplyCollateralFeeBps | every GET /v1/markets item | Current supply fee rate in basis points ("0"–"500"). |
feeWrapperAddress | every GET /v1/markets item | Wrapper address charging the fee for this project, or null if none configured. |
metadata.feeBps | POST /v1/actions for supply | Fee rate at action-creation time. Always present; 0 when no wrapper or wrapper currently charges zero. |
metadata.feeAmount | POST /v1/actions for supply | Fee in raw units of the supplied (collateral) token. Always present; "0" when there is no fee. |
metadata.effectiveCollateralAmount | POST /v1/actions for supply (derived) | Gross collateral derived from borrowAmount + targetLtv, already grossed up for the supply fee. |
Borrow Fees
A wrapper contract between the user and Morpho Blue that takes an origination fee on the borrowed amount. Borrowing is the true value unlock for a credit product, so charging there lands the fee on the right side of the equation — a user posting $100K collateral to borrow $50K is charged against the $50K, not the $100K.
The position stays on the user's EOA. One wrapper contract per chain. No per-user accounts, factories, or proxies.
Two execution modes:
| Mode | Flow | When to use |
|---|---|---|
borrow | 3-tx: authorize → wrapper borrow → deauthorize | Clients that can't sign EIP-712 messages |
borrowWithSig | 1-tx atomic: authorize + borrow + fee + deauthorize via EIP-712 sigs | Clients that support message signing |
Key properties:
- Fee basis: borrowed amount. Origination fee in bps, bounded on-chain by
minFeeBps/maxFeeBps. - Dynamic pricing: exact fee is computed off-chain by the API per market and passed into each transaction — we can charge less when borrow rates are high and more when they're low, within the on-chain bounds.
- No standing authorization: the wrapper is authorized only for the duration of the flow and deauthorized at the end. For the 3-tx variant the API enforces the deauthorize step by returning
authorize,borrow, anddeauthorizetogether in the same action'stransactions[], so it always runs. - Wrapper only touches borrow. Supply, repay, and withdraw go directly to Morpho — no fee, no extra hops.
- Fee splitting between Yield.xyz and the partner is settled off-chain.
Revenue Share via Protocol Mechanics
For protocols that don't lend themselves to direct fee capture, monetization runs through protocol-level revenue share or curated liquidity.
SparkLend — Integrator Revenue Share
SparkLend does not expose a commission or fee that integrators can charge on top. Spark's economics are driven by its cost of capital — the rate at which Spark borrows from Sky — and it captures a variable margin between that funding cost and user borrowing activity. A portion of that margin is shared with integrators.
- Attribution: borrowing volume is tracked per integrator via an integrator-specific
ref_code. - Revenue basis: a share of Spark's margin over its cost of capital — not a fixed protocol fee, and not the market's visible borrow–supply spread.
- Variability: the margin and the integrator share are not fixed; they flex with borrowing demand, market conditions, and the scale/quality of the integrator.
- Distribution: typically manual, paid out once a minimum borrowed-TVL threshold is reached.
###
Updated 3 months ago

