Token holders and 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
The token API exposes the main holder and transfer operations:
GET /api/v2/tokens/{tokenAddress}/holderslists holder balances for a token.GET /api/v2/tokens/{tokenAddress}/holder-balancesreads one holder balance.GET /api/v2/tokens/{tokenAddress}/eventslists indexed token events.GET /api/v2/tokens/{tokenAddress}/historical-balanceslists indexed balance checkpoints for tokens with the historical balances feature attached.POST /api/v2/tokens/{tokenAddress}/transfersexecutes standard or allowance-based transfers.POST /api/v2/tokens/{tokenAddress}/burnsburns tokens from one or more holder addresses.POST /api/v2/tokens/{tokenAddress}/forced-transfersexecutes custodian forced transfers.PUT /api/v2/tokens/{tokenAddress}/address-freezessets or clears an address freeze.POST /api/v2/tokens/{tokenAddress}/partial-freezesfreezes part of a holder balance.POST /api/v2/tokens/{tokenAddress}/partial-unfreezesreleases part of a frozen holder balance.POST /api/v2/tokens/{tokenAddress}/recoveriesrecovers tokens from a lost wallet to the caller's wallet.POST /api/v2/tokens/{tokenAddress}/forced-recoveriesrecovers tokens from a lost wallet to a specified replacement wallet.GET /api/v2/tokens/{tokenAddress}/transfer-approvalslists transfer approval records.POST /api/v2/tokens/{tokenAddress}/transfer-approvalscreates a pre-approved from-to transfer approval.POST /api/v2/tokens/{tokenAddress}/transfer-approval-revocationsrevokes 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
Use the holders endpoint to power cap-table style views, reconciliation jobs, and post-operation checks.
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 addressvalue: the indexed token balancefrozen: the frozen amount in the holder balanceavailable: the spendable balance after frozen amounts and address-level freezes are appliedisFrozen: whether the holder address is frozenlastUpdatedAt: 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
Use the holder-balance endpoint when you need to verify one address before or after an operation.
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
Use the token events endpoint to read indexed on-chain events for one token.
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 itemsmeta: total count and facet countslinks: 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:
eventNamesenderAddressaccountAddresswalletAddress, which matchessenderAddress,accountAddress, or the event emitter address; supports onlyeqandinArraytransactionHash, which uses case-insensitive substring matching by default; useeqfor exact matchesblockTimestampdate 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:
filter[walletAddress][eq]=0xHOLDER
filter[walletAddress][inArray]=0xHOLDER1,0xHOLDER2Transaction hash shorthand uses substring matching. For an exact transaction hash match, use:
filter[transactionHash][eq]=0xTRANSACTION_HASHFilter by event name:
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:
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:
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:
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:
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
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.
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 checkpointskind:accountortotalSupplysender: the address that triggered the checkpointoldBalanceandnewBalance: display balance stringsoldBalanceExactandnewBalanceExact: exact smallest-unit values for filtering and reconciliationblockNumber,blockTimestamp,txHash, andlogIndex: 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:
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
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}/activitieslists indexed events where the address is involved.GET /api/v2/system/accounts/{accountAddress}/activity-metricsreturns 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
Use standard transfers when the authenticated signer is moving its own balance to one or more recipients.
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
Use transferFrom when the operation spends from another address using an
allowance.
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
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.
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
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.
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
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:
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:
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:
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:
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
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:
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:
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
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:
- 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.
- Create the transfer approval for the source identity, recipient identity, and approved amount.
- Let the holder initiate a transfer that fits the configured approval mode.
- List transfer approvals to inspect pending, consumed, or revoked approvals.
- 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:
- List the approval and confirm it is still
pending. - 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.
- Create a new approval with the corrected amount or operating evidence.
- 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
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.
Operational guidance
For regulated operations, treat holder and transfer APIs as part of the evidence chain:
- Read holder state before the operation when the workflow requires a balance check.
- Execute the transfer using the narrowest operation that fits the case: standard, allowance-based, forced, or pre-approved.
- Capture the transaction hash or action status.
- Read holder state again after confirmation.
- 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.