# Token holders and transfers

Source: https://docs.settlemint.com/docs/developers/api-integration/token-holders-transfers
Query token holders, inspect balances, execute transfers, and understand the controls around standard, allowance-based, forced, and pre-approved transfer workflows.



DALP exposes holder and transfer APIs for day-two asset operations after a token
is live. Use them to reconcile holder balances, execute transfers, inspect
allowances, and operate governed exception workflows such as forced transfers or
pre-approved transfers.

These APIs do not bypass asset controls. Transfers still execute against the
asset's configured identity, compliance, freeze, role, allowance, and approval
rules. Amount fields use the token's smallest units, sent as decimal strings.

## Choose the transfer path [#choose-the-transfer-path]

Pick the narrowest operation that matches the business instruction before you
queue a mutation. That keeps the request shape, role requirement, and retry
record clear.

* Use a standard transfer to move the authenticated signer's balance to one or
  more recipients. Send `transferType: "standard"`, with one to 10,000
  `recipient` and `amount` items, and omit `from`.
* Use `transferFrom` when the authenticated signer spends another holder's
  allowance. Send `transferType: "transferFrom"` with one item that includes
  `from`, `recipient`, and `amount`.
* Use a forced transfer only for an approved exception workflow. Send one to
  10,000 `from`, `recipient`, and `amount` items; the caller still needs the
  required token role and the contract-side forced-transfer controls still
  apply.
* Use transfer approvals when the asset requires an explicit from-to approval
  before execution. Create the approval with `fromWallet`, `toWallet`, `amount`,
  and optional identity overrides, then revoke it through the revocation endpoint
  if it is no longer valid.

Use an `Idempotency-Key` header on transfer and forced-transfer mutations when
your integration may retry after a timeout. Reuse the same key only for the same
business instruction.

## Endpoint summary [#endpoint-summary]

The token API exposes the main holder and transfer operations:

* `GET /api/v2/tokens/{tokenAddress}/holders` lists holder balances for a token.
* `GET /api/v2/tokens/{tokenAddress}/holder-balances` reads one holder balance.
* `GET /api/v2/tokens/{tokenAddress}/events` lists indexed token events.
* `GET /api/v2/tokens/{tokenAddress}/historical-balances` lists indexed balance
  checkpoints for tokens with the historical balances feature attached.
* `GET /api/v2/tokens/{tokenAddress}/permit-info` reads EIP-2612 permit
  metadata for tokens with the permit feature attached.
* `POST /api/v2/tokens/{tokenAddress}/permits` relays an EIP-2612 permit
  signature through the transaction queue.
* `POST /api/v2/tokens/{tokenAddress}/transfers` executes standard or
  allowance-based transfers.
* `POST /api/v2/tokens/{tokenAddress}/burns` burns tokens from one or more
  holder addresses.
* `POST /api/v2/tokens/{tokenAddress}/forced-transfers` executes custodian forced
  transfers.
* `PUT /api/v2/tokens/{tokenAddress}/address-freezes` sets or clears an address
  freeze.
* `POST /api/v2/tokens/{tokenAddress}/partial-freezes` freezes part of a
  holder balance.
* `POST /api/v2/tokens/{tokenAddress}/partial-unfreezes` releases part of a
  frozen holder balance.
* `POST /api/v2/tokens/{tokenAddress}/recoveries` recovers tokens from a lost
  wallet to the caller's wallet.
* `POST /api/v2/tokens/{tokenAddress}/forced-recoveries` recovers tokens from a
  lost wallet to a specified replacement wallet.
* `GET /api/v2/tokens/{tokenAddress}/transfer-approvals` lists transfer approval
  records.
* `POST /api/v2/tokens/{tokenAddress}/transfer-approvals` creates a pre-approved
  from-to transfer approval.
* `POST /api/v2/tokens/{tokenAddress}/transfer-approval-revocations` revokes a
  transfer approval.

Older endpoints also exist for legacy integrations, including
`/api/token/{tokenAddress}/holders` and `/api/token/{tokenAddress}/holder`.
Use the `/api/v2/tokens/...` endpoints for new integrations because they use
path-based token addresses and collection-style pagination.

## List token holders [#list-token-holders]

