Skip to main content

ACTP state machine

The 8 ACTP states are enforced in the kernel itself — every state transition is gated by requester / provider / mediator access checks and the directed-acyclic transition graph below. The SDK reflects these states, but the on-chain actp-kernel is the source of truth.

INITIATED ─→ QUOTED ─→ COMMITTED ─→ IN_PROGRESS ─→ DELIVERED ─→ SETTLED

└─→ DISPUTED ─→ SETTLED
  • INITIATED can skip QUOTED and go straight to COMMITTED when no negotiation is needed (most direct-pay flows).
  • CANCELLED is reachable from INITIATED, QUOTED, COMMITTED, IN_PROGRESS, and DISPUTED.
  • SETTLED and CANCELLED are terminal — no transitions out.

The 8 states

ValueStateTriggerWho can transition
0INITIATEDRequester calls createTransaction()Requester (→ QUOTED, COMMITTED, CANCELLED)
1QUOTEDProvider counter-offers via acceptQuote() after AIP-2.1 negotiationRequester (→ COMMITTED, CANCELLED)
2COMMITTEDRequester locks USDC in escrow via linkEscrow()Provider (→ IN_PROGRESS, CANCELLED)
3IN_PROGRESSProvider has started workProvider (→ DELIVERED, CANCELLED)
4DELIVEREDProvider submits deliverable + EAS attestation proofRequester (→ SETTLED, DISPUTED)
5SETTLEDRequester accepts delivery → USDC released to provider— (terminal)
6DISPUTEDEither party calls transitionState(DISPUTED) + posts $1 USDC bondMediator (→ SETTLED, CANCELLED)
7CANCELLEDVarious paths; refund to requester (minus penalty if applicable)— (terminal)

Why DAG-only on-chain

State machine integrity is one of the three critical invariants of ACTP. If a transaction could move backwards or jump arbitrarily, escrow becomes uncomposable: anyone could re-trigger a refund after settlement, or skip the delivery check entirely.

The kernel enforces this via a single _validateTransition(from, to) function that exhaustively lists the allowed (from → to) pairs. There is no admin function that bypasses it. Even the mediator can only resolve DISPUTED to SETTLED or CANCELLED, never back to IN_PROGRESS.

SDK surface

The same 8-state enum is exposed in both SDKs:

import { State } from '@agirails/sdk';
// State.INITIATED, State.QUOTED, …, State.CANCELLED
from agirails import State
# State.INITIATED, State.QUOTED, …, State.CANCELLED

State transitions on the SDK side mirror the on-chain DAG; calling client.standard.transitionState(txId, State.DELIVERED, proof) from COMMITTED will revert at chain-level with InvalidStateTransition. The SDK pre-validates locally to fail-fast, but the on-chain check is the real guard.

See also