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:
- User clicks action — your app handles the UI
- Request transaction — POST to the appropriate build endpoint
- Return unsigned CBOR — the API builds the transaction
- Prompt wallet signature — show the wallet popup
- User approves & signs — CIP-30 wallet API
- Submit signed tx — wallet submits to Cardano
- Register for confirmation — tell Andamio to track it
- Block confirmation — Cardano confirms (~20-30s)
- Sync DB + push event — gateway syncs, you get notified
- Display success — update your UI
Lifecycle Diagram
| Step | Who | What Happens |
|---|---|---|
| BUILD | API Gateway | Builds an unsigned transaction (CBOR) |
| SIGN | User's wallet | User signs the transaction in their browser wallet |
| SUBMIT | User's wallet | Signed transaction is submitted to the Cardano network |
| REGISTER | Frontend | Calls the registration endpoint with the transaction hash and type |
| CONFIRM | Gateway (automatic) | Polls the blockchain until the transaction appears on-chain |
| UPDATE | Gateway (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:
| State | Meaning |
|---|---|
idle | Ready to start a new transaction |
fetching | Building unsigned CBOR via API |
signing | Waiting for user to approve in wallet |
submitting | Submitting signed transaction to Cardano |
success | Transaction submitted, hash received |
error | Something failed — can reset to idle |
State Transitions
Once a transaction is registered, it enters the state machine and transitions automatically:
| State | Meaning |
|---|---|
pending | Submitted to Cardano, awaiting on-chain confirmation |
confirmed | Found on-chain, database update in progress (or not needed) |
updated | Database synchronized — terminal success |
failed | Database sync retries exhausted — terminal failure |
expired | Not 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:
| Phase | Client | Gateway |
|---|---|---|
| Check | Validate params (Zod schemas) | Validate eligibility, balances, permissions |
| Effect | Sign transaction in wallet | Confirm transaction on-chain |
| Interact | Submit to blockchain | Sync 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" }| Aspect | On-Chain | Off-Chain |
|---|---|---|
| Stored | Commitment hash (32 bytes) | Full evidence payload |
| Cost | Minimal (~0.02 ADA) | Database storage |
| Privacy | Public but opaque | Access-controlled |
| Verifiability | Anyone can verify | Hash 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
| Transaction | Type Key | Role | DB Sync |
|---|---|---|---|
| Mint Access Token | access_token_mint | User | No |
Course
| Transaction | Type Key | Role | DB Sync |
|---|---|---|---|
| Create Course | course_create | Owner | Yes |
| Enroll in Course | course_enroll | Student | No |
| Manage Teachers | teachers_update | Owner | Yes |
| Manage Modules | modules_manage | Teacher | Yes |
| Submit Assignment | assignment_submit | Student | Yes |
| Assess Assignments | assessment_assess | Teacher | Yes |
| Claim Course Credential | credential_claim | Student | No |
Project
| Transaction | Type Key | Role | DB Sync |
|---|---|---|---|
| Create Project | project_create | Owner | Yes |
| Manage Managers | managers_manage | Owner | Yes |
| Manage Tasks | tasks_manage | Manager | Yes |
| Commit to Task | project_join | Contributor | Yes |
| Submit Task Work | task_submit | Contributor | Yes |
| Assess Tasks | task_assess | Manager | Yes |
| Claim Project Credential | project_credential_claim | Contributor | Yes |
| Fund Treasury | treasury_fund | User | No |
"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 Endpoint | TX Type | Notes |
|---|---|---|
/tx/course/student/assignment/commit | assignment_submit | First commitment to a module |
/tx/course/student/assignment/update | assignment_submit | Update 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 Endpoint | TX Type | Notes |
|---|---|---|
/tx/project/contributor/task/commit (first time) | project_join | Mints contributor token |
/tx/project/contributor/task/commit (repeat) | project_join | No 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 periodicallyAll 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:
| Type | Required Metadata |
|---|---|
course_create | owner_alias (auto-captured, no action needed) |
project_join | task_hash (required — 64 hex chars) |
project_credential_claim | task_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):
- Optional: Create a draft commitment via the API — the student's intent is saved immediately
- Required: Build and submit the on-chain transaction, then register it
- Automatic: On confirmation, the state machine either updates the existing draft or creates a new record, setting the status to
ON_CHAIN
Status progression: DRAFT → ON_CHAIN → ACCEPTED or REFUSED (after teacher assessment)
Task Commitments (Projects)
When a contributor commits to a task (project_join):
- Optional: Create a draft commitment via the API
- Required: Build and submit the on-chain transaction, then register with
task_hashin metadata - Automatic: On confirmation, the state machine confirms the commitment and links the contributor to the task
Status progression: DRAFT → SUBMITTED → ACCEPTED or REFUSED → REWARDED
Note: The API previously returned a
COMMITTEDstatus betweenDRAFTandSUBMITTED. As of v2.1.1-rc12, the API normalizesCOMMITTEDtoSUBMITTEDat 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
| Endpoint | Purpose |
|---|---|
GET /api/v2/tx/status/:tx_hash | Current state of a single transaction |
GET /api/v2/tx/pending | All tracked transactions for the authenticated user |
GET /api/v2/tx/types | List all valid transaction types |
GET /api/v2/tx/stream/:tx_hash | SSE stream — real-time state change events |
SSE Stream Events
| Event | When | Payload |
|---|---|---|
state | Immediately on connect | Current state |
state_change | Each transition | { tx_hash, old_state, new_state, timestamp } |
complete | Terminal 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
| Requirement | Description |
|---|---|
| Enrolled | Student holds an enrollment token for the course |
| Assignments complete | All required modules assessed as ACCEPTED |
| Wallet connected | Browser wallet with ~1.5 ADA for fees |
The Flow
| Step | Layer | What Happens |
|---|---|---|
| 1. Click "Claim Credential" | UI | useTransaction().execute({ txType: "credential_claim", ... }) |
| 2. Build | Client → Gateway | POST /api/v2/tx/course/student/credential/claim → unsigned CBOR |
| 3. Sign | Client → Wallet | wallet.signTx(cbor) — user approves in wallet popup |
| 4. Submit | Client → Blockchain | wallet.submitTx(signed) → txHash |
| 5. Register | Client → Gateway | POST /api/v2/tx/register with { tx_hash, tx_type: "credential_claim" } |
| 6. Monitor | Client ← Gateway | SSE stream at /api/v2/tx/stream/{txHash} |
| 7. Confirm | Gateway | ~30s later: pending → confirmed → updated |
| 8. Complete | UI | Toast: "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
| Phase | Where | What |
|---|---|---|
| Check | Gateway (build) | Validates enrollment, all assignments accepted, sufficient ADA |
| Effect | Blockchain | Burns enrollment token, mints credential NFT |
| Interact | Gateway (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_idorproject_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:
| Resource | Description |
|---|---|
| How Transactions Work | README section with diagrams, code examples, and the 7-step flow |
| useTransaction hook | Full implementation with PHASE 0-6 inline comments |
| useTxStream hook | SSE stream handling for real-time state updates |
The useTransaction hook follows explicit phases that map to the lifecycle above:
| Phase | Purpose |
|---|---|
| PHASE 0 | Check wallet connection |
| PHASE 1 | Validate params (Zod schema) |
| PHASE 2 | Build (POST to gateway → unsigned CBOR) |
| PHASE 3 | Sign (user approves in wallet) |
| PHASE 4 | Submit (to Cardano blockchain) |
| PHASE 5 | Register (with gateway + global store) |
| PHASE 6 | Success (update state, fire callbacks) |