Use the holders endpoint to power cap-table style views, reconciliation jobs, and
post-operation checks.

```bash
curl "https://your-platform.example.com/api/v2/tokens/0xTOKEN/holders?limit=50&sortBy=-lastUpdatedAt" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Holder collection items include:

* `account.id`: the holder wallet address
* `value`: the indexed token balance
* `frozen`: the frozen amount in the holder balance
* `available`: the spendable balance after frozen amounts and address-level freezes are applied
* `isFrozen`: whether the holder address is frozen
* `lastUpdatedAt`: the indexed balance update time

The collection supports pagination, filtering, and sorting. `lastUpdatedAt` is
the default sort field, descending. You can filter by holder address, update
time, and frozen-address state.

## Read one holder balance [#read-one-holder-balance]

Use the holder-balance endpoint when you need to verify one address before or
after an operation.

```bash
curl "https://your-platform.example.com/api/v2/tokens/0xTOKEN/holder-balances?holderAddress=0xHOLDER" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

The response contains the same holder balance fields as the holders collection,
wrapped in `data.holder`. If the address has no positive indexed balance, the
holder field can be `null`.

## List token events [#list-token-events]

Use the token events endpoint to read indexed on-chain events for one token.

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?filter[walletAddress]=0xHOLDER" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

The response uses the canonical collection envelope:

* `data`: event items
* `meta`: total count and facet counts
* `links`: pagination links for the current query

The default sort is `-blockTimestamp`, so the newest events are returned first.
Supported sortable fields are `blockTimestamp` and `blockNumber`.

Supported filters are:

* `eventName`
* `senderAddress`
* `accountAddress`
* `walletAddress`, which matches `senderAddress`, `accountAddress`, or the event
  emitter address; supports only `eq` and `inArray`
* `transactionHash`, which uses case-insensitive substring matching by default; use `eq` for exact matches
* `blockTimestamp` date range

The token address in the path still scopes the result set. A `walletAddress`
filter only narrows events for that token. It does not return activity from other
tokens, even when the same wallet or feature contract address appears there.

Wallet address filters must use the supported operator format:

```bash
filter[walletAddress][eq]=0xHOLDER
filter[walletAddress][inArray]=0xHOLDER1,0xHOLDER2
```

Transaction hash shorthand uses substring matching. For an exact transaction hash match, use:

```bash
filter[transactionHash][eq]=0xTRANSACTION_HASH
```

Filter by event name:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?filter[eventName]=TransferCompleted" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Filter by sender address:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?filter[senderAddress]=0xSENDER" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Filter by transaction hash:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?filter[transactionHash][eq]=0xTRANSACTION_HASH" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Filter by block timestamp range:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?filter[blockTimestamp][gte]=2026-01-01T00:00:00Z&filter[blockTimestamp][lte]=2026-01-31T23:59:59Z" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Paginate through the token events collection:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/events?page[offset]=50&page[limit]=50&sort=-blockTimestamp" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

## List historical balance checkpoints [#list-historical-balance-checkpoints]

Use the historical balances endpoint when you need a block-by-block balance trail
for a token that has the historical balances feature attached. The endpoint
returns account checkpoint rows by default. Total-supply checkpoints are available
with the `kind` filter.

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/historical-balances?filter[account][eq]=0xHOLDER&sort=-blockNumber" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

Historical balance items include:

* `account`: the holder address for account checkpoints, or the zero address for total-supply checkpoints
* `kind`: `account` or `totalSupply`
* `sender`: the address that triggered the checkpoint
* `oldBalance` and `newBalance`: display balance strings
* `oldBalanceExact` and `newBalanceExact`: exact smallest-unit values for filtering and reconciliation
* `blockNumber`, `blockTimestamp`, `txHash`, and `logIndex`: chain position fields for ordering and replay

The endpoint uses the canonical collection envelope with `data`, `meta`, and
`links`. The default sort is newest block first. Supported filters include
`account`, `kind`, `blockNumber`, `blockTimestamp`, `oldBalance`, and
`newBalance`. Use equality filters for the checkpoint discriminators:
`filter[account][eq]=0xHOLDER` and `filter[kind][eq]=totalSupply` are the only
operator forms for `account` and `kind`. Balance filters use exact smallest-unit
values, while the response also includes display balance strings.

