Skip to main content

Transactions

Transaction Lifecycle

Every transaction follows a defined state machine from creation to on-chain confirmation (or terminal failure):

StatusDescriptionNext
pendingCreated, no quorum required (or quorum already satisfied at creation time).requested, to-cancel, failed
approval-pendingWaiting for required vault-quorum approvals.approved, to-cancel, cancelled
approvedQuorum reached, waiting to be sent to signing.requested, to-cancel
requestedSubmitted to the MPC signing pipeline.signed, to-cancel, failed
signedSigned by the MPC network.submitted, failed
submittedBroadcast to the blockchain.mined, replaced, failed
minedConfirmed on chain (terminal — happy path).
to-cancelCancellation requested via Decline; platform is unwinding.cancelled
cancelledCancelled (terminal — see reasonCode on the event for details).
failedFailed on submission/processing (terminal).
replacedSuperseded by a replacement transaction that mined (terminal).

A warning event (separate from status) is emitted when processing is paused but the transaction is still alive — most commonly for insufficient balance at signing time. See Warnings and retries.

Cancellation reason codes you may see on cancelled events: insufficient_balance, singner_unavailable, not_enough_utxos, order_expired, invalid_order, transaction_expired, declined_by_client, rejected_by_approver, rejected_by_master_approver, replacement_status_invalid.

Create a Transaction

tip

Asset IDs The asset field accepts a Universal Asset ID, not a human-readable name. Query available assets via GET /networks/{network}/assets. Native coins use a SLIP-44–scoped identifier (e.g. c60 for native ETH on Ethereum, c0 for native BTC); token assets append a _t… segment that encodes the token contract or issuer (e.g. c60_t0xdac17f958d2ee523a2206206994597c13d831ec7 for USDT on Ethereum).

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "gMP71sR5sNUnGdKFTsNzp6",
"destination": "0x742d35Cc6634C0532925a3b8...",
"network": "ethereum-mainnet",
"asset": "c60_t0xdac17f958d2ee523a2206206994597c13d831ec7",
"amount": "1000.50",
"feePriority": "medium",
"note": "Payment to supplier"
}' \
https://api.carabaas.com/api/v1/transactions

Request fields

FieldRequiredDescription
orderIdYesA valid UUID string. Idempotency key — see Idempotency.
addressIdConditionalSource address. Required for non-UTXO networks. For UTXO networks one of addressId / accountId / vaultId must be provided.
accountIdConditionalSource account (UTXO chains only) — collect UTXOs across all addresses in the account.
vaultIdConditionalSource vault (UTXO chains only) — collect UTXOs across all addresses in the vault.
destinationYesRecipient on-chain address.
networkYesCustody network identifier (e.g. ethereum-mainnet, bitcoin-mainnet).
assetYesUniversal Asset ID for that network.
amountYesDecimal string in the asset’s human units (e.g. "1000.50").
feePriorityNoOne of low | medium | high. Default medium. (custom is not accepted — use options.gasPrice on ETH-like networks instead.)
noteNoUp to 256 chars. Visible only to you — saved with the transaction record.
referenceNoUp to 128 chars. Shared field — visible to you and to the counterparty integrations.
optionsNoNetwork-specific knobs — see below.

options

OptionApplies toDescription
gasPriceETH-likeDecimal string. Takes precedence over feePriority when set.
gasLimitETH-likeDecimal string.
dataETH-likeHex-encoded calldata (0x…) for smart-contract interaction.
memoXRP, ATOM, EOS, HBAR, LUNA/LUNC, XDB, XEM, XLM, ALGODestination tag / memo. Wire format depends on the chain.
changeAddressIdUTXOAddress that should receive the change output. Must belong to the source vault.
feePayerAddressIdSolanaAddress paying the network fee. Must belong to the source vault.

Source selection

Network familyRequired source fields
Account-based (ETH-like, Tezos, Solana, Tron, Cosmos, Ripple, Stellar, …)addressId is required.
UTXO (Bitcoin, Litecoin, Dogecoin, Bitcoin Cash, Dash)One of addressId / accountId / vaultId. When sending from accountId / vaultId, also pass options.changeAddressId.

For UTXO specifics — change handling, address types — see Bitcoin & UTXO Chains.

If you pass more than one source field, the platform validates they refer to the same vault/account; mismatches return 400.

Authorization

The caller must hold initiate permission on the vault that owns the source address. Without it the request is rejected with 403 before validation.

Idempotency

The orderId field is the idempotency key. Submitting the same orderId twice does not create a duplicate — the API returns the existing transaction object.

