Transaction tracking
Understand DALP transaction finality, indexer visibility, and safe retry behavior.
Use this guide when DALP accepts a write request but the lifecycle is not yet clear to your integration. DALP finality means the queued operation reached a terminal platform state. Explorer and indexed-resource visibility can arrive slightly later. Start with the request ID, then use the transaction hash when you need the mined receipt.
Supply-changing retries
For minting and other supply-changing writes, send one idempotency key for the approved instruction and keep polling the transaction queue. DALP recognizes the same queued operation by matching the sender, EVM chain, and idempotency key.
If the first submission is still active, DALP keeps the retry attached to that in-flight workflow instead of creating a second independent mint. If the first submission already completed inside the 24-hour idempotency window, DALP returns the recorded result. After that window, reconcile the earlier transaction before issuing a replacement instruction with a new idempotency key. If the original request failed, reached dead letter, or was cancelled, submit a new one only after reconciling the previous result.
The boundary is a platform transaction-control boundary. DALP token contracts still enforce configured mint authority, such as the Supply Management role. For the full mint retry model, see Mint replay, idempotency, and supply controls.
Prerequisites
- Platform URL, such as
https://your-platform.example.com - API key. See Getting Started.
- A transaction request ID from an asynchronous DALP response, or a transaction hash from an error response or the
X-Transaction-Hashheader.
Choose the lookup
| What you have | Endpoint or command | Use it for |
|---|---|---|
| Transaction request ID | GET /api/v2/transaction-requests/{transactionId} | Poll DALP queue state, sub-status, operation kind, transaction hash, block number, and error message. |
| Request ID, watching from the Console | GET /api/v2/transaction-requests/{transactionId}/stream | Receive the same status as live events while the Console is open, instead of polling on a timer. |
| Nothing yet, a wallet, or a filter | GET /api/v2/transaction-requests | List recent transaction requests with a decoded description, status, and sender for monitoring. |
| Transaction hash | GET /api/v2/blockchain-transactions/{transactionHash} | Read the EVM transaction receipt, including success or reverted execution. |
| Authenticated CLI session plus hash | dalp blockchain-transactions read {transactionHash} | Run the same receipt lookup from a terminal. |
Read the decoded call description
Each transaction request carries a human-readable description of the operation it performs, decoded from the contract call at the time DALP queues it. Instead of reading a raw hash or a generic operation kind, you see the call itself, such as mint(0x71C7…, 1000000) or Bond.mint(…). Batched smart-account calls are unwrapped so the description names the inner contract call rather than the account-execution wrapper.
The transaction list endpoint returns this label as an optional description field on each row:
curl --globoff -X GET "https://your-platform.example.com/api/v2/transaction-requests?filter[status]=PENDING_APPROVAL" \
-H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx"{
"data": [
{
"transactionId": "01934567-89ab-7def-8123-456789abcdef",
"kind": "token.mint",
"status": "PENDING_APPROVAL",
"subStatus": null,
"fromAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"chainId": 1,
"transactionHash": null,
"description": "mint(0x71C7656EC7ab88b098defB751B7401B5f6d8976F, 1000000)",
"createdAt": "2026-03-09T10:00:00.000Z",
"updatedAt": "2026-03-09T10:00:00.000Z"
}
]
}The same decoded label appears in the Console when a transaction awaits a custody-provider signature. An approver reviews the call they are about to authorise instead of an opaque payload. Treat description as optional: it is null when DALP cannot decode a readable description for that entry, such as older requests or multi-step operations that carry no single decodable call. Fall back to the kind value whenever description is null.
Finality and visibility
DALP uses the transaction request state as the source of truth for platform finality. A request is terminal when it reaches one of four states: COMPLETED, FAILED, DEAD_LETTER, or CANCELLED. For a successful write, COMPLETED means DALP has finished the operation and recorded the result for the caller-visible entry.
Application-level finality supports T+0 settlement workflows such as DvP and XvP coordination. Do not compare DALP finality directly to a generic EVM confirmation rule such as waiting 12 blocks. The EVM receipt and block number still matter for chain inspection. Gate workflow progress on the queue state returned by DALP.
Indexer visibility is a second phase. After a completed on-chain mutation has a known block number, DALP waits for the Ledger Index to process that block before returning the completed state from the status endpoint. The wait is event-driven: DALP listens for index block-progress events and resolves immediately when it has already caught up. If the Ledger Index does not catch up within 30 seconds, the endpoint still returns the current transaction state.
This means there are two safe observations to separate in your integration:
| Observation | What it means | What to do |
|---|---|---|
Queue status is COMPLETED | DALP finished the requested operation. | Continue the business workflow. Do not resubmit the original write. |
Receipt has status: "Success" | The EVM transaction was mined successfully. | Use the receipt for chain inspection, reconciliation, and explorer links. |
| A resource or explorer has not caught up yet | Indexed views can lag the terminal transaction state. | Poll the affected resource or explorer for a short period instead of retrying the write. |
Queue status is terminal but not COMPLETED | DALP stopped the operation as failed, cancelled, or requiring intervention. | Follow the error message, sub-status, or operational runbook before submitting a new request. |
For developer-facing workflows, treat COMPLETED as the point where the DALP operation is final. Treat indexer and explorer lag as a read-your-writes visibility concern. Never use a missing explorer entry or a temporarily stale indexed view as evidence that the original write did not happen.
Caller visibility
DALP scopes queue records to the caller. A status lookup returns the same not-found result for a missing transaction request and for one outside the caller's wallet or organization scope.
Hash lookup has a slightly different boundary. DALP can return public on-chain receipt data for a known hash. Any pending record or receipt must be visible to the API key, wallet, and organization making the lookup. A not-found response from the hash endpoint means DALP did not find the transaction on-chain and did not find a caller-visible record.
Steps
Poll the queue status when you have a request ID
Use the status endpoint from asynchronous responses, retry workflows, and runbooks that return a transaction request ID.
curl -X GET "https://your-platform.example.com/api/v2/transaction-requests/01934567-89ab-7def-8123-456789abcdef" \
-H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx"A status response includes the operation kind, queue state, optional sub-status, primary transaction hash, confirmed block number when available, and any error message DALP recorded.
{
"data": {
"transactionId": "01934567-89ab-7def-8123-456789abcdef",
"kind": "token.mint",
"status": "CONFIRMING",
"subStatus": null,
"transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"blockNumber": null,
"errorMessage": null,
"createdAt": "2026-03-09T10:00:00.000Z",
"updatedAt": "2026-03-09T10:00:15.000Z"
}
}When a failed write maps to a known DALP error, the same response includes a populated contractError next to errorMessage.
{
"data": {
"transactionId": "01934567-89ab-7def-8123-456789abcdef",
"kind": "token.mint",
"status": "FAILED",
"subStatus": "UNKNOWN_ERROR",
"transactionHash": null,
"blockNumber": null,
"errorMessage": "Available: 10000000, requested: 50000000",
"contractError": {
"id": "DALP-1106",
"args": { "available": "10000000", "requested": "50000000" },
"why": "The contract's on-chain state matches this failure: the amount exceeds the currently frozen token balance. Available: 10000000, requested: 50000000.",
"message": "The amount exceeds the currently frozen token balance. Available: 10000000, requested: 50000000.",
"fix": "Reduce the amount to at most the currently frozen balance, or freeze additional tokens first."
},
"createdAt": "2026-03-09T10:00:00.000Z",
"updatedAt": "2026-03-09T10:00:15.000Z"
}
}Read the receipt when you have a transaction hash
Use the hash endpoint after a timeout error, an X-Transaction-Hash header, or a status response that contains transactionHash.
curl -X GET "https://your-platform.example.com/api/v2/blockchain-transactions/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
-H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx"From an authenticated CLI session, run the same lookup with dalp blockchain-transactions read.
dalp blockchain-transactions read 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefA mined transaction returns a receipt with the execution result, block number, and gas details.
{
"data": {
"transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"from": "0xABCD1234567890abcdef1234567890abcdef1234",
"receipt": {
"status": "Success",
"revertReason": null,
"revertReasonDecoded": null,
"blockNumber": "12345678",
"blockHash": "0xabcd...",
"gasUsed": "21000",
"effectiveGasPrice": "1000000000",
"from": "0xABCD1234567890abcdef1234567890abcdef1234",
"to": "0x567890abcdef1234567890abcdef1234567890ab"
}
}
}If DALP has a caller-visible stored record but the receipt is not yet available, receipt is null. Poll after a short interval rather than treating a null receipt as a failure signal.
Decide what to do next
Use the queue response and receipt together. Do not use one stale response as the only retry signal.
| Result | Meaning | Next step |
|---|---|---|
| Queue status is non-terminal | DALP still has work in progress. | Wait a few seconds and poll the status endpoint again. |
Queue status is COMPLETED and the receipt status is Success | The operation finished. | Continue your workflow. Do not resubmit the original request. |
Receipt status is Reverted | The EVM transaction was mined but failed. | Fix the cause shown by revertReasonDecoded or the related application error, then submit a new request. |
Status response includes errorMessage | DALP recorded a queue failure or timeout detail. | Investigate the message before sending a replacement request. Read contractError for structured detail when present. |
| Either lookup returns not found | DALP cannot show a caller-visible record for that identifier. | Confirm the API key, wallet, organization, chain, and identifier before retrying. |
Stream status updates
When you watch a transaction from the Console, you do not have to poll the status endpoint on a timer. GET /api/v2/transaction-requests/{transactionId}/stream keeps the status open as a server-sent events stream. It sends the current status as the first event, then sends a new event only when the queue state, sub-status, transaction hash, or error message changes. When the request reaches a terminal state, the stream sends that final status and closes.
Each event is the status object itself, the same inner shape the poll endpoint returns inside its data envelope. The poll response wraps the status in { "data": { ... }, "links": { ... } }; a stream event carries that status object directly, with no data wrapper. A parser that reads the poll response should target the status fields, not the outer data key, when it reads a stream event. The stream is a live mirror of the poll: replace your local copy of the status with the contents of each event rather than merging events together.
event: message
data: {"transactionId":"01934567-89ab-7def-8123-456789abcdef","kind":"token.mint","status":"CONFIRMING","subStatus":null,"transactionHash":"0x1234...","blockNumber":null,"errorMessage":null,"createdAt":"2026-03-09T10:00:00.000Z","updatedAt":"2026-03-09T10:00:15.000Z"}How the stream behaves
| Behavior | Detail |
|---|---|
| First event | The current status at the moment the connection opens. If the request is already terminal, that single event closes the stream. |
| Change events | One event for each observed transition: a new queue state, sub-status, transaction hash, or error message. |
| Terminal close | The stream sends the terminal status (COMPLETED, FAILED, DEAD_LETTER, or CANCELLED) and then closes. |
| Connection cap | The platform closes the connection after a few minutes even if no terminal state has arrived. Fall back to the status poll to finish the wait. |
| Scope | The same caller scope as the status read. A request outside the caller's wallet or organization scope returns the same not-found result. |
The stream is consumed same-origin by the Console. For request-by-request integration, for a server-to-server client, and for any flow that authenticates with an API key, poll GET /api/v2/transaction-requests/{transactionId} instead. Because the connection closes on its own before a slow transaction settles, design a Console client to fall back to the status poll when the stream ends without a terminal event.
Transaction state flow
This diagram shows how DALP moves from an accepted request to a final state and where to act on each outcome.
Response fields
Queue status response
| Field | Type | Description |
|---|---|---|
transactionId | string | Transaction request identifier. |
kind | string | Mutation kind submitted to the queue, such as token.create or token.mint. |
status | string | Current queue state. Terminal states are COMPLETED, FAILED, DEAD_LETTER, and CANCELLED. |
subStatus | string | null | Optional progress or failure detail. |
transactionHash | string | null | Primary EVM transaction hash after broadcast. |
transactionHashes | string[] | Present only when a batch operation produced multiple transactions. |
blockNumber | string | null | Confirmed block number when DALP can resolve it. |
errorMessage | string | null | Queue error or timeout detail when available. |
contractError | object | null | Structured failure detail, present only when a reverted write maps to a known DALP error. |
createdAt | string | When DALP accepted the transaction request. |
updatedAt | string | When the request status last changed. |
When a write fails on-chain and the revert maps to a known DALP error, the status response carries a structured contractError object alongside the flat errorMessage. Use contractError to show clear failure copy instead of a raw string. For non-DALP reverts and successful operations, contractError is null, and errorMessage stays the readable fallback.
| Field | Type | Description |
|---|---|---|
contractError.id | string | DALP error code for the revert, such as DALP-1106. |
contractError.args | object | Decoded revert values keyed by name, such as available and requested. |
contractError.why | string | Explanation of why the operation failed. |
contractError.message | string | Headline summary of the failure. |
contractError.fix | string | Suggested next step. Present when DALP has guidance for that error. |
Compliance reverts
When the failed write is a mint (DALP-1110) or a transfer (DALP-1096) that the token's compliance rules rejected, contractError.args classifies the cause. Branch on it instead of parsing the message text. The same enrichment a synchronous compliance error returns is stored on the request and projected onto the status response, so an asynchronous failure carries the structured reason when you poll it.
contractError.args.reason | Meaning | Typical next step |
|---|---|---|
identity-not-registered | The party's wallet is not registered in the token's identity registry and cannot hold the token. | Register the party's identity in the token's identity registry, then retry. |
identity-not-verified | The party is registered but is missing required identity claims (unissued, revoked, or expired). | Have the required claims issued by a trusted issuer, then retry. |
module-blocked | A compliance module rejected the operation, for example a country, supply, or investor-limit rule. | Review the token's active compliance modules to find the constraint. |
When DALP can identify the failing party, contractError.args also carries party (sender or recipient) and the affected wallet. The enrichment is best effort. DALP skips it in some cases, for example for large batch transfers or when it cannot complete in time, and the response then falls back to the headline message under the same id. Branch on contractError.id first, then read contractError.args.reason when it is present. For the full classification, the role needed to read granular per-address detail, and the matching synchronous error shape, see Compliance failure reasons.
Receipt response
| Field | Type | Description |
|---|---|---|
transactionHash | string | EVM transaction hash. |
from | string | Sender wallet address. |
receipt | object | null | EVM receipt. null means the receipt is not available to this lookup yet. |
receipt.status | "Success" | "Reverted" | EVM execution result. |
receipt.revertReason | string | null | Raw revert reason when available. |
receipt.revertReasonDecoded | string | null | Decoded revert reason when available. |
receipt.blockNumber | string | Block containing the transaction. |
receipt.gasUsed | string | Gas consumed by the transaction. |
receipt.effectiveGasPrice | string | Effective gas price paid. |
Polling pattern
- Save the transaction request ID or hash from the original response.
- Wait two to three seconds before the first follow-up lookup.
- Poll the status endpoint every three to five seconds while the queue remains active.
- Treat
COMPLETEDas final for the DALP operation. - Read the receipt once a transaction hash is available.
- If the receipt succeeds but an indexed resource or explorer view is stale, poll the read side for visibility instead of retrying the write.
- Stop when the queue reaches a terminal state, the receipt is mined, or the lookup returns a scoped not-found result that you have verified.
Queue recovery after missing receipts
DALP keeps polling for a receipt after broadcast. If none arrives, DALP marks the queued entry as timed out and hands it to reconciliation rather than changing the queue record on every poll. That timeout is not a finality signal for client retries. DALP needs more evidence before it can classify the entry as completed, failed, or ready for rescue.
Reconciliation checks the chain again. If the receipt appears, DALP completes or fails the stored entry from that receipt. If the chain still shows nothing, DALP waits until the dropped-from-mempool window elapses before requeuing. That window is one hour from the stored transaction update time. After that point, DALP requeues when retry budget remains, or routes to manual intervention when the budget is exhausted.
For API clients, the rule stays simple: check the transaction status before submitting a replacement request. DALP's recovery protects the queue, but it does not make blind external retries safe.
Troubleshooting
| Issue | What to check |
|---|---|
| 404 after a known submission | Confirm the same API key, wallet, organization, chain, and identifier were used. |
Receipt remains null | Keep polling for a short period, then inspect the configured EVM explorer or operational logs. |
Receipt is Reverted without a decoded reason | The contract may not expose a revert string. Check logs and the operation inputs. |
| Receipt shows success but indexed state is not visible yet | Treat the write as successful. Wait a few seconds and query the affected resource or explorer again. |
Related operations
- Error handling: Handle API errors and blockchain reverts.
- CLI command reference: Look up a transaction hash from an authenticated terminal.
- Reconciliate balances: Compare on-chain state with your records.
- API reference: Complete endpoint documentation.
How to read Trading Venue market data and streams
Read venue markets, depth, trades, candles, and the ticker through the v2 API, and consume the live market-data stream with sequence-gap resynchronization.
How to fund wallets for gas
Keep transactions from stalling on networks where gas is not free. Check gas readiness before you submit, fund the right wallet, and resolve a GAS_REQUIRED failure.