To include total-supply checkpoints, filter by `kind`:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/historical-balances?filter[kind][eq]=totalSupply" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

If the token does not have the historical balances feature attached, the endpoint
returns an empty collection envelope.

## Read account activity [#read-account-activity]

Use account activity endpoints when you need the event history or activity metrics
for one address in the active system:

* `GET /api/v2/system/accounts/{accountAddress}/activities` lists indexed events
  where the address is involved.
* `GET /api/v2/system/accounts/{accountAddress}/activity-metrics` returns the
  activity time series and count for the address.

Account activity reads are visibility-scoped. The API returns activity for the
caller's own wallet set, participant wallet or identity targets that the caller's
role can inspect, active-system feed addresses, and configured account-abstraction
infrastructure addresses that the caller's role can inspect. Requests for other
arbitrary addresses return an empty collection or zero-count metrics instead of
exposing unrelated activity.

## Read permit metadata [#read-permit-metadata]

Use the permit-info endpoint when an integration needs the EIP-2612 domain data
and current holder nonce before collecting or relaying a permit signature. The
token must be indexed in the caller's tenant scope. If the token has no attached
permit feature, `data` is `null`.

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/permit-info?owner=0xHOLDER" \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx"
```

```json
{
  "data": {
    "featureAddress": "0x00000000000000000000000000000000000000f3",
    "owner": "0xabcdef0000000000000000000000000000000001",
    "nonce": "12",
    "domainSeparator": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "permitTypeHash": "0x6e71edae12b1b97f4d1f60370fef10178563ef29188d1232f565f79b8f6aad8c"
  },
  "links": {
    "self": "/v2/tokens/0xTOKEN/permit-info"
  }
}
```

Response fields are:

* `featureAddress`: the attached permit feature contract address that was read
* `owner`: the queried holder address, or `null` when no `owner` query parameter
  was supplied
* `nonce`: the current permit nonce for the queried holder, or `null` when no
  owner was supplied
* `domainSeparator`: the EIP-712 domain separator reported by the permit feature
* `permitTypeHash`: the EIP-712 Permit struct typehash used by the feature

When you only need domain metadata, omit `owner`. The endpoint then reads the
domain separator without reading a holder nonce. If live permit metadata cannot
be read from the attached feature, the API returns a token feature availability
error instead of returning stale nonce data.

## Relay a permit signature [#relay-a-permit-signature]

Use the permits endpoint after a holder signs an EIP-2612 Permit message. The
signature authorizes `spender` for `value` in the token's smallest units. The API
caller supplies the transaction-queue sender wallet that submits the permit call
to the attached permit feature.

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/permits \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "owner": "0xabcdef0000000000000000000000000000000001",
    "spender": "0xabcdef0000000000000000000000000000000002",
    "value": "1000000000000000000",
    "deadline": "1767225600",
    "v": 27,
    "r": "0x1111111111111111111111111111111111111111111111111111111111111111",
    "s": "0x2222222222222222222222222222222222222222222222222222222222222222"
  }'
```

Permit inputs are:

| Field      | Description                                          |
| ---------- | ---------------------------------------------------- |
| `owner`    | Holder address that signed the permit.               |
| `spender`  | Address approved to spend the holder's tokens.       |
| `value`    | Approved token amount in the token's smallest units. |
| `deadline` | Signature deadline as a Unix timestamp in seconds.   |
| `v`        | ECDSA recovery id. The value must be `27` or `28`.   |
| `r`        | ECDSA signature `r` value as a 32-byte hex string.   |
| `s`        | ECDSA signature `s` value as a 32-byte hex string.   |

The token must have the permit feature attached. The holder signature authorizes
the allowance, so the relay caller does not need a token role such as governance,
supply management, or custody. The caller still needs authenticated API access to
the token in its tenant scope, a verified sender wallet, and the normal
transaction-queue path for submitting the permit call. After the queue accepts
the call, the response follows the same queued-operation envelope used by other
token feature mutations.

## Execute standard transfers [#execute-standard-transfers]

Use standard transfers when the authenticated signer is moving its own balance to
one or more recipients.

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/transfers \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: transfer-northwind-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "transferType": "standard",
    "transfers": [
      {
        "recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
        "amount": "1000000000000000000"
      }
    ]
  }'