The HTTP status code distinguishes create vs replay; read it explicitly:

HTTPMeaning
201A new transaction was created for this orderId.
200A transaction with this orderId already exists; the same object is returned.

Replays return the existing transaction regardless of its current status, so data.status is the canonical signal for progress. Generate a fresh UUID per logical business action; on timeouts/network errors reuse the same orderId so retries don’t spawn duplicates.

Get transaction details

# By transaction ID (default)
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}

# By blockchain hash
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/{hash}?idType=blockchainHash"

# By order ID
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/{orderId}?idType=orderId"

idType accepts id (default), blockchainHash, or orderId. The response includes lifecycle events and creator by default; approvals, decodedPayload, and quorum are returned when the caller has the relevant permissions on the vault.

Decoded payload

curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/decoded

Returns the transaction with its decoded payload — useful for verifying the exact operation that will be signed (calldata decoding, recipients, amounts) before approving.

Lifecycle events

curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/events

Returns the full event timeline (pending, approval-pending, approved, signed, submitted, mined, warning, failed, cancelled, replaced, …) with timestamps and data payloads. Use this to reconstruct what happened to a transaction post-hoc; for live updates rely on Webhooks.

List transactions

curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions?organizationId=djk2wDuMhsx9KR2r7JgBQW"

organizationId is required. Vault scoping is automatic — without manageVaults on the organization (or Vault:read global), results are restricted to vaults the caller has read on.

Filters

All filters use bracket notation and accept arrays (repeat the parameter, or use comma-separated values per your HTTP client).

FilterNotes
filter[id]Internal transaction IDs.
filter[orderId]Your orderIds.
filter[hash]On-chain transaction hashes.
filter[vaultId]Vault IDs (intersected with the caller’s accessible vaults).
filter[accountId]Account IDs.
filter[network]Custody network codes.
filter[asset]Universal Asset IDs.
filter[sourceAddressId]Source address IDs.
filter[status]One or more of the lifecycle statuses (see Transaction Lifecycle).
filter[creatorId]Client IDs of transaction creators.
relations[]Hydrate optional relations: vault, account, address, events, approvals, creator.
searchFree-text search over address name / network address / hdpath (min 3 chars).
page, pageSizeDefaults page=0, pageSize=100. Max pageSize=1000.
# Status + network, hydrate vault and approvals
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions\
?organizationId=djk2wDuMhsx9KR2r7JgBQW\
&filter[status]=mined\
&filter[network]=ethereum-mainnet\
&relations[]=vault&relations[]=approvals"

The response is paginated:

{
"data": [ /* transactions */ ],
"pagination": { "total": 1234, "page": 0, "pageSize": 100, "totalPages": 13 },
"empty": false,
"hasMore": true
}

Blockchain-sourced transactions

List on-chain transactions involving your vault addresses (both outgoing and incoming/external):

curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/blockchain\
?vaultId=kR7mNpX2wQvL9sYhBjD4eT&network=ethereum-mainnet"

One of vaultId / accountId / sourceAddressId is required; network is optional. Pagination uses page / pageSize and a hasMore cursor (no total).

Simulate a transaction

Preview fee/balance impact for a candidate transaction without persisting anything:

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "gMP71sR5sNUnGdKFTsNzp6",
"destination": "0x742d35Cc...",
"network": "ethereum-mainnet",
"asset": "c60",
"amount": "1.0"
}' \
https://api.carabaas.com/api/v1/transactions/simulate

The body is a regular CreateTxDto. The endpoint runs the simulation for all three fee priorities (low, medium, high) and returns an array of results, each with the projected balanceChanges and chain-specific metadata (e.g. gasPrice, gasLimit on ETH-like).

Approve a transaction

When a transaction sits in approval-pending, approvers in the vault’s quorum sign it via:

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"keyId": "the-approver-key-id",
"approval": "<signature-over-approvalString>"
}' \
https://api.carabaas.com/api/v1/transactions/{txId}/approve

Constraints:

  • Caller must have approve permission on the vault.
  • Transaction must currently be in approval-pending; otherwise 400 Transaction is not in approval-pending status.
  • Transaction must have a quorum attached; otherwise 400 Transaction has no quorum associated.

The signed string is over the transaction’s approvalString (deterministic, returned in the transaction object). Once the quorum threshold is met the transaction transitions to approved and proceeds automatically. See Approval Workflow for the end-to-end signing protocol.

List approvals

curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/approvals

