Andamio Logo
Protocol/Protocol V2/Transaction State Machine

Transaction State Machine

How Andamio tracks every Cardano transaction from build to database synchronization

Transaction State Machine

How Andamio tracks every transaction from build to database synchronization — the lifecycle, states, monitoring, and integration patterns.

Looking for individual transaction specs? The V2 Transactions section has detailed technical reference for each transaction type — inputs, outputs, costs, and datums.

The Happy Path

You build the UX, Andamio handles credentials. Here's what happens for every transaction:

  1. User clicks action — your app handles the UI
  2. Request transaction — POST to the appropriate build endpoint
  3. Return unsigned CBOR — the API builds the transaction
  4. Prompt wallet signature — show the wallet popup
  5. User approves & signs — CIP-30 wallet API
  6. Submit signed tx — wallet submits to Cardano
  7. Register for confirmation — tell Andamio to track it
  8. Block confirmation — Cardano confirms (~20-30s)
  9. Sync DB + push event — gateway syncs, you get notified
  10. Display success — update your UI

Lifecycle Diagram

StepWhoWhat Happens
BUILDAPI GatewayBuilds an unsigned transaction (CBOR)
SIGNUser's walletUser signs the transaction in their browser wallet
SUBMITUser's walletSigned transaction is submitted to the Cardano network
REGISTERFrontendCalls the registration endpoint with the transaction hash and type
CONFIRMGateway (automatic)Polls the blockchain until the transaction appears on-chain
UPDATEGateway (automatic)Syncs the confirmed on-chain data to the Andamio database

Client Transaction States

The frontend tracks its own state machine during the build-sign-submit flow:

StateMeaning
idleReady to start a new transaction
fetchingBuilding unsigned CBOR via API
signingWaiting for user to approve in wallet
submittingSubmitting signed transaction to Cardano
successTransaction submitted, hash received
errorSomething failed — can reset to idle

State Transitions

Once a transaction is registered, it enters the state machine and transitions automatically:

StateMeaning
pendingSubmitted to Cardano, awaiting on-chain confirmation
confirmedFound on-chain, database update in progress (or not needed)
updatedDatabase synchronized — terminal success
failedDatabase sync retries exhausted — terminal failure
expiredNot confirmed on-chain within the timeout window — terminal

All database updates are idempotent — if a sync is interrupted and retried, it produces the same result.

Common Pitfall: confirmed is NOT terminal

Don't refetch data when the state reaches confirmed — the database hasn't synced yet. Wait for updated:

// ❌ WRONG — data will be stale
if (status.state === "confirmed") refetchData();

// ✅ CORRECT — wait for DB sync (~30s after on-chain)
if (status.state === "updated") refetchData();

The confirmed state means "found on-chain" but the gateway is still syncing to the database. Only updated guarantees fresh data.

Architecture Pattern: Check-Effect-Interact (CEI)

Andamio transactions follow a layered CEI pattern across client and gateway:

PhaseClientGateway
CheckValidate params (Zod schemas)Validate eligibility, balances, permissions
EffectSign transaction in walletConfirm transaction on-chain
InteractSubmit to blockchainSync state to database

The client never writes to the database directly — the gateway handles all database synchronization after on-chain confirmation. This ensures the blockchain remains the source of truth.

Data Model: Hash On-Chain, Data Off-Chain

Andamio stores hashes on the blockchain, not raw data:

On-chain:  assignment_info: "sha256:abc123def456..."
Off-chain: evidence: { url: "https://...", text: "My submission notes" }
AspectOn-ChainOff-Chain
StoredCommitment hash (32 bytes)Full evidence payload
CostMinimal (~0.02 ADA)Database storage
PrivacyPublic but opaqueAccess-controlled
VerifiabilityAnyone can verifyHash proves integrity

Why this pattern?

  • Blockchain space is expensive — hashes are small and fixed-size
  • Privacy — raw evidence (URLs, text, files) never touches the chain
  • Verifiability — anyone can prove off-chain data matches the on-chain hash

Transaction Types

Andamio supports 17 transaction types across three categories:

Global

TransactionType KeyRoleDB Sync
Mint Access Tokenaccess_token_mintUserNo

Course