```

Each request accepts between 1 and 10,000 transfer items. For standard transfers,
do not include `from` addresses.

A transfer response uses the standard blockchain mutation envelope. Store the
returned transaction hash or queued action status with your workflow record, then
verify the final chain result through transaction tracking.

Before the API queues a standard transfer, it checks the sender's indexed
available balance for the total requested amount. Available balance excludes
frozen amounts and returns zero for frozen holder addresses. If the token metadata
or latest holder state is not indexed yet, the pre-check can also see zero
available balance. If the pre-check rejects the request, reduce the amount or
wait for recent token, balance, or freeze changes to index before retrying.

## Standard transfer batching [#standard-transfer-batching]

Standard transfer batches use the token's `batchTransfer` path when the request
contains more than one transfer item. Requests with up to 10 transfer items are
queued as one on-chain batch transaction. Larger requests enter the durable batch
execution path, which estimates a safe chunk size, processes chunks sequentially,
and records the resulting transaction hashes.

This changes the operational boundary by batch size:

| Request shape                         | Execution behavior              | Operational note                                                                                           |
| ------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| 1 standard transfer                   | One `transfer` transaction      | Use for a single sender-to-recipient move.                                                                 |
| 2 to 10 standard transfers            | One `batchTransfer` transaction | The batch succeeds or reverts as one transaction.                                                          |
| 11 to 10,000 standard transfers       | Chunked durable batch execution | Reconcile all returned transaction hashes. If a later chunk fails, earlier chunks may already be on-chain. |
| Any multi-item `transferFrom` request | Rejected before execution       | Submit one `transferFrom` request per allowance-based transfer.                                            |

Use one `Idempotency-Key` per business instruction. For large standard batches,
retry the same request with the same key after a timeout so the platform can
reattach to the accepted queued operation instead of accepting a duplicate batch.

## Execute allowance-based transfers [#execute-allowance-based-transfers]

Use `transferFrom` when the operation spends from another address using an
allowance.

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/transfers \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "transferType": "transferFrom",
    "transfers": [
      {
        "from": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
        "recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
        "amount": "1000000000000000000"
      }
    ]
  }'
```

For `transferFrom`, every transfer item must include a `from` address. A
`transferFrom` request supports one transfer item only. If you need to spend from
multiple allowance sources or send to multiple recipients, send one request per
`transferFrom` operation and reconcile each queued transaction separately. Do not
build retry logic around a multi-item `transferFrom` request because DALP rejects
or fails it before queueing the allowance-based batch.

## Burn holder balances [#burn-holder-balances]

Use burns when an authorized operator needs to remove tokens from one or more
holder addresses. The token must support burning, and the signer must have the
required token role.

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/burns \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "addresses": ["0x8ba1f109551bD432803012645Ac136ddd64DBA72"],
    "amounts": ["1000000000000000000"]
  }'
```

`addresses` and `amounts` must have the same number of items. A burn request can
include up to 100 holder addresses. Amounts use the token's raw base units.
When the request contains more than one holder address, DALP queues one
`batchBurn` transaction for the matching address and amount arrays.

Before the API queues a burn, it checks each holder's indexed available balance
against the requested amount for that holder. Available balance excludes frozen
amounts and returns zero for frozen holder addresses. If token metadata or holder
state is not indexed yet, the pre-check can see zero available balance. If a
pre-check fails, adjust the burn amount or retry after the indexer reflects the
latest token, balance, and freeze state.

## Execute forced transfers [#execute-forced-transfers]

Forced transfers are governed exception operations. Use them only when the
institution has the proper operating basis and the signer has the required asset
role.

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/forced-transfers \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: forced-transfer-case-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "transfers": [
      {
        "from": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
        "recipient": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
        "amount": "1000000000000000000"
      }
    ]
  }'
```

Forced transfers require matching `from`, `recipient`, and `amount` values for
each transfer item. A single request can include up to 10,000 transfer items.
Store the business reason, approval evidence, and resulting transaction hash
outside the API call as part of your exception workflow.

## Freeze holder addresses and balances [#freeze-holder-addresses-and-balances]

