This guide covers building, signing, and submitting Cardano transactions via the ODATANO API.


Table of Contents

  1. Overview
  2. Workflow Steps
  3. Signing Methods
  4. HSM Signing
  5. Transaction Types
  6. API Reference
  7. Error Handling
  8. Troubleshooting
  9. Examples

Overview

ODATANO follows a Build → Sign → Submit workflow with complete private key isolation:

Transaction Flow
  • Server never sees private keys
  • Signing is external (CLI, browser wallet, hardware wallet) or via HSM (key never leaves chip)
  • Full audit trail via TransactionBuilds, SigningRequests, TransactionSubmissions entities
  • Two builder engines: CSL (TX_BUILDERS=csl) and Buildooor (TX_BUILDERS=buildooor)

Workflow Steps

Step 1: Build Unsigned Transaction

POST /odata/v4/cardano-transaction/BuildSimpleAdaTransaction

{
  "senderAddress": "addr_test1vqm5vyp8...",
  "recipientAddress": "addr_test1qrgfq5j...",
  "lovelaceAmount": 10000000
}

Response includes unsignedTxCbor, txBodyHash, fee, inputs[], outputs[], and a buildId for tracking.

The server fetches protocol parameters and UTxOs, selects inputs, calculates fees, creates change output, serializes to unsigned CBOR, and stores the build record.

Step 2: Sign Transaction (External)

The server never performs this step. Private keys remain under client control.

Cardano CLI

echo '{ "type": "Tx ConwayEra", "description": "", "cborHex": "84a50081..." }' > unsigned.tx

cardano-cli conway transaction sign \
  --tx-body-file unsigned.tx \
  --signing-key-file payment.skey \
  --testnet-magic 2 \
  --out-file signed.tx

cat signed.tx | jq -r '.cborHex'

Browser Wallet (CIP-30)

const api = await window.cardano.nami.enable();
const witnesses = await api.signTx(unsignedTxCbor, true);

Hardware Wallet (Ledger/Trezor)

Via browser extension integration (same CIP-30 API as above).

Step 3: Submit Signed Transaction

POST /odata/v4/cardano-transaction/SubmitTransaction

{
  "buildId": "a8f4c3b2-1e5d-4f9a-b7c6-2d8e9f1a3b4c",
  "signedTxCbor": "84a5008182582071f3d8c1b2..."
}

Response includes txHash, status, submittedAt, and submittedToBackend. Submission goes to Ogmios (primary), then Blockfrost/Koios as fallback.


Signing Methods