TransactionType KeyRoleDB Sync
Create Coursecourse_createOwnerYes
Enroll in Coursecourse_enrollStudentNo
Manage Teachersteachers_updateOwnerYes
Manage Modulesmodules_manageTeacherYes
Submit Assignmentassignment_submitStudentYes
Assess Assignmentsassessment_assessTeacherYes
Claim Course Credentialcredential_claimStudentNo

Project

TransactionType KeyRoleDB Sync
Create Projectproject_createOwnerYes
Manage Managersmanagers_manageOwnerYes
Manage Taskstasks_manageManagerYes
Commit to Taskproject_joinContributorYes
Submit Task Worktask_submitContributorYes
Assess Taskstask_assessManagerYes
Claim Project Credentialproject_credential_claimContributorYes
Fund Treasurytreasury_fundUserNo

"No" DB Sync means the blockchain is the sole source of truth for that data. "Yes" DB Sync means the Gateway automatically synchronizes on-chain state to the database after confirmation.

TX Type Mapping

Some build endpoints share the same state machine type. This is intentional — the on-chain action is identical, so Andamioscan indexes them the same way.

Course Assignments

Build EndpointTX TypeNotes
/tx/course/student/assignment/commitassignment_submitFirst commitment to a module
/tx/course/student/assignment/updateassignment_submitUpdate evidence on existing commitment

Both use assignment_submit because the on-chain action is the same: spending and recreating the enrollment UTxO with updated assignment info. The state machine's upsert behavior handles both cases correctly.

Project Tasks

Build EndpointTX TypeNotes
/tx/project/contributor/task/commit (first time)project_joinMints contributor token
/tx/project/contributor/task/commit (repeat)project_joinNo mint for existing contributors

The state machine tries the "join" classifier first, then falls back to "task submit" for already-enrolled contributors. From the API consumer's perspective, use project_join for all task commits.

See also: API Integration Guide for the complete endpoint-to-type mapping table.

Integration Flow

The full flow for building, signing, and tracking a transaction:

1. POST /api/v2/tx/{build-endpoint}         → unsigned_tx (+ course_id/project_id for creates)
2. wallet.signTx(unsigned_tx)                → signed_tx
3. wallet.submitTx(signed_tx)                → tx_hash
4. POST /api/v2/tx/register                  → { state: "pending" }
5. GET  /api/v2/tx/stream/{tx_hash}   (SSE)  → real-time state change events
   OR
   GET  /api/v2/tx/status/{tx_hash}  (poll)  → check state periodically

All build endpoints require authentication via Authorization: Bearer <jwt> or X-API-Key: <key>.

Registration

After submitting a transaction to the Cardano network, register it with the state machine:

POST /api/v2/tx/register

{
  "tx_hash": "64-char hex hash from wallet.submitTx()",
  "tx_type": "course_create",
  "instance_id": "optional course_id or project_id",
  "metadata": {}
}

Most transaction types require no metadata. Only three types need it:

TypeRequired Metadata
course_createowner_alias (auto-captured, no action needed)
project_jointask_hash (required — 64 hex chars)
project_credential_claimtask_hash (required — 64 hex chars)

Registering the same tx_hash twice returns 409 Conflict.

Commitment Lifecycle

Several transaction types involve commitments — records that link a participant to a piece of work (an assignment or a task). These commitments follow a draft-to-chain lifecycle that gives developers two integration paths.

Two Paths to On-Chain

Draft-first is recommended because it gives users immediate feedback — they can see their pending commitment in the UI before the blockchain transaction confirms. But if a client goes straight to chain (skipping the draft step), the state machine handles it automatically by creating the DB record on confirmation.

Assignment Commitments (Courses)

When a student submits an assignment (assignment_submit):

  1. Optional: Create a draft commitment via the API — the student's intent is saved immediately
  2. Required: Build and submit the on-chain transaction, then register it
  3. Automatic: On confirmation, the state machine either updates the existing draft or creates a new record, setting the status to ON_CHAIN

Status progression: DRAFTON_CHAINACCEPTED or REFUSED (after teacher assessment)

Task Commitments (Projects)

When a contributor commits to a task (project_join):

  1. Optional: Create a draft commitment via the API
  2. Required: Build and submit the on-chain transaction, then register with task_hash in metadata
  3. Automatic: On confirmation, the state machine confirms the commitment and links the contributor to the task

Status progression: DRAFTSUBMITTEDACCEPTED or REFUSEDREWARDED