Use freeze operations when a custodian needs to stop or limit transfers for a
specific holder address. The token must support custodian operations, and the
signer must have the custodian role on that token. Address freezes set or clear
the holder-level freeze flag. Partial freezes lock a positive amount of one
holder's balance, and partial unfreezes release a positive amount that was
previously frozen.

Set an address freeze:

```bash
curl -X PUT https://your-platform.example.com/api/v2/tokens/0xTOKEN/address-freezes \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: freeze-address-case-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "userAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "freeze": true
  }'
```

Clear an address freeze by sending the same holder address with `freeze` set to
`false`:

```bash
curl -X PUT https://your-platform.example.com/api/v2/tokens/0xTOKEN/address-freezes \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: clear-address-freeze-case-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "userAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "freeze": false
  }'
```

The CLI `dalp tokens freeze-address` command sets the address freeze flag. Use the
API example above to clear the holder-level flag; partial unfreezes remain a
separate operation for releasing a frozen balance amount.

Freeze part of a holder balance:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/partial-freezes \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: partial-freeze-case-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "userAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "amount": "1000000000000000000"
  }'
```

When the indexer already has a token-holder balance row, the partial-freeze API
checks the holder's indexed available balance before queueing the transaction.
Available balance excludes amounts that are already frozen. If that pre-check
rejects the request, reduce the amount or wait for the indexer to reflect a
recent transfer before retrying. If the holder row is not indexed yet, the API
can still queue the transaction and the on-chain freeze call enforces the
balance constraint.

Release part of a frozen balance:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/partial-unfreezes \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: partial-unfreeze-case-2026-01-15-001" \
  -H "Content-Type: application/json" \
  -d '{
    "userAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "amount": "1000000000000000000"
  }'
```

Freeze and unfreeze operations require the custodian role on a token that
supports custodian operations. Use a stable `Idempotency-Key` for each freeze,
clear-freeze, partial-freeze, or partial-unfreeze instruction that your
integration may retry. Reuse the same key only when the method, path, and request
body are the same. Use the holder and event endpoints before and after the
mutation when your operating process requires evidence of the affected address,
amount, and resulting transaction.

Freeze operations are single-holder operations. To freeze several holder
addresses, send one address-freeze request per holder and reconcile each queued
transaction. The freeze is enforced after the transaction confirms on-chain and
the token or holder state is read back. Until then, treat the operation as queued
or pending in your operating workflow.

If a batch transfer must stop because one transfer item is on compliance hold,
do not include that item in a standard transfer batch. Use the TransferApproval
workflow when the asset requires pre-approval, or freeze the affected holder
address or balance before submitting transfers that must not move. Keep the hold
reason, approval evidence, and release decision outside the API call in your
compliance record.

## Recover tokens from a lost wallet [#recover-tokens-from-a-lost-wallet]

Use recovery operations when a holder has lost access to a wallet and the
institution's recovery process has approved a replacement path.

Recover tokens from a lost wallet to the caller's wallet:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/recoveries \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "lostWallet": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
  }'
```

Force recover tokens from a lost wallet to a specified replacement wallet:

```bash
curl -X POST https://your-platform.example.com/api/v2/tokens/0xTOKEN/forced-recoveries \
  -H "X-Api-Key: sm_dalp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "lostWallet": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
    "newWallet": "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
  }'