Returns approvals collected so far, each with the approving key and client. Caller needs read on the vault (or manageVaults on the org); approver identity (hideApprovers) is hidden unless the caller also has approve on the vault or Vault:read globally.

Master approval payload

curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/master-approval-payload

Returns { approvalString, approvals, encodedTransaction, payloads } — the bundle a master approver needs to issue the final signature. Caller must have approve on the vault, and the transaction must already have its payload constructed (otherwise 400 Transaction has no payload constructed yet).

Decline a transaction

curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/decline

Requests cancellation of a transaction that has not yet reached a terminal state. The transaction transitions to to-cancel while the platform unwinds processing, then to cancelled (reasonCode: declined_by_client).

Caller must have decline permission on the vault. Cancellation is best-effort: once a transaction is submitted to the network, it can no longer be safely declined — use Replace for ETH-like networks or wait for the network outcome.

Replace a transaction

Replace a still-unmined transaction with a higher-fee version (e.g. RBF / fee bump):

curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "feePriority": "high" }' \
https://api.carabaas.com/api/v1/transactions/{txId}/replace

Body fields (all optional):

FieldNotes
feePrioritylow | medium | high. Default for replacement is high.
gasPriceDecimal string. Overrides feePriority when set.
gasLimitDecimal string. Falls back to the parent transaction’s value if omitted.

Constraints (return 400 otherwise):

  • Only Ethereum-like networks support replacement.
  • The original transaction must be in submitted status.
  • A replacement transaction itself cannot be replaced — bump the parent instead.

Caller must have initiate on the vault. The replacement carries a new transaction id but reuses the parent’s orderId; on success the parent eventually transitions to replaced (terminal) once the replacement mines.

Warnings and retries

When the platform pauses processing for a transient reason it emits a warning event (in addition to the lifecycle status, which stays unchanged) and retries automatically. Reason codes you’ll see on the warning payload:

Reason codeWhat it means
insufficient_balanceSource address can’t cover amount + fee at signing time. Top up the address; the platform will pick the transaction back up.
singner_unavailableAn MPC signer is temporarily offline. Resolves automatically when signers return.

If the underlying condition resolves, processing continues from where it stopped — no new transaction is created, the same id/orderId proceeds. If it doesn’t resolve within the platform’s retry window, the transaction is cancelled with a matching reasonCode. Subscribe to webhooks (warning, cancelled) to surface this to your users.

Special transaction types

The base POST /api/v1/transactions flow handles standard transfers. The following endpoints create specialised custody transactions that share the same lifecycle, idempotency, and approval semantics:

Trustline (Stellar / Ripple)

POST /api/v1/transactions/trustline — open an asset trustline so the address can hold a non-native token. See Stellar & Ripple — Trustlines.

Reveal (Tezos)

POST /api/v1/transactions/reveal — one-time on-chain public-key reveal required before any outgoing Tezos operation. See Tezos Reveal.

ERC-20 / TRC-20 transfer-from

POST /api/v1/transactions/transfer-from — pull tokens previously approved to a spender address.

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "8uPDmg3KsbUfsB8dx",
"spenderAddressId": "mnfUjfKLCxFbLZQmB",
"destination": "0xRecipient...",
"network": "ethereum-mainnet",
"asset": "c60_t0x779877A7B0D9E8603169DdbD7836e478b4624789",
"amount": "150",
"feePriority": "medium"
}' \
https://api.carabaas.com/api/v1/transactions/transfer-from

Both addressId (the token holder / from) and spenderAddressId (the address paying gas in the native coin) must belong to the same vault.

Setup-allowance flow

POST /api/v1/transactions/flows/setup-allowance — multi-step orchestrated flow that configures an ERC-20/TRC-20 allowance from ownerAddressId for spenderAddressId.

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ownerAddressId": "8uPDmg3KsbUfsB8dx",
"spenderAddressId": "mnfUjfKLCxFbLZQmB",
"network": "ethereum-mainnet",
"asset": "c60_t0x779877A7B0D9E8603169DdbD7836e478b4624789",
"feePriority": "medium"
}' \
https://api.carabaas.com/api/v1/transactions/flows/setup-allowance

orderId (UUID) is optional — if provided it acts as the flow-level idempotency key. The response is a Flow object (not a Transaction); the underlying erc20_approve / trc20_approve transactions are created inside the flow.

Manage flows via:

  • GET /api/v1/transactions/flows/{id} — fetch by id (default) or orderId (?idType=orderId).
  • PATCH /api/v1/transactions/flows/{id}/decline — request flow cancellation.

See Also