# Token holders and transfers

Source: https://docs.settlemint.com/docs/developer-guides/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.

## 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.
* `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_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_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_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_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_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_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_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_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_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_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.

## 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_xxxxxxxxxxxxxxxx" \
  -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.

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.

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

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

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_xxxxxxxxxxxxxxxx" \
  -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. 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. 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_xxxxxxxxxxxxxxxx" \
  -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_xxxxxxxxxxxxxxxx" \
  -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_xxxxxxxxxxxxxxxx" \
  -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_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "userAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "amount": "1000000000000000000"
  }'
```

Freeze and unfreeze operations require the custodian role on the token. 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.

## 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_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_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 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/developer-guides/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.
