Skip to Content
Pact lifecycle

Pact lifecycle

Every agreement uses the same six-state machine:

CREATED --fund all--> ACTIVE --propose--> PROPOSED --cosign all--> SETTLED | | | \ | fundBy | performBy | \ object v v | v CANCELLED SETTLED | DISPUTED --verdict--> SETTLED (refund) | +--objectBy--> SETTLED ACTIVE or PROPOSED --unanimous cancel before deadline--> CANCELLED

SETTLED and CANCELLED are terminal. No later call can change them.

CREATED

The pact exists but no named party is bound merely because it was included. Parties fund their own deposit + bond; an anonymous participant may atomically reserve an open slot while funding.

The pact activates immediately when every required party is funded, minParties is met, and all open slots are filled. Any unfilled optional named party is dropped. Otherwise, at fundBy:

  • required parties funded and minParties met → ACTIVE
  • requirement missing or participation below minimum → CANCELLED, full refund

Before activation, a funder may withdraw independently.

ACTIVE

Work occurs outside the protocol. Parties may upload deliverables, and the authorized proposer submits evidence with a basis-point distribution.

proposer may be a fixed party or "any". Symmetric games should use "any" so a losing fixed proposer cannot refuse to propose and force a refund. Compare-and-swap guarantees that only the first valid proposal wins.

If no proposal exists at performBy, poke settles a full refund.

PROPOSED

The proposal and its inputHash are frozen. Every non-proposer party can:

  • cosign to approve explicitly
  • object once with a reason
  • remain silent

Co-signatures from every non-proposer party settle immediately. The first valid objection enters DISPUTED. If objectBy passes without an objection, silence is consent and the pact settles as proposed.

DISPUTED

The evaluator receives the frozen terms, evidence, proposal, objection, and any sandbox check results. It may uphold the proposal or replace it with any valid distribution.

A valid evaluator-signed verdict settles immediately. If no verdict arrives by verdictBy, poke applies the server-owned policy frozen into the pact. Production uses onFailure: "refund", so every stake and bond is returned.

Relative deadlines

Windows are durations from the event that begins each phase:

DeadlineDerived fromDefault
fundBycreatedAt + windows.fund24 hours
performByactiveAt + windows.perform168 hours
objectByproposedAt + windows.object72 hours
verdictBydisputedAt + evaluator.timeoutMs10 minutes

Relative windows ensure a late-but-valid delivery still receives the full review window.

Poke and liveness

POST /pacts/:id/poke is unsigned and permissionless. It executes only a deadline-driven, deterministic transition that is already due. Calling it too early is a no-op/error and cannot change the pact.

The built-in keeper scans every 30 seconds for convenience, but it has no protocol privilege. Any party or external keeper can preserve liveness.

Mutual cancellation

During ACTIVE or PROPOSED, all parties may sign a cancellation before the next deadline. The cancellation payload binds stateNonce and an expiresAt no later than that deadline. After the deadline, the timeout path wins.

Every party prepares the same action-bound authorization, then one party submits the collected signature objects through stdin; the JSON array is rejected in argv:

: "${PACT_CANCEL_EXPIRES_AT:?set to Unix milliseconds before the current deadline}" umask 077 pact cancel <pactId> --expires-at "$PACT_CANCEL_EXPIRES_AT" > party-cancel-me.json jq -s '[.[].signature]' party-cancel-*.json | pact cancel <pactId> --expires-at "$PACT_CANCEL_EXPIRES_AT" --signatures-stdin

Exchange these short-lived authorizations through an authenticated secure channel. Keep any unavoidable file mode-0600 and delete every copy after submission, expiry, or a nonce change.

Concurrency guarantees

Every transition is a conditional update on the expected state and stateNonce. Competing funds, proposals, objections, co-signatures, cancellations, and timeouts can have only one winner. Terminal payouts are written as pending, executed idempotently, and resumed after a process failure until marked done.