> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trusset.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Lending

> Overcollateralized lending against imported security tokens

Lending markets let you lend against security tokens that were issued outside Trusset and imported into your instance. A borrower pledges tokens, receives a settlement asset such as USDC, and repays with interest. Liquidity providers fund the pool and earn the supply rate.

The collateral is a third-party token you do not control. The API therefore exposes the compliance plumbing that makes such a token usable as collateral: adapter authorization, identity registry checks, and a named collateral agent recorded on every lock.

## Collateral token standards

The market itself is standard-agnostic. It tracks references and amounts and never touches the token, which is the collateral adapter's job. What a market accepts is therefore decided by the adapter its collateral mode uses.

A `CUSTODY` market holds collateral through the ERC-20 interface and nothing else. ERC-20, ERC-1400 and ERC-3643 all work there, and so does a token with custom compliance that happens to present an ERC-20 surface.

A `FREEZE` market is the narrow one. Freezing a balance where it sits needs `isAgent`, `getFrozenTokens`, `freezePartialTokens` and `unfreezePartialTokens`, which is the ERC-3643 agent surface. A token without it is refused at deployment with `FREEZE_MODE_UNSUPPORTED_TOKEN`, and `CUSTODY` is the answer.

What the API does require of every collateral token, in either mode, is a surface its adapter can be authorized on. One of three satisfies it: an identity registry, a contract allow-list, or the ERC-3643 freeze interface. A token exposing none of the three is refused with `NOT_A_SECURITY_TOKEN`, because an adapter that can never be authorized could never take collateral.

## Base Path

```
https://api.trusset.org/lending-external-securities/api
```

Six resource groups sit under that path:

| Group                                             | Path            | Covers                                                           |
| ------------------------------------------------- | --------------- | ---------------------------------------------------------------- |
| Markets                                           | `/markets`      | Deployment, configuration, oracle pricing, readiness             |
| Positions                                         | `/positions`    | Liquidity provision, borrowing, position queries                 |
| Liquidations                                      | `/liquidations` | Liquidation execution, settlement, Dutch auctions                |
| Hooks                                             | `/hooks`        | Risk monitors and automated liquidation reactions                |
| [Vaults](/endpoints/vaults/introduction)          | `/vaults`       | Investor deposits and redemptions on an operator's earning vault |
| [Pledges](/endpoints/asset-register/introduction) | `/pledges`      | The pledge record a register provider enters against a token     |

Every request authenticates with an instance API key in the `X-API-Key` header. See [Authentication](/endpoints/authentication). The instance bound to your key determines both the network and the data you can reach. Markets belonging to another instance return `MARKET_NOT_FOUND`.

## Response envelope

```json theme={null}
{
  "success": true,
  "data": { },
  "error": null,
  "metadata": {
    "timestamp": "2025-06-15T12:00:00.000Z",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "instanceId": "inst_abc123"
  }
}
```

On failure, `success` is `false`, `data` is `null`, and `error` carries `code` and `message`. Schema failures use `VALIDATION_ERROR` with `error.details` listing each offending field. Always branch on `success` rather than on the HTTP status alone.

## Writes

Every write endpoint returns unsigned transaction data for a wallet you control to sign and broadcast. Nothing is submitted on your behalf.

```json theme={null}
{
  "action": "SIGN_TRANSACTION",
  "transaction": { "to": "0x70A0...", "data": "0x..." },
  "functionName": "openLoan"
}
```

Every `transaction` object carries the `chainId` your instance resolves to and `value: "0"`, so a payload can never be broadcast against the wrong network by accident.

Some operations need more than one transaction. Those return `SIGN_TRANSACTIONS` with an ordered `steps` array that must be broadcast in sequence.

```json theme={null}
{
  "action": "SIGN_TRANSACTIONS",
  "steps": [
    { "action": "SIGN_TRANSACTION", "transaction": { "to": "0xUSDC...", "data": "0x..." }, "functionName": "approve", "description": "Approve the market to pull USDC" },
    { "action": "SIGN_TRANSACTION", "transaction": { "to": "0x70A0...", "data": "0x..." }, "functionName": "addLiquidity" }
  ]
}
```