```

Standard recovery requires the emergency role. Forced recovery requires the
custodian role because it specifies both the lost wallet and the replacement
wallet. Store the recovery approval, identity evidence, and transaction outcome
in your operating record.

## Transfer approval workflows [#transfer-approval-workflows]

For assets that use the TransferApproval compliance module, an approval authority
can pre-approve transfers from one identity to another for a specific approved
amount. The token's configured approval mode determines how that approved amount
can be consumed.

Use these operations when your transfer process requires explicit maker-checker
style approval before a holder initiates the transfer:

1. Configure the token's TransferApproval module with the correct parameter schema for the installed module. Use either exemption/one-time-use settings or approval-mode settings; do not mix them.
2. Create the transfer approval for the source identity, recipient identity, and approved amount.
3. Let the holder initiate a transfer that fits the configured approval mode.
4. List transfer approvals to inspect pending, consumed, or revoked approvals.
5. Revoke stale approvals that should no longer execute. Any configured approval authority can revoke a pending approval.

Approval and revocation requests use the same request-body shape:

* `fromWallet`: wallet address of the source holder whose identity is approved
  to send.
* `toWallet`: wallet address of the recipient holder whose identity is approved
  to receive.
* `amount`: approved token amount in base units, sent as a decimal string
  greater than zero.
* `fromIdentityAddress` and `toIdentityAddress`: optional identity contract
  address overrides. Provide both together or omit both. Use them when your
  workflow already stores the identity addresses from the approvals list and you
  want DALP to use those identities directly instead of resolving the wallets
  through the identity registry.

Approval modes apply when the installed TransferApproval module uses approval-mode settings. They determine how the approved amount can be consumed:

* `0`: exact amount: one transfer must match the approved value exactly. After
  that transfer succeeds, the approval is used and cannot be reused.
* `1`: up to once: one transfer can use any amount up to the approved value.
  Transferring less than the approved value still uses the approval.
* `2`: up to total: multiple transfers can spend against the approval until the
  approved total is exhausted. Further transfers require a new approval or a
  higher approved amount.

Expiry still applies in every mode. Approval expiry is configured in seconds,
from 1 to 31,536,000 seconds. If the approval expires before it is consumed,
create a new approval instead of retrying the stale one.

To update an approval, treat the change as a revoke-and-recreate operation:

1. List the approval and confirm it is still `pending`.
2. Revoke the stale approval using the same token, source identity, recipient
   identity, and amount. The API accepts wallet addresses, or identity address
   overrides when the identities are already known from the approvals list.
3. Create a new approval with the corrected amount or operating evidence.
4. Re-read the approvals list and store the new approval status in your
   workflow record.

The approval mode is fixed after the module is configured. To change from exact
amount to an up-to mode, or between up-to modes, deploy a new TransferApproval
module with the desired mode.

## Controls and failure handling [#controls-and-failure-handling]

For standard transfers, `transferFrom`, and burns, DALP checks indexed available
balances before it submits the on-chain transaction. Available balance excludes
frozen amounts. If the requested amount is higher than the available balance, the
API returns an error before the transaction is queued.

The same pre-queue check normalizes the token and holder addresses. Send valid
Ethereum addresses in `0x` format for the token, source holder, and burn holder
fields; malformed addresses fail before DALP submits the operation.

DALP also rejects transfer or burn requests while the token is paused for that
operation. Fix the address, holder balance, frozen amount, or paused token state
before retrying.

Transfer and burn mutations may fail when DALP or the underlying contracts reject
the operation. Common categories include:

* missing or insufficient role permissions
* paused token state
* frozen sender, recipient, or balance state
* failed identity or compliance checks
* missing allowance for `transferFrom`
* stale, revoked, or missing transfer approval
* insufficient available balance after frozen amounts are excluded

When a transfer or burn fails for insufficient available balance, re-read the
holder balance and token events before retrying. The indexed balance may still be
catching up after a recent operation, or another operation may have spent or
frozen part of the balance since your last read.

When a transfer mutation returns a blockchain transaction hash, use the
transaction-tracking guide to verify confirmation and recover from timeout cases.
See [Transaction tracking](/docs/developers/operations/transaction-tracking).

## Operational guidance [#operational-guidance]

For regulated operations, treat holder and transfer APIs as part of the evidence
chain:

1. Read holder state before the operation when the workflow requires a balance
   check.
2. Execute the transfer using the narrowest operation that fits the case:
   standard, allowance-based, forced, or pre-approved.
3. Capture the transaction hash or action status.
4. Read holder state again after confirmation.
5. Attach approvals, exception reasons, or reconciliation notes to your operating
   record outside DALP when required by policy.

This separates execution from governance evidence while keeping the on-chain
operation enforceable by the asset's configured controls.

## Related primitives [#related-primitives]

* Use [Transaction tracking](/docs/developers/operations/transaction-tracking) to follow queued transfers after DALP returns a transaction hash or action status.
* Use [Token lifecycle](/docs/developers/api-integration/token-lifecycle) to create and operate the token before you run holder and transfer operations.
* Use [Compliance modules](/docs/developers/api-integration/compliance-modules) to understand the identity and compliance controls that transfers must satisfy.
