Transactions
A transaction is an intent that flows through network authorization, policy, screening, and quota checks before it can be signed and broadcast. Native and QRC20 transfers use the same endpoint; token routing is automatic from the selected assetId.
Create a transfer
POST/v1/transactions
{
"externalId": "payout-2026-08-001", // your idempotency handle (see below)
"networkCode": "QUANTUM_SANDBOX", // required explicit operation network
"sourceWalletId": "…", // required — must not be WATCH_ONLY
"destinationAddress": "0x…", // required
"assetId": "20804000-0000-4000-8000-000000000001", // required; QRC20 assets route to token transfer
"amount": "1000000000000000000", // required; decimal integer string in Qwei/raw units
"note": "August payout batch 1",
"metadata": {"batch": "2026-08"}
}
Returns 201 with the full transaction object in status SUBMITTED.
Idempotency — use both layers
externalId— domain-level: creating a second transaction with the sameexternalIdreturns the existing one instead of double-spending.X-Idempotency-Keyheader — transport-level: any POST/PUT retried with the same key within 24 hours replays the original response (markedX-Idempotent-Replayed: true). Reusing a key on a different method or path returns422 IDEMPOTENCY_CONFLICT(1601).
X-Idempotency-Key — not Idempotency-Key. The latter is silently ignored.Status lifecycle
stateDiagram-v2
[*] --> SUBMITTED
SUBMITTED --> QUEUED
QUEUED --> REJECTED: policy denies
QUEUED --> PENDING_AUTHORIZATION: approval rule matches
PENDING_AUTHORIZATION --> PENDING_SIGNATURE: approve
PENDING_AUTHORIZATION --> REJECTED: reject
QUEUED --> PENDING_AML_SCREENING: screening pending / flagged
PENDING_AML_SCREENING --> PENDING_SIGNATURE: clears
PENDING_AML_SCREENING --> BLOCKED: blocked
QUEUED --> PENDING_SIGNATURE: all checks clear
PENDING_SIGNATURE --> SIGNED: approved in EnQlave
PENDING_SIGNATURE --> FAILED: signing failure / expiry
SIGNED --> BROADCASTING
BROADCASTING --> CONFIRMING
BROADCASTING --> FAILED: broadcast failure
CONFIRMING --> COMPLETED
COMPLETED --> [*]
Any pre-broadcast state can be moved to CANCELLED via the cancel endpoint. COMPLETEDFAILEDREJECTEDCANCELLEDBLOCKED are terminal. PENDING_AML_SCREENING is also the fail-safe parking state when the screening provider itself errors — treat it as "on hold", not strictly "screening in progress".
The transaction object
{
"id": "…", "tenantId": "…", "externalId": "payout-2026-08-001",
"sourceVaultId": "…", "sourceWalletId": "…",
"destinationAddress": "0x…", "assetId": "20804000-0000-4000-8000-000000000001",
"status": "CONFIRMING", "failureReason": "",
"chainTxHash": "0x…", "confirmations": 3,
"amount": "1000000000000000000", // decimal strings, present when set:
"fee": "…", "gasPrice": "…", "maxFeePerGas": "…", "maxPriorityFee": "…",
"gasLimit": 21000, "nonce": 17, "blockNumber": 3392847,
"note": "…", "metadata": {…},
"createdAt": "…", "updatedAt": "…", "broadcastAt": "…", "confirmedAt": "…"
}
Query, estimate, act
| Endpoint | Purpose |
|---|---|
GET/v1/transactions | List. Filters: status, vaultId, walletId, assetId, dateFrom, dateTo (RFC 3339, inclusive); sorting: sortBy=createdAt&sortOrder=asc|desc; paging: limit (default 20, max 100) + cursor |
GET/v1/transactions/{id} | Fetch one |
GET/v1/transactions/export | CSV export (same filters) |
GET/v1/transactions/estimate-fee?sourceAddress=…&destinationAddress=…&assetId=…&amount=… | Fee estimate before creating |
POST/v1/transactions/{id}/approve | Body {"approverId","reason"} — moves to PENDING_SIGNATURE |
POST/v1/transactions/{id}/reject | Body {"approverId","reason"} |
POST/v1/transactions/{id}/cancel | Cancel any pre-broadcast transaction |
Cursor pagination
List responses end with "page": {"nextCursor": "…", "hasMore": true}. Cursors are opaque and direction-bound — replaying a cursor under a different sortOrder returns 400 invalid cursor. Iterate until hasMore is false.

