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

# Extension Action

> Confirm, apply or cancel a maturity extension proposal

Builds the transaction for the next step of a maturity extension proposed with [Propose Extension](/endpoints/lending/propose-extension). The three actions are signed by different roles.

| `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            |

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](/endpoints/lending/confirm-extension-transaction).

## Path Parameters

<ParamField path="marketId" type="string" required>Market ID.</ParamField>

<ParamField path="proposalId" type="string" required>Proposal ID on the market, digits only. [List Extensions](/endpoints/lending/list-extensions) returns it.</ParamField>

<ParamField path="action" type="string" required>`confirm`, `apply` or `cancel`.</ParamField>

## Body Parameters

<ParamField body="loanIds" type="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`.
</ParamField>

## 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 with `EXTENSION_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](/endpoints/lending/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.

<ResponseField name="data" type="object">
  <Expandable>
    <ResponseField name="action" type="string">`SIGN_TRANSACTION`.</ResponseField>
    <ResponseField name="transaction" type="object">`{ to, data }` for the call on the market.</ResponseField>
    <ResponseField name="to" type="string">The market address.</ResponseField>
    <ResponseField name="data" type="string">The encoded call.</ResponseField>
    <ResponseField name="value" type="string">Always `"0"`.</ResponseField>
    <ResponseField name="chainId" type="integer">The chain your instance resolves to.</ResponseField>
    <ResponseField name="functionName" type="string">`confirmExtension`, `applyExtension` or `cancelExtension`.</ResponseField>
    <ResponseField name="requiredRole" type="string">`DEFAULT_ADMIN_ROLE` for `confirm`, `ISSUER_OR_ADMIN` for `apply` and `cancel`.</ResponseField>
    <ResponseField name="description" type="string">Plain-language summary of the step.</ResponseField>
    <ResponseField name="requiredSigner" type="string">Always `null` here: the signer is whoever holds the role, not one fixed address.</ResponseField>
    <ResponseField name="heldByThisInstance" type="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.</ResponseField>
    <ResponseField name="signerAddress" type="string">The wallet of your instance that holds the role, or `null`.</ResponseField>
    <ResponseField name="note" type="string">What to do when your instance cannot sign: the wallet the lender of record placed the role with must sign, because any other wallet reverts `NotAuthorized`. `null` when your instance can sign.</ResponseField>
    <ResponseField name="confirmWith" type="object">`{ endpoint, field }`, pointing at [Confirm Extension Transaction](/endpoints/lending/confirm-extension-transaction) with the field `txHash`.</ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  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"] }'
  ```

  ```typescript TypeScript theme={null}
  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 }) });
  ```
</RequestExample>

<ResponseExample>
  ```json Calldata Response theme={null}
  {
    "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"
    }
  }
  ```

  ```json Error - Out of Scope theme={null}
  {
    "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"] }
    }
  }
  ```

  ```json Error - Expired theme={null}
  {
    "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."
    }
  }
  ```
</ResponseExample>

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