MethodSpeedSecurityKey LocationUse Case
Cardano CLIMediumHighFile system (.skey)Backend automation
Browser Wallet (CIP-30)FastVery HighBrowser extensionWeb dApps, Fiori
Hardware WalletSlowMaximumDedicated deviceHigh-value transactions
HSM (PKCS#11)FastMaximumHSM chipEnterprise automation

HSM Signing

When an HSM is configured, ODATANO supports automated server-side signing. The private key never leaves the HSM chip.

Workflow Comparison

External (4 steps): Build → CreateSigningRequest → Sign (client) → SubmitVerifiedTransaction

HSM (2 steps): Build → SignAndSubmitWithHsm

HSM Actions

ActionDescription
GetHsmStatusCheck HSM connection, key info, derived Cardano address
SignWithHsmSign a build (creates signing request + verification, no submit)
SignAndSubmitWithHsmSign + submit in one atomic step

Configuration

HSM_ENABLED=true
HSM_PKCS11_MODULE=/usr/lib/pkcs11/yubihsm_pkcs11.so
HSM_SLOT=0
HSM_PIN=0001password
HSM_KEY_LABEL=cardano-signing-key

Example

// 1. Check HSM status
const status = await POST('/odata/v4/cardano-sign/GetHsmStatus', {});
// → { connected: true, cardanoAddress: "addr_test1...", publicKeyHash: "a1b2..." }

// 2. Build transaction (use HSM address as sender)
const build = await POST('/odata/v4/cardano-transaction/BuildSimpleAdaTransaction', {
  senderAddress: status.cardanoAddress,
  recipientAddress: 'addr_test1...',
  lovelaceAmount: '5000000',
});

// 3. Sign + submit
const submission = await POST('/odata/v4/cardano-sign/SignAndSubmitWithHsm', {
  buildId: build.id,
});
// → { txHash: "abc123...", status: "submitted" }

For HSM security details, supported hardware, and SoftHSM dev setup, see Security Guide.


Transaction Types

Simple ADA Transfer

Action: BuildSimpleAdaTransaction — Transfer lovelace between addresses. Supports outputDatumJson for sending to script addresses and assetsJson for including native tokens.

Transaction with Metadata

Action: BuildTransactionWithMetadata — ADA transfer with attached CIP-20 metadata (invoices, receipts, on-chain records).

Multi-Asset Transfer

Action: BuildMultiAssetTransaction — Transfer ADA + native tokens. Supports outputDatumJson for script address outputs.

Token Minting

Action: BuildMintTransaction — Create native tokens. Supports scriptParamsJson for parameterized validators, inlineDatumJson for datum on minted output, mintRedeemerJson for custom redeemers, lockOnScript to route output to script address, and requiredSignersJson for Plutus extra_signatories.

Plutus Smart Contract Spending

Action: BuildPlutusSpendTransaction — Spend UTxOs locked at Plutus script addresses. Supports inlineDatumJson for continuing output datum (state machines), lockOnScript to re-lock at script address, and requiredSignersJson.

Plutus workflow:

1. Lock:  BuildMintTransaction (lockOnScript + inlineDatumJson) → Sign → Submit
2. Spend: BuildPlutusSpendTransaction (validatorScript + redeemer + lockOnScript) → Sign → Submit

Collateral Setup

Action: SetCollateral — Creates a dedicated 5 ADA collateral UTxO for Plutus transactions. Returns 409 if collateral already exists, 400 if insufficient funds (< 6 ADA).


API Reference

BuildSimpleAdaTransaction

ParameterTypeRequiredDescription
senderAddressbech32YesSource address
recipientAddressbech32YesRecipient address
lovelaceAmountIntegerYesAmount in lovelace (1 ADA = 1,000,000)
changeAddressbech32NoChange address (defaults to sender)
outputDatumJsonStringNoInline datum for recipient output (PlutusData JSON)
assetsJsonStringNoNative assets: [&#123;"unit":"policyId+name","quantity":"amt"&#125;]

BuildTransactionWithMetadata

ParameterTypeRequiredDescription
senderAddressbech32YesSource address
recipientAddressbech32YesRecipient address
lovelaceAmountIntegerYesAmount in lovelace
metadataJsonStringYesTransaction metadata (JSON)
changeAddressbech32NoChange address

BuildMultiAssetTransaction

ParameterTypeRequiredDescription
senderAddressbech32YesSource address
recipientAddressbech32YesRecipient address
lovelaceAmountIntegerYesAmount in lovelace
assetsJsonStringYesAssets: [&#123;"unit":"policyId+name","quantity":"amt"&#125;]
changeAddressbech32NoChange address
outputDatumJsonStringNoInline datum for recipient output

BuildMintTransaction

ParameterTypeRequiredDescription
senderAddressbech32YesSender (pays fees)
recipientAddressbech32YesRecipient (overridden when lockOnScript=true)
lovelaceAmountIntegerYesLovelace with minted assets
mintActionsJsonStringYesMint actions: [&#123;"assetUnit":"policyId+name","quantity":"amt"&#125;]
mintingPolicyScriptStringYesMinting policy CBOR hex
changeAddressbech32NoChange address
requiredSignersJsonStringNoEd25519 key hashes for extra_signatories
scriptParamsJsonStringNoPlutusData params for parameterized validators
inlineDatumJsonStringNoInline datum on recipient output
mintRedeemerJsonStringNoMinting redeemer (defaults to integer 0)
lockOnScriptBooleanNoRoute output to derived script address

Returns scriptHash, fingerprint, scriptAddress when applicable.

BuildPlutusSpendTransaction

ParameterTypeRequiredDescription
senderAddressbech32YesSender (pays fees)
recipientAddressbech32YesRecipient (overridden when lockOnScript=true)
lovelaceAmountIntegerYesLovelace to send
validatorScriptStringYesPlutus validator CBOR hex
scriptTxHashStringYesUTxO tx hash at script address (64-char hex)
scriptOutputIndexIntegerYesUTxO output index
redeemerJsonStringYesRedeemer PlutusData JSON
datumJsonStringNoInput datum (for hash-based datums)
changeAddressbech32NoChange address
requiredSignersJsonStringNoEd25519 key hashes for extra_signatories
scriptParamsJsonStringNoPlutusData params for parameterized validators
inlineDatumJsonStringNoInline datum on continuing output
lockOnScriptBooleanNoRe-lock at derived script address

Returns scriptHash, scriptAddress when applicable.

SetCollateral

ParameterTypeRequiredDescription
addressbech32YesAddress to set up collateral for

Returns 200 (build), 409 (already available), or 400 (insufficient funds).

SubmitTransaction

ParameterTypeRequiredDescription
buildIdUUIDYesBuild ID from a build action
signedTxCborStringYesFully signed transaction CBOR hex

SubmitSignedTransaction

ParameterTypeRequiredDescription
signedTxCborStringYesFully signed transaction CBOR hex
networkStringYesTarget network

For externally built transactions (not via ODATANO actions).


Error Handling

Error CodeHTTPCauseResolution
ODATANO_INSUFFICIENT_FUNDS400Sender doesn’t have enough ADATop up address or reduce amount
ODATANO_INVALID_INPUT400Malformed address, invalid amount, missing fieldsValidate bech32 format, check required params
ODATANO_TX_VALIDATION_FAILED400Wrong signing key or tampered CBORVerify correct .skey, re-build if modified
ODATANO_PROVIDER_UNAVAILABLE503All backends unreachable/timed outRetry after 30s, check Ogmios/Blockfrost status
ODATANO_TX_ALREADY_SUBMITTED409Transaction already on chain/mempoolExpected (idempotent), check explorer

Troubleshooting

”All backends failed: Failed to acquire requested point”

Ogmios node not fully synchronized. Check: curl http://localhost:1337/health — wait until networkSynchronization > 0.99.

”Insufficient funds” but wallet has balance

UTxOs not yet confirmed or spent in a pending transaction. Wait 1-2 minutes for confirmations.

”Invalid signature” after signing

Wrong signing key or unsigned TX was modified. Verify key matches sender address, check --testnet-magic matches network. Re-build if needed.

”No ADA-only UTxO available for collateral”

Plutus transactions require collateral. Use SetCollateral to create a dedicated 5 ADA UTxO, or ensure the sender has at least one UTxO with >= 5 ADA.


Examples

Postman Collection

TypeScript Scripts


References