Borrowing
Withdraw Collateral
Release excess collateral from an open loan
POST
/
lending-external-securities-v2
/
api
/
positions
/
{marketId}
/
withdraw-collateral
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/withdraw-collateral" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "amount": "100"}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/withdraw-collateral`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, amount: '100' })
});
const { data } = await res.json();
if (data.collateralRelease) {
console.warn(data.warning);
}
const tx = await borrowerWallet.sendTransaction(data.transaction);
await tx.wait();
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: tx.hash })
});
const { data: result } = await confirmed.json();
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "withdrawCollateral",
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/withdraw-collateral",
"field": "txHash",
"body": { "loanId": "5", "txHash": null }
}
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0"
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0",
"escrowed": true,
"escrowedAmount": "100.0",
"delivered": false,
"adapterAddress": "0x3c5e8b1d7f9a2046c8e1b3d5f7a9c0e2d4b6f8a1",
"claimAction": "claim-escrowed-collateral",
"message": "The collateral token refused this release, so the custody adapter holds the amount in escrow instead of delivering it. The holder reclaims it with claim-escrowed-collateral once the restriction is lifted."
}
}
Removes collateral from a loan while leaving it open. Only the loan’s borrower can sign it. The contract reverts if the withdrawal would push the health factor below
The API applies a stricter bound than the contract. It refuses a withdrawal that would leave the debt above what the remaining collateral supports at the market’s collateral factor, the limit a new borrow faces. A withdrawal between that limit and the liquidation threshold is refused here with
While a Dutch auction is open against the loan, the contract reverts the withdrawal. The API does not check for an open auction before offering calldata.
Confirming with
1.0, measured at the liquidation threshold, so a loan can only shed collateral it does not need.
A
CUSTODY withdrawal the collateral token refuses does not revert. The adapter escrows the balance instead, so the borrower signs a transaction that confirms and delivers nothing. When that is the case, or when the token’s verdict cannot be read, the response carries collateralRelease and warning. Surface them to the borrower before they sign.WOULD_BREACH_HEALTH_FACTOR, although the contract would accept it.
Single transaction in both modes.
Path Parameters
string
required
Body Parameters
integer
required
On-chain loan ID. A positive integer. Accepted as a JSON number or as a numeric string, so the value Get Loan returns passes straight back.
string
Security tokens to release, as a positive decimal string. Parsed at the collateral token’s decimals. Required when building calldata, not needed on the confirm call. On a token whose ERC-1400
granularity() is above one base unit, it must be a whole multiple of the granularity.object
EIP-712 signed price from Sign Price. Omit to price against the stored oracle value. A market that prices from an external feed refuses a signed price with
EXTERNAL_FEED_ACTIVE, and a market with a fixed price with FIXED_PRICE_ACTIVE.string
Hash of the transaction you broadcast for this operation. Send it to confirm the transaction and record the result. Omit it to receive the calldata.
Response Fields
object
Show child attributes
Show child attributes
string
SIGN_TRANSACTION when txHash is omitted.object
Unsigned transaction,
{ to, data, value, chainId }, for the withdrawCollateral call. Returned when txHash is omitted.string
withdrawCollateral. Returned when txHash is omitted.object
Present on a
CUSTODY market when the collateral token would refuse to return the collateral to the borrower, or when its verdict cannot be read before signing. The withdrawal still succeeds, and the borrower reclaims an escrowed balance through Claim Escrowed Collateral.string
Human-readable form of
collateralRelease, present alongside it.object
{ endpoint, field, body }: this route, txHash as the field, and a body carrying loanId with txHash: null. Returned when txHash is omitted.string
Transaction hash. Returned when confirming with
txHash.string
Loan ID, echoed back as a string. Returned when confirming with
txHash.string
Collateral released from the loan, read from the mined transaction. Returned when confirming with
txHash.boolean
true when the adapter escrowed the release instead of delivering it. Present only then, together with escrowedAmount, delivered: false, adapterAddress, claimAction: "claim-escrowed-collateral" and a message.A withdrawal that would breach that bound, exceed the posted collateral, or target an inactive loan is refused before a transaction is offered, with
WOULD_BREACH_HEALTH_FACTOR, INSUFFICIENT_COLLATERAL or LOAN_NOT_ACTIVE. The price is only consulted while the loan still carries debt, so a debt-free withdrawal is never refused for a stale price.Where the released tokens end up depends on the market mode.
FREEZE markets unfreeze them in place, so the borrower’s balance does not change but the tokens become transferable again. CUSTODY markets transfer them out of the adapter back to the borrower.curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/withdraw-collateral" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "amount": "100"}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/withdraw-collateral`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, amount: '100' })
});
const { data } = await res.json();
if (data.collateralRelease) {
console.warn(data.warning);
}
const tx = await borrowerWallet.sendTransaction(data.transaction);
await tx.wait();
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: tx.hash })
});
const { data: result } = await confirmed.json();
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "withdrawCollateral",
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/withdraw-collateral",
"field": "txHash",
"body": { "loanId": "5", "txHash": null }
}
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0"
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0",
"escrowed": true,
"escrowedAmount": "100.0",
"delivered": false,
"adapterAddress": "0x3c5e8b1d7f9a2046c8e1b3d5f7a9c0e2d4b6f8a1",
"claimAction": "claim-escrowed-collateral",
"message": "The collateral token refused this release, so the custody adapter holds the amount in escrow instead of delivering it. The holder reclaims it with claim-escrowed-collateral once the restriction is lifted."
}
}
Error Codes
| Code | HTTP | Cause |
|---|---|---|
NO_MARKET_ADDRESS | 400 | The market has no on-chain address recorded |
MISSING_MARKET_ID | 400 | marketId is longer than 100 characters |
VALIDATION_ERROR | 400 | loanId is not a positive integer, or amount is missing on the build call, not a positive decimal string, or carries more decimal places than the collateral token supports |
LOAN_NOT_ACTIVE | 400 | The loan is closed and can no longer be modified |
INSUFFICIENT_COLLATERAL | 400 | amount exceeds the collateral the loan holds |
COLLATERAL_GRANULARITY_MISMATCH | 400 | amount is not a whole multiple of the collateral token’s ERC-1400 granularity, so the token would refuse the transfer. The message names the nearest amounts that are |
WOULD_BREACH_HEALTH_FACTOR | 400 | The remaining collateral would support less than the loan’s outstanding debt at the market’s collateral factor, priced at the signed price when a signedPrice is supplied |
PRICE_STALE | 400 | The loan carries debt and the price is older than the oracle’s staleness window. On an attested market, push a price with Sync Oracle Price or supply a signedPrice. On an external-feed market, wait for the feed to publish |
INVALID_SIGNED_PRICE | 400 | The signedPrice is malformed, expired, dated in the future or older than the oracle’s price age window, or it was signed by a wallet that is not an authorized signer on the market’s oracle, which details.signer then names. Sign a fresh quote with Sign Price |
PRICE_DEVIATION_TOO_LARGE | 400 | The signedPrice moves further from the oracle’s current price than its maxDeviationBps allows |
PRICE_WINDOW_DEVIATION_TOO_LARGE | 400 | The signedPrice falls outside the band the oracle accepts within its current one-hour window |
LOAN_NOT_FOUND | 404 | No loan with that ID exists on this market |
MARKET_NOT_FOUND | 404 | No market with this ID on your instance |
EXTERNAL_FEED_ACTIVE | 409 | A signedPrice was sent to a market that prices from an external feed. Retry without it |
FIXED_PRICE_ACTIVE | 409 | A signedPrice was sent to a market with a fixed price. Retry without it |
SIGNED_PRICE_OUTDATED | 409 | The signedPrice is not dated after the price the oracle already holds, so the oracle would refuse it. Sign a fresh quote |
MARKET_STATE_UNAVAILABLE | 503 | The market could not be read to check its price, or the collateral token’s granularity could not be read. Retry shortly |
LOAN_STATE_UNAVAILABLE | 503 | The loan or the resulting borrow limit could not be read. Retry shortly |
PRICE_STATE_UNAVAILABLE | 503 | The oracle could not be read to check the signedPrice, so nothing was built. Retry shortly |
txHash can also return any transaction verification error.⌘I
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/withdraw-collateral" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "amount": "100"}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/withdraw-collateral`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, amount: '100' })
});
const { data } = await res.json();
if (data.collateralRelease) {
console.warn(data.warning);
}
const tx = await borrowerWallet.sendTransaction(data.transaction);
await tx.wait();
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: tx.hash })
});
const { data: result } = await confirmed.json();
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "withdrawCollateral",
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/withdraw-collateral",
"field": "txHash",
"body": { "loanId": "5", "txHash": null }
}
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0"
}
}
{
"success": true,
"data": {
"txHash": "0x2d6f8b0a4c1e3957b9d1f3a5c7e9b0d2f4a6c8e1b3d5f7a9c2e4b6d8f0a1c3e5",
"loanId": "5",
"amount": "100.0",
"escrowed": true,
"escrowedAmount": "100.0",
"delivered": false,
"adapterAddress": "0x3c5e8b1d7f9a2046c8e1b3d5f7a9c0e2d4b6f8a1",
"claimAction": "claim-escrowed-collateral",
"message": "The collateral token refused this release, so the custody adapter holds the amount in escrow instead of delivering it. The holder reclaims it with claim-escrowed-collateral once the restriction is lifted."
}
}
