Rates and Loan Terms
Extension Action
Confirm, apply or cancel a maturity extension proposal
POST
/
lending-external-securities-v2
/
api
/
markets
/
{marketId}
/
extensions
/
{proposalId}
/
{action}
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/extensions/2/apply" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "loanIds": ["1", "2", "3", "5", "8"] }'
const base = `https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/extensions`;
const headers = { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' };
const build = await fetch(`${base}/${proposalId}/confirm`, { method: 'POST', headers });
const { data: calldata } = await build.json();
const tx = await adminWallet.sendTransaction({
to: calldata.to,
data: calldata.data,
value: calldata.value,
chainId: calldata.chainId
});
await tx.wait();
await fetch(`${base}/confirm-tx`, { method: 'POST', headers, body: JSON.stringify({ txHash: tx.hash }) });
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x..."
},
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"functionName": "applyExtension",
"requiredRole": "ISSUER_OR_ADMIN",
"requiredSigner": null,
"heldByThisInstance": true,
"signerAddress": "0x5ad87a0621175206b72d10e4b8577b192e7f40ab",
"note": null,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/markets/clx_secmarket_001/extensions/confirm-tx",
"field": "txHash"
},
"chainId": 11155111,
"value": "0"
}
}
{
"success": false,
"error": {
"code": "EXTENSION_LOAN_OUT_OF_SCOPE",
"message": "Batch proposal 2 was made when the loan counter stood at 14, so it reaches loans #1 to #14 only. Loan #15 was opened after it: applying the batch to it is a no-op on chain that still costs gas. Propose a new extension that covers it instead.",
"details": { "maxLoanId": "14", "outOfScope": ["15"] }
}
}
{
"success": false,
"error": {
"code": "EXTENSION_EXPIRED",
"message": "Extension proposal 3 expired at 1789600000 (unix seconds): a proposal lapses seven days after it is made unless it is confirmed. Propose it again."
}
}
Builds the transaction for the next step of a maturity extension proposed with Propose Extension. The three actions are signed by different roles.
The API reads the proposal first and refuses what the contract would revert on, with one gap named below. Record each mined step with Confirm Extension Transaction.
action | Contract call | Signed by | What it does |
|---|---|---|---|
confirm | confirmExtension | DEFAULT_ADMIN_ROLE | Applies a LOAN or FLOOR proposal on the spot. Makes a BATCH applicable |
apply | applyExtension | ISSUER_ROLE or DEFAULT_ADMIN_ROLE | Extends one page of up to 200 loans under a confirmed BATCH |
cancel | cancelExtension | ISSUER_ROLE or DEFAULT_ADMIN_ROLE | Withdraws a proposal, confirmed or not, that has not been used up |
Path Parameters
string
required
string
required
Proposal ID on the market, digits only. List Extensions returns it.
string
required
confirm, apply or cancel.Body Parameters
array
On-chain loan IDs for one
apply page, each a positive integer or a string of digits. Required by apply, 1 to 200 entries. Ignored by confirm and cancel.Confirm Within Seven Days
A proposal lapses if the admin does not confirm it within seven days of being made, and a lapsed proposal is refused withEXTENSION_EXPIRED. Propose it again. The API also re-checks that the proposed date still pushes the loan’s maturity or the floor out.
The API does not re-check that a LOAN proposal’s loan is still open. A loan repaid or liquidated since the proposal makes the confirmation revert on-chain, so read it with Get Loan before the admin signs.
A confirmed BATCH never lapses. It stays applicable until it is cancelled.
Apply a Batch in Pages
Send the loan IDs in pages of up to 200. Re-running a page is harmless. A loan that is closed, carries no maturity, or already matures on or after the proposed date is skipped on-chain rather than reverting the page. A batch reaches only the loans that existed when it was proposed,1 to its maxLoanId. A page naming a later loan is refused with EXTENSION_LOAN_OUT_OF_SCOPE, and error.details lists maxLoanId and the IDs outOfScope. Propose a new extension for those loans.
Switched Off
With the market’s extension switch off,confirm and apply are refused with EXTENSION_NOT_ALLOWED. cancel still works, so a proposal can always be withdrawn.
Response Fields
The calldata response carries the transaction twice.transaction holds to and data. The same to and data also sit directly on data, and that is where the chainId and value binding lands, so sign from those four fields.
object
Show child attributes
Show child attributes
string
SIGN_TRANSACTION.object
{ to, data } for the call on the market.string
The market address.
string
The encoded call.
string
Always
"0".integer
The chain your instance resolves to.
string
confirmExtension, applyExtension or cancelExtension.string
DEFAULT_ADMIN_ROLE for confirm, ISSUER_OR_ADMIN for apply and cancel.string
Plain-language summary of the step.
string
Always
null here: the signer is whoever holds the role, not one fixed address.boolean
true when a verified wallet of your instance holds the role, or either role for ISSUER_OR_ADMIN. false when none does, null when the check could not be completed. The build is never refused on it.string
The wallet of your instance that holds the role, or
null.string
What to do when your instance cannot sign: the wallet the lender of record placed the role with must sign, because the market reverts the call from any other wallet.
null when your instance can sign.object
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/extensions/2/apply" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "loanIds": ["1", "2", "3", "5", "8"] }'
const base = `https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/extensions`;
const headers = { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' };
const build = await fetch(`${base}/${proposalId}/confirm`, { method: 'POST', headers });
const { data: calldata } = await build.json();
const tx = await adminWallet.sendTransaction({
to: calldata.to,
data: calldata.data,
value: calldata.value,
chainId: calldata.chainId
});
await tx.wait();
await fetch(`${base}/confirm-tx`, { method: 'POST', headers, body: JSON.stringify({ txHash: tx.hash }) });
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x..."
},
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"functionName": "applyExtension",
"requiredRole": "ISSUER_OR_ADMIN",
"requiredSigner": null,
"heldByThisInstance": true,
"signerAddress": "0x5ad87a0621175206b72d10e4b8577b192e7f40ab",
"note": null,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/markets/clx_secmarket_001/extensions/confirm-tx",
"field": "txHash"
},
"chainId": 11155111,
"value": "0"
}
}
{
"success": false,
"error": {
"code": "EXTENSION_LOAN_OUT_OF_SCOPE",
"message": "Batch proposal 2 was made when the loan counter stood at 14, so it reaches loans #1 to #14 only. Loan #15 was opened after it: applying the batch to it is a no-op on chain that still costs gas. Propose a new extension that covers it instead.",
"details": { "maxLoanId": "14", "outOfScope": ["15"] }
}
}
{
"success": false,
"error": {
"code": "EXTENSION_EXPIRED",
"message": "Extension proposal 3 expired at 1789600000 (unix seconds): a proposal lapses seven days after it is made unless it is confirmed. Propose it again."
}
}
Error Codes
| Code | HTTP | Cause |
|---|---|---|
MISSING_MARKET_ID | 400 | The marketId path segment is longer than 100 characters |
VALIDATION_ERROR | 400 | proposalId is not a string of digits, or loanIds is malformed or longer than 200 |
INVALID_EXTENSION | 400 | apply was called without loanIds |
EXTENSION_LOAN_OUT_OF_SCOPE | 400 | An apply page names a loan opened after the batch was proposed. error.details carries maxLoanId and outOfScope |
EXTENSION_NOT_FOUND | 404 | No proposal with this ID exists on the market |
MARKET_NOT_FOUND | 404 | No market with this ID on your instance |
EXTENSION_NOT_ALLOWED | 409 | The extension switch is off. Applies to confirm and apply |
EXTENSION_CONSUMED | 409 | The proposal was already applied or cancelled |
EXTENSION_ALREADY_CONFIRMED | 409 | confirm on a BATCH that is already confirmed. Apply it in pages instead |
EXTENSION_EXPIRED | 409 | confirm after the proposal’s seven days ran out |
LOAN_NOT_ACTIVE | 409 | confirm on a LOAN proposal whose loan carries no maturity |
INVALID_EXTENSION | 409 | confirm on a LOAN or FLOOR proposal that would no longer push the maturity or the floor out |
EXTENSION_NOT_BATCH | 409 | apply on a LOAN or FLOOR proposal, which applies on confirmation |
EXTENSION_NOT_CONFIRMED | 409 | apply on a BATCH the admin has not confirmed |
MARKET_PENDING_CURATOR | 409 | The market has no lender of record yet, so nobody holds the roles these steps require |
MARKET_STATE_UNAVAILABLE | 503 | The proposal, the term configuration or the loan’s term could not be read. Retry shortly |
⌘I
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/extensions/2/apply" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "loanIds": ["1", "2", "3", "5", "8"] }'
const base = `https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/extensions`;
const headers = { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' };
const build = await fetch(`${base}/${proposalId}/confirm`, { method: 'POST', headers });
const { data: calldata } = await build.json();
const tx = await adminWallet.sendTransaction({
to: calldata.to,
data: calldata.data,
value: calldata.value,
chainId: calldata.chainId
});
await tx.wait();
await fetch(`${base}/confirm-tx`, { method: 'POST', headers, body: JSON.stringify({ txHash: tx.hash }) });
{
"success": true,
"data": {
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x..."
},
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"functionName": "applyExtension",
"requiredRole": "ISSUER_OR_ADMIN",
"requiredSigner": null,
"heldByThisInstance": true,
"signerAddress": "0x5ad87a0621175206b72d10e4b8577b192e7f40ab",
"note": null,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/markets/clx_secmarket_001/extensions/confirm-tx",
"field": "txHash"
},
"chainId": 11155111,
"value": "0"
}
}
{
"success": false,
"error": {
"code": "EXTENSION_LOAN_OUT_OF_SCOPE",
"message": "Batch proposal 2 was made when the loan counter stood at 14, so it reaches loans #1 to #14 only. Loan #15 was opened after it: applying the batch to it is a no-op on chain that still costs gas. Propose a new extension that covers it instead.",
"details": { "maxLoanId": "14", "outOfScope": ["15"] }
}
}
{
"success": false,
"error": {
"code": "EXTENSION_EXPIRED",
"message": "Extension proposal 3 expired at 1789600000 (unix seconds): a proposal lapses seven days after it is made unless it is confirmed. Propose it again."
}
}