Note: The API previously returned a COMMITTED status between DRAFT and SUBMITTED. As of v2.1.1-rc12, the API normalizes COMMITTED to SUBMITTED at read time — commit and submit are the same action on-chain.

Note: A contributor's first commit to a project also "joins" the project by minting a contributor token. Subsequent commits by the same contributor do not mint again. The state machine handles both cases transparently.

Task Drafts

Managers can also create tasks as drafts in the database before publishing them on-chain via tasks_manage. When the on-chain transaction confirms, the state machine automatically matches new on-chain tasks to existing drafts by content hash and updates their status to ON_CHAIN.

Monitoring

EndpointPurpose
GET /api/v2/tx/status/:tx_hashCurrent state of a single transaction
GET /api/v2/tx/pendingAll tracked transactions for the authenticated user
GET /api/v2/tx/typesList all valid transaction types
GET /api/v2/tx/stream/:tx_hashSSE stream — real-time state change events

SSE Stream Events

EventWhenPayload
stateImmediately on connectCurrent state
state_changeEach transition{ tx_hash, old_state, new_state, timestamp }
completeTerminal state reached{ tx_hash, final_state } — stream auto-closes

Self-Healing

If a transaction is confirmed on-chain but the database update was missed (for example, due to a service restart), the Gateway automatically detects and repairs the inconsistency when related data is next accessed. This is transparent to API consumers — no manual intervention is needed.

Example Flow: Claiming a Course Credential

A complete walkthrough of one transaction from click to completion.

Prerequisites

RequirementDescription
EnrolledStudent holds an enrollment token for the course
Assignments completeAll required modules assessed as ACCEPTED
Wallet connectedBrowser wallet with ~1.5 ADA for fees

The Flow

StepLayerWhat Happens
1. Click "Claim Credential"UIuseTransaction().execute({ txType: "credential_claim", ... })
2. BuildClient → GatewayPOST /api/v2/tx/course/student/credential/claim → unsigned CBOR
3. SignClient → Walletwallet.signTx(cbor) — user approves in wallet popup
4. SubmitClient → Blockchainwallet.submitTx(signed) → txHash
5. RegisterClient → GatewayPOST /api/v2/tx/register with { tx_hash, tx_type: "credential_claim" }
6. MonitorClient ← GatewaySSE stream at /api/v2/tx/stream/{txHash}
7. ConfirmGateway~30s later: pendingconfirmedupdated
8. CompleteUIToast: "Credential claimed!" — credential NFT now in wallet

What the User Sees

[Click]       → Button shows spinner: "Building..."
[~2 seconds]  → Wallet popup appears
[Sign]        → Button: "Submitting..."
[~1 second]   → Toast: "Credential claim submitted"
[~30 seconds] → Toast updates: "✓ Credential claimed!"

CEI Pattern Applied

PhaseWhereWhat
CheckGateway (build)Validates enrollment, all assignments accepted, sufficient ADA
EffectBlockchainBurns enrollment token, mints credential NFT
InteractGateway (sync)No DB update needed — credential_claim is on-chain only

This transaction has DB Sync: No because the credential NFT in the wallet is the sole proof of completion — the blockchain is the source of truth.

See also: Claim Course Credential API Reference for full request/response details.

Important Notes

  • Course and project creation return IDs: The build response includes a course_id or project_id. Save these — they're the on-chain policy IDs used for all subsequent operations.
  • Single teacher/manager at creation: Courses start with one teacher and projects start with one manager (the creator). Use the manage endpoints to add more after creation.
  • Not yet implemented: Task Leave and Task Update sub-actions have build endpoints but are not yet tracked by the state machine. The on-chain effects still work.

Implementation Reference

For working code examples, see the andamio-app-template:

ResourceDescription
How Transactions WorkREADME section with diagrams, code examples, and the 7-step flow
useTransaction hookFull implementation with PHASE 0-6 inline comments
useTxStream hookSSE stream handling for real-time state updates

The useTransaction hook follows explicit phases that map to the lifecycle above:

PhasePurpose
PHASE 0Check wallet connection
PHASE 1Validate params (Zod schema)
PHASE 2Build (POST to gateway → unsigned CBOR)
PHASE 3Sign (user approves in wallet)
PHASE 4Submit (to Cardano blockchain)
PHASE 5Register (with gateway + global store)
PHASE 6Success (update state, fire callbacks)