A multi-step payload also names the step that carries the confirmable action, as `confirmStepIndex` and `confirmWith`. Confirm with that step's hash, which is not always the last one.

Once you have broadcast a transaction, call the same endpoint again with its hash in the body:

```json theme={null}
{ "txHash": "0x9f2c41d8b7e05a3164c2870fbd935e1a4c7802db6135ea9048f7c21b5d3ea41b" }
```

The API verifies the mined receipt, records the result against the market, and returns the values that only exist after execution, such as `loanId`, `positionId`, `liquidationId`, and `auctionId`. Verification checks that the transaction succeeded, targeted the market this endpoint operates on, and called the expected function on the entity you named. See [Confirming a Transaction](/endpoints/introduction#confirm-a-transaction).

<Warning>
  Positions, liquidity providers, and the transaction ledger are written on the confirm call. Broadcast without confirming and the loan is still live on-chain, but Trusset will not see it until the next reconciliation pass.
</Warning>

Trusset never holds, fetches or uses a private key, and no endpoint signs on your behalf. This is enforced rather than assumed. A write whose body carries a field named for key material, or for signing on behalf of someone else, is rejected with `400 KEY_MATERIAL_REJECTED` at any nesting depth. The refused names include `privateKey`, `mnemonic`, `seedPhrase`, `keystore`, `signerKey`, `secretKey`, `useRelayer`, `signOnBehalf`, `onBehalfOf` and `autoSubmit`.

## Collateral modes

Every market is deployed in one of two modes, fixed at deployment. A single collateral token can back several markets, as long as they differ in collateral mode or in settlement asset.

**`FREEZE`** is the default. The adapter freezes the borrower's tokens where they sit. The borrower keeps custody and no ERC-20 approval is needed to open a loan. This requires the market's adapter to hold the agent role on the collateral token, granted by whoever controls that token's agent administration. The token must implement the ERC-3643 freeze interface (`isAgent` and `getFrozenTokens`), which is checked at deployment and rejected with `FREEZE_MODE_UNSUPPORTED_TOKEN` if absent.

**`CUSTODY`** escrows the tokens in the adapter. Borrowers must call `approve` on the collateral token before posting collateral, and the API returns that approval as an explicit step. This requires the adapter to be a verified holder on the token's identity registry. If a return transfer is ever blocked by compliance, the borrower reclaims through [Claim Escrowed Collateral](/endpoints/lending/claim-escrowed-collateral).

Both modes require the market's liquidation router to be an eligible recipient of the collateral token, because a liquidation transfers seized tokens to it.

## Pricing

There is no automatic price feed. Prices come from the issuer, as a net asset value the issuer publishes.

[Sync Oracle Price](/endpoints/lending/sync-oracle) pushes a NAV on-chain. [Sign Price](/endpoints/lending/sign-price) returns the EIP-712 payload for a price update, which you sign in your own environment. A borrower then submits that signature alongside their own transaction, updating the price atomically without a separate oracle write. Both endpoints require an explicit positive `price`. Both also require the signing wallet to be an authorized signer on the market's oracle.

`priceSource` records how the market's price is meant to be interpreted. `NAV` is the default. `MARKET` signals an observable market price and the contract responds by disallowing Dutch auctions and raising the auction floor premium. The backend applies no branching of its own on this field.

That covers the two sources a price is pushed to. `ORACLE` is the third, and it works the other way: the market reads an external feed named at deployment, the factory probes that feed inside the deployment transaction, and no opening price is stated. Nothing is pushed to an `ORACLE` market. The source is fixed at deployment and cannot be changed afterwards on any market.

## Rate modes

The market's interest model is deployed with the market, and how it prices is fixed there. Read it from `rateMode` on [List Markets](/endpoints/lending/list-markets) or [Get Market Metrics](/endpoints/lending/get-metrics).

| `rateMode` | What the borrower pays                                                              |
| ---------- | ----------------------------------------------------------------------------------- |
| `CURVE`    | The utilization curve, unbounded. The default                                       |
| `CLAMPED`  | That curve, held between `rateFloor` and `rateCap`                                  |
| `FIXED`    | One rate, stamped onto each loan when it opens, unchanged for the life of that loan |

On a `FIXED` market, three numbers can differ at once and each answers a different question. `borrowRate` is what the pool charges in aggregate. `fixedStampRateBps` is what a loan opened right now would be stamped with. What an outstanding loan pays was stamped when it opened and is on neither figure. Quote a borrower from the `offering` block on [Open Loan](/endpoints/lending/open-loan), which is the only place the rate for the loan being opened appears before it exists.

The supply rate is the borrow rate less three deductions: the Trusset infrastructure fee, the `operatorShare` the market offered its lender of record, and the market's own `protocolFeeRate`. `operatorShare` is capped at half, so the liquidity providers always keep the larger part.

## Loan terms

A market can carry a term offering, also fixed at deployment. `maxLoanDuration` sets how long a loan may run, `terminalDueDate` sets a hard date every loan falls due by whatever its own duration, and `termGracePeriod` sets how long after the due date the term penalty waits. A market whose `maxLoanDuration` and `terminalDueDate` are both zero makes open-ended loans and none of this applies to it.

Terms change what liquidation means. A termed loan becomes liquidatable when its term runs past the due date plus grace, whatever its health factor. That is a second trigger alongside the health one, and only the health trigger is reported by [Get Liquidatable Loans](/endpoints/lending/get-liquidatable-loans).

Terms also bound the exit. `latestDueAt` and `exitHorizon` on [Get Market Metrics](/endpoints/lending/get-metrics) say when the book could in principle be wound down. An unbounded horizon means at least one open-ended loan is outstanding.

## Vault liquidity

A market can be allowed to draw on registered vaults when its own pool runs short. `useVaultLiquidity` says whether it may, and `reserveRatio` is the share of its own pool held back from borrowing.

The figure that decides whether a loan can be funded is `borrowableLiquidity`, not `availableLiquidity`. It already nets off the reserve and adds whatever a vault would lend. Gate a borrow control on it.

## Units

Three separate decimal domains apply. Send and receive plain decimal strings in every case. Do not send base units.

| Domain       | Precision                                                 | Applies to                                                                                          |
| ------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Borrow asset | The settlement token's decimals, on `borrowAssetDecimals` | Borrow, repay, liquidity, prices, minimums, proceeds                                                |
| Collateral   | The collateral token's decimals, on `collateralDecimals`  | Collateral amounts, caps, seized amounts, auction sizes                                             |
| LP shares    | Always 18                                                 | The `shares` argument to [Remove Liquidity](/endpoints/lending/remove-liquidity), and `totalShares` |

Both decimal values are on the market record from [Get Market](/endpoints/lending/get-market). Sending more decimal places than the target token supports returns `VALIDATION_ERROR` with the message `Amount must be a number with at most N decimal places`.

Risk parameters are integers in basis points. `7500` is 75 percent. Health factor is a decimal string where `1.0` is the liquidation boundary: at or above is healthy, below is liquidatable.

<Note>
  The borrow asset is fixed at deployment. Any ERC-20 with 6 to 18 decimals is accepted, validated on-chain at deploy time. Omitting it selects USDC. See [List Borrow Assets](/endpoints/lending/list-borrow-assets).
</Note>

## Lender of record

A market is deployed without parties. It names no admin, no issuer and no collateral agent. Instead it carries the `candidates` you nominated at [Deploy Market](/endpoints/lending/deploy-market), or it was opened to every approved institution with `openToAnyCurator`. Until one of them takes the role, the market is **seeking a lender of record**. `DEFAULT_ADMIN_ROLE`, `ISSUER_ROLE` and the oracle owner are all unheld, so every administrative transaction on it would revert. `openLoan` and `addLiquidity` revert too.

The API refuses those calls before a wallet signs rather than letting them be paid for in gas. Opening a loan, borrowing more, providing liquidity, changing risk parameters and changing who may liquidate all return `409 MARKET_PENDING_CURATOR` while the market is in this state. [Get Configuration Status](/endpoints/lending/get-configuration-status) reports it as `pendingCurator`, and never reports a market fully configured while it holds.

Taking the role is what names the parties. The adopting institution declares its own `admin`, `issuer`, `collateralAgent`, `saleRecipient`, `liquidationOperator`, `oracleOwner` and `priceSigner`, a `realizationMode` of `EXCHANGE_SALE`, `ISSUER_REDEMPTION` or `AUCTION`, and a `liquidityCommitment`. Because that binds an institution to another party's market, the right to take the role is granted by Trusset and is not derived from a nomination alone.

<Note>
  Adoption is not performed with an instance API key. A nominated institution takes the role from its own Trusset issuer account. This surface exposes two reads. [Get Nominations](/endpoints/lending/get-nominations) shows who has been nominated on a market. [List Pending Markets](/endpoints/lending/list-pending-markets) shows the markets currently seeking a lender of record.
</Note>

## Market lifecycle

<Steps>
  <Step title="Deploy">
    [Deploy Market](/endpoints/lending/deploy-market) deploys the market, oracle, interest model, insurance fund, collateral adapter, and a dedicated liquidation router, in one factory transaction. It also nominates the candidates who may become the lender of record.
  </Step>

  <Step title="Adopt">
    A nominated candidate takes the lender-of-record role, which names the market's parties and declares how seized collateral is realized. Track it with [Get Nominations](/endpoints/lending/get-nominations).
  </Step>

  <Step title="Authorize">
    A new market cannot take loans until token-side authorizations are complete. [Get Setup Steps](/endpoints/lending/get-setup-steps) returns the outstanding items with ready-to-sign calldata. These are executed by the token's agent or identity registry operator, not by Trusset.
  </Step>

  <Step title="Configure">
    [Update Market Config](/endpoints/lending/update-config) sets collateral factor, liquidation threshold, penalties, fees, caps, and auction behaviour. Defaults are seeded at deployment.
  </Step>

  <Step title="Price">
    Push an initial NAV with [Sync Oracle Price](/endpoints/lending/sync-oracle). Loans revert against a stale price.
  </Step>

  <Step title="Fund">
    Liquidity providers deposit the borrow asset with [Add Liquidity](/endpoints/lending/add-liquidity). The first deposit into an empty pool has a minimum.
  </Step>

  <Step title="Lend">
    Borrowers open positions with [Open Loan](/endpoints/lending/open-loan).
  </Step>
</Steps>

Confirm readiness with [Get Configuration Status](/endpoints/lending/get-configuration-status) before directing borrowers at a market.

## Liquidations

A loan becomes liquidatable when its health factor falls below `1.0`. What happens next depends on the market's `useDutchAuction` setting.

With Dutch auctions enabled, liquidation opens an auction whose price decays linearly from `auctionStartPremium` to `auctionMinPremium` over `auctionDuration`. Bidders buy collateral through [Bid on Auction](/endpoints/lending/bid-on-auction). Collateral unsold when the auction expires routes to the liquidation router through [Settle Expired Auction](/endpoints/lending/settle-expired-auction).

With Dutch auctions disabled, seized collateral transfers straight to the liquidation router and a pending liquidation is recorded.

Once collateral reaches the router it does not sell itself. There is no auto-sell for external securities, because these instruments have no on-platform venue. The operator drives settlement explicitly:

<Steps>
  <Step title="Withdraw for sale">
    [Withdraw Collateral for Sale](/endpoints/lending/withdraw-collateral-for-sale) moves collateral to the sale recipient the lender of record fixed when it took the role. That destination cannot be rotated, and it must pass the token's compliance checks.
  </Step>

  <Step title="Sell off-platform">
    Execute the sale through your own channel. Trusset is not involved.
  </Step>

  <Step title="Report proceeds">
    [Report Partial Sale](/endpoints/lending/report-partial-sale) deposits interim proceeds into the router. Repeatable.
  </Step>

  <Step title="Settle">
    [Settle Liquidation](/endpoints/lending/settle-liquidation) routes the cumulative proceeds to the market and closes the liquidation.
  </Step>
</Steps>

Proceeds above the debt plus liquidation penalty are returned to the borrower by the contract. If the settlement token blocks that transfer on compliance grounds, the surplus is escrowed on the market for the borrower to claim, rather than reverting the settlement.

A liquidation left unsettled for 7 days can be written off with [Handle Liquidation Timeout](/endpoints/lending/handle-timeout), which draws on the insurance fund to cover the bad debt. Write-off does not recover collateral from the router, so dispose of or return the collateral first.

## Per-market liquidation router

Each market gets its own `SecurityLiquidationRouter`, deployed by the factory in the same transaction. The market admin holds `DEFAULT_ADMIN_ROLE` on it and the `liquidationOperator` holds `OPERATOR_ROLE`. Both are named by the lender of record when it takes the role, not at deployment. Trusset holds no role. Naming your registered wallet as `liquidationOperator` is what allows settlement to be driven through this API.

Every market payload carries `liquidationRouterAddress`. Endpoints that act on a liquidation resolve the correct router automatically.

Every market carries its own router for its whole life. Deployment rejects a market without one and no setter can clear it, so there is no shared router to fall back to. Two consequences follow. A router read that fails is a retryable failure rather than a reason to guess, and it comes back as `ROUTER_UNRESOLVED`. And liquidation IDs restart at 1 on each router, so the same ID can name a different liquidation on two markets in one instance. The settlement endpoints take a `marketId` to say which you mean, and refuse with `AMBIGUOUS_LIQUIDATION_ID` when it is missing and needed.

## Rate limits

Every request made with an instance API key counts against a ceiling of 200 requests per minute for that key. Some endpoints carry a second, tighter limit on top of it:

| Limit          | Applies to                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200 per minute | Every request on this surface, keyed by API key                                                                                                                                                                                                                                                                                                                                                                                                               |
| 10 per minute  | Every write, plus [Get Lifecycle Statement](/endpoints/lending/get-lifecycle-statement)                                                                                                                                                                                                                                                                                                                                                                       |
| 100 per minute | [List Markets](/endpoints/lending/list-markets), [List Borrow Assets](/endpoints/lending/list-borrow-assets), [Get Market History](/endpoints/lending/get-history), [Get Index Status](/endpoints/lending/get-index-status), [Get Terms](/endpoints/lending/get-terms), [List Terms Versions](/endpoints/lending/list-terms-versions), [Get Nominations](/endpoints/lending/get-nominations), [List Pending Markets](/endpoints/lending/list-pending-markets) |

Reads not named above are bounded only by the 200 per minute ceiling. [Verify Signed Price](/endpoints/lending/verify-price) is a write that carries no tighter limit, because it reads a signature rather than moving anything.

Exceeding a limit returns `429` with code `RATE_LIMIT_EXCEEDED`, a `Retry-After` header in seconds, and `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. See [Rate Limits](/endpoints/rate-limits).

## Credits

Requests on this surface also draw on your instance's monthly API credit allowance, billed against the lending service. Every response carries `X-Credits-Limit`, `X-Credits-Remaining` and `X-Credits-Reset`, plus an `X-Request-Id` you can quote in support requests. When the allowance is exhausted the request is refused with `402` and code `CREDIT_LIMIT_EXCEEDED`, and `metadata` carries `creditsLimit`, `creditsRemaining` and `resetsAt`. Credits consumed by a request that fails with a `5xx` are refunded automatically.
