Storage Interfaces
Key0 defines three storage interfaces that back the challenge lifecycle. All state mutations go through these interfaces to guarantee atomicity and prevent race conditions.IChallengeStore— Manages challenge records and atomic state transitions.ISeenTxStore— Prevents double-spend by tracking consumed transaction hashes.IAuditStore— Optional write-only audit trail for every state transition.
IChallengeStore
The primary store. Every challenge flows throughcreate and one or more transition calls.
Methods
Retrieve a challenge by its unique identifier. Returns
null if the challenge does not exist.Find a non-expired challenge in the
PENDING state for the given requestId. Used for idempotency: if the same client sends the same requestId twice, the engine returns the existing challenge instead of creating a duplicate. Returns null when no active challenge exists.Persist a new challenge record. Implementations must reject with an error if a record with the same
challengeId already exists. The optional meta parameter is forwarded to the audit store when one is configured.Atomically move a challenge from
fromState to toState, optionally writing additional fields via updates. Returns true if the transition succeeded, or false if the current state did not match fromState (optimistic concurrency control). This is the only safe way to change a challenge’s state.Return all records in the
PAID state whose paidAt timestamp is older than minAgeMs milliseconds and that have a fromAddress set. Used by the refund cron to locate undelivered payments eligible for automatic refund.ISeenTxStore
Guards against double-spend attacks by ensuring each on-chain transaction hash is consumed at most once.Methods
Check whether a transaction hash has already been claimed. Returns the
challengeId it was used for, or null if the hash is unclaimed.Atomically mark a transaction hash as consumed for the given challenge. Returns
true if the hash was successfully stored, or false if it was already claimed (double-spend attempt). Implementations must use an atomic set-if-not-exists operation (e.g., Redis SET NX, Postgres INSERT ... ON CONFLICT DO NOTHING).IAuditStore
An optional, write-only audit trail. When configured, every call toIChallengeStore.create and IChallengeStore.transition also appends an entry here. Implementations must not expose update or delete operations.
Methods
Append a single audit entry. The
id field is omitted from the input and assigned by the store (e.g., BIGSERIAL in Postgres, auto-incrementing index in Redis).Retrieve the full transition history for a challenge, ordered chronologically. Useful for debugging and compliance.
Supporting Types
TransitionMeta
Metadata attached tocreate and transition calls, forwarded to the audit store.
| Field | Type | Description |
|---|---|---|
actor | AuditActor | Who or what triggered the state transition. |
reason | string (optional) | Human-readable explanation for the transition. |
AuditEntry
A single immutable record of a state transition.| Field | Type | Description |
|---|---|---|
id | string | number | Store-assigned identifier (BIGSERIAL for Postgres, index for Redis). |
challengeId | string | The challenge this entry belongs to. |
requestId | string | The original request ID. Survives challenge cleanup. |
clientAgentId | string (optional) | The agent that initiated the payment flow. |
fromState | ChallengeState | null | Previous state. null for the initial creation entry. |
toState | ChallengeState | State after the transition. |
actor | AuditActor | Who triggered the transition (engine, cron, admin, or system). |
reason | string (optional) | Human-readable reason for the transition. |
updates | Record<string, unknown> | null | Snapshot of the fields changed alongside the transition. |
createdAt | Date | Timestamp of when the transition occurred. |
ChallengeTransitionUpdates
Fields that may be written alongside a state transition. Defined as a partial pick fromChallengeRecord.
| Field | Type | Written during |
|---|---|---|
txHash | string | PENDING to PAID |
paidAt | Date | PENDING to PAID |
accessGrant | object | PENDING to PAID |
fromAddress | string | PENDING to PAID |
deliveredAt | Date | PAID to DELIVERED |
refundTxHash | string | REFUND_PENDING to REFUNDED |
refundedAt | Date | REFUND_PENDING to REFUNDED |
refundError | string | REFUND_PENDING to REFUND_FAILED |
Built-in Implementations
Key0 ships with Redis and Postgres implementations for all three interfaces.| Class | Interface | Backend |
|---|---|---|
RedisChallengeStore | IChallengeStore | Redis |
RedisSeenTxStore | ISeenTxStore | Redis |
RedisAuditStore | IAuditStore | Redis |
PostgresChallengeStore | IChallengeStore | Postgres |
PostgresSeenTxStore | ISeenTxStore | Postgres |
PostgresAuditStore | IAuditStore | Postgres |
transition and markUsed to guarantee correctness under concurrent access. Postgres implementations rely on row-level locking and INSERT ... ON CONFLICT for the same guarantees.
IChallengeStore and ISeenTxStore are required. IAuditStore is optional. When omitted, state transitions still function correctly but no audit trail is recorded.
