Chio/Docs
LOGIN · JOIN

EconomyTools

Contracts

A contract is a priced, signed, bilateral agreement to perform work: a bid, an ask, and an acceptance bound to a funds reservation.

A priced agreement between a buying agent and a publishing provider is three signed bodies under chio.marketplace.*, each digesting the one before it, plus a reservation that makes acceptance admissible. Their exact wire shapes are below. The venue those bodies move through is on The Open Market and the registry a contract is priced against is on Capability Discovery. The kernel's guard verdict interface shares the word contract and nothing else.


What a contract is

A contract is the priced bilateral agreement between a buying agent and the operator publishing a listing. It is not one record. It is a chain of three independently signed artifacts produced by the two entry points of chio_open_market::bidding, bid and accept: a BidRequest from the buyer, an AskResponse from the provider, and an AcceptedBid that binds both to a verified funds hold.

There is no Contract type in the crate, and that is the point. Each leg is wrapped in a SignedExportEnvelope carrying body, signerKey, and signature over the RFC 8785 canonical JSON of that body. Different keys sign different legs: the buyer signs the bid, the provider signs the ask and the capability token inside it, and the buyer signs the acceptance. A verifier can check any leg in isolation and can reconstruct the whole agreement from the last one.

chio-open-market is pure logic: no I/O, no runtime state, #![forbid(unsafe_code)]. It takes already-fetched signed artifacts and is the fail-closed judge of whether they satisfy market rules. It holds no balances, dispatches no tools, and issues no receipts; the module notes that it deliberately does not depend on a receipt store.

chio_open_market::biddingrust
pub fn bid(
    request: &SignedBidRequest,
    context: BidMintContext<'_>,
) -> Result<SignedAskResponse, BiddingError>;

pub fn accept(
    ask: &SignedAskResponse,
    reservation: &VerifiedReservationReceipt,
    acceptor_keypair: &Keypair,
    accepted_at: u64,
) -> Result<SignedAcceptedBid, BiddingError>;

The three signed legs

The buyer names a ceiling and a window. The provider quotes its own advertised price and mints a token against it. The buyer accepts, and only then does a funds hold enter the record.

rendering
The three legs of a contract, plus the reservation leg that acceptance is measured against. The provider never sees the reservation receipt; it sees the acceptance that names it.

Leg one: bidRequest

Schema chio.marketplace.bid-request.v1, wrapped as SignedBidRequest and signed by the buying agent. Every string field is rejected when empty or when it carries surrounding whitespace.

FieldTypeMeaning
schemaStringMust equal chio.marketplace.bid-request.v1
agent_idStringThe buyer. Carried forward unchanged onto the ask and the acceptance
listing_idStringThe published listing being bid against
max_price_per_callMonetaryAmountA ceiling, not an offer. units must be greater than zero
window_secondsu64Must be greater than zero. Sets both the ask expiry and the token expiry
requested_scopeRequestedScopeThe narrowing applied to the minted token
issued_atu64Unix seconds at which the buyer signed

RequestedScope carries server_id, tool_name, an optional max_invocations: Option<u32>, and capability_scope_prefix. All three strings are required and non-empty.

Leg two: askResponse

Schema chio.marketplace.ask-response.v1, wrapped as SignedAskResponse and signed by the provider's issuer keypair. The provider assembles it inside a BidMintContext, which pairs the resolved listing with the issuer key, the buyer's subject key, an opaque token_id, and the evaluation time now.

FieldTypeMeaning
bid_digestStringSHA-256 hex over the canonical JSON of the BidRequest body
quoted_priceMonetaryAmountA clone of the listing's advertised price_per_call, never the buyer's ceiling
token_offerCapabilityTokenMinted and separately signed by the issuer, bound to the buyer subject
listing_id, agent_idStringCopied from the resolved listing and the bid
issued_atu64The provider's now, not the bid's issued_at
expires_atu64issued_at + window_seconds, checked for overflow

The venue is posted-price. A bid whose ceiling sits below the advertised price is refused with BidCeilingTooLow rather than quoted down, and a bid whose ceiling sits above it is quoted at the advertised price rather than at the ceiling.

Leg three: acceptedBid

Schema chio.marketplace.accepted-bid.v1, wrapped as SignedAcceptedBid and signed by the acceptor, whose public key must equal ask.body.token_offer.subject. This is the settlement-facing leg.

FieldTypeMeaning
bid_digestStringCopied from the ask, so leg one stays reachable from leg three
ask_digestStringRecomputed over the AskResponse body at acceptance time
bid_receipt_idStringThe receipt id from the verified funds reservation
quoted_priceMonetaryAmountCopied from the ask
accepted_atu64Must fall in [ask.issued_at, ask.expires_at)
token_id, token_subject, token_expires_atString, PublicKey, u64The grant this contract paid for, named but not embedded

The three market bodies serialize their fields as camelCase. The capability token nested inside the ask does not rename, so its fields stay snake_case on the wire. The shapes below are illustrative values in real field names, not captured production data:

contract-legs.jsonjson
{
  "bidRequest": {
    "body": {
      "schema": "chio.marketplace.bid-request.v1",
      "agentId": "agent-alpha",
      "listingId": "listing-1",
      "maxPricePerCall": { "units": 200, "currency": "USD" },
      "windowSeconds": 300,
      "requestedScope": {
        "serverId": "demo-server",
        "toolName": "search",
        "maxInvocations": 10,
        "capabilityScopePrefix": "tools:search"
      },
      "issuedAt": 120
    },
    "signerKey": "3f9a...",
    "signature": "b71c..."
  },
  "askResponse": {
    "body": {
      "schema": "chio.marketplace.ask-response.v1",
      "listingId": "listing-1",
      "agentId": "agent-alpha",
      "bidDigest": "6c1f...",
      "quotedPrice": { "units": 100, "currency": "USD" },
      "tokenOffer": {
        "schema": "chio.capability.v1",
        "id": "token-1",
        "issuer": "a04e...",
        "subject": "3f9a...",
        "scope": {
          "grants": [
            {
              "server_id": "demo-server",
              "tool_name": "search",
              "operations": ["invoke"],
              "max_invocations": 10,
              "max_cost_per_invocation": { "units": 100, "currency": "USD" },
              "max_total_cost": { "units": 1000, "currency": "USD" }
            }
          ]
        },
        "issued_at": 120,
        "expires_at": 420
      },
      "issuedAt": 120,
      "expiresAt": 420
    },
    "signerKey": "a04e...",
    "signature": "5d2b..."
  },
  "acceptedBid": {
    "body": {
      "schema": "chio.marketplace.accepted-bid.v1",
      "listingId": "listing-1",
      "agentId": "agent-alpha",
      "bidDigest": "6c1f...",
      "askDigest": "e8b3...",
      "bidReceiptId": "receipt-42",
      "quotedPrice": { "units": 100, "currency": "USD" },
      "acceptedAt": 180,
      "tokenId": "token-1",
      "tokenSubject": "3f9a...",
      "tokenExpiresAt": 420
    },
    "signerKey": "3f9a...",
    "signature": "9a17..."
  }
}

Identity and digest chaining

Every digest in the chain is sha256_hex(canonical_json_bytes(body)): SHA-256 over RFC 8785 canonical JSON, rendered as 64 lowercase hex characters. The digest covers the envelope's body only. The signerKey and signature are verified separately, so a leg's digest and a leg's authenticity are two independent questions and both must be answered.

The chain runs one way. The ask commits to the bid through bid_digest. The acceptance commits to the ask through ask_digest and repeats bid_digest verbatim, so a holder of the acceptance can address all three bodies. The funds reservation commits to the ask through its own ask_digest, which is structurally checked to be 64 lowercase hex characters before the market will read it.

Digest linkage is not enough by itself, so bid also proves that the listing, the pricing hint, the publisher, and the minting key are the same authority before it mints anything:

  • Listing identity. The bid's listing_id must equal the resolved listing id, the pricing hint must name the same listing id, and the pricing and listing namespaces must match after normalization. Otherwise ListingMismatch.
  • Provider identity. The pricing hint's provider_operator_id must equal the publisher's operator_id and the listing's namespace_ownership.owner_id, and both the pricing hint and the listing must be signed by the namespace's signer_public_key.
  • Minting authority. The issuer keypair supplied in the BidMintContext must hold the public key returned by provider_signing_key(listing), which is the key that signed the advertised price. The party that quotes and the party that mints are the same party, or the bid is refused with AuthorityMismatch.
  • Token authority. At acceptance, ask.body.token_offer.issuer must equal ask.signer_key, the token signature must verify on its own, and the token window must fully cover the ask window: token.issued_at no later than ask.issued_at and token.expires_at no earlier than ask.expires_at.

The denials are named, and a caller can discriminate them:

BiddingErrorRaised when
BidSignatureInvalidThe bid envelope does not verify against its own signer key
ListingSignatureInvalid, PricingSignatureInvalidThe listing or the pricing hint does not verify. accept reuses PricingSignatureInvalid for an ask envelope that does not verify
ListingNotActiveListing status is suspended, superseded, revoked, or retired
PricingExpiredThe pricing hint is outside [issued_at, expires_at). Also raised when accepted_at reaches the ask expiry
ListingStaleThe listing is inadmissible for a reason other than expired pricing, such as a replica that is not fresh
CurrencyMismatch, BidCeilingTooLowThe bid currency differs from the advertised currency, or the ceiling is below the advertised price
ScopeOutsideListingThe requested scope prefix is not covered by the advertised scope, or the requested server_id is not the listing subject
WindowOutOfBoundsA zero or overflowing window, or a token window that does not cover the ask window
TotalCostOverflowAdvertised price times max_invocations exceeds u64
TokenSignatureInvalid, AuthorityMismatchThe token offer does not verify, or a key does not belong where the flow requires it
ReservationReceiptInvalidThe funds reservation is unverifiable, unbound, or insufficient

The reservation that makes acceptance admissible

accept does not take a signed reservation blob. It takes a VerifiedReservationReceipt, a type whose fields are private and whose only constructor is VerifiedReservationReceipt::from_signed. That constructor demands an expected_reservation_authority: &PublicKey, so the caller has to name the settlement authority it trusts before the market will look at the hold at all. An unverifiable body, a signer that is not the expected authority, or a failed signature all collapse to ReservationReceiptInvalid. Acceptance without a hold is not a slower path. It is unreachable.

The receipt itself is chio.marketplace.reservation-receipt.v1, wrapped as SignedReservationReceipt. Its body carries receipt_id, agent_id, listing_id, ask_digest, and reserved_amount. The market crate defines the shape and refuses to fetch it; the hold is somebody else's job and the receipt is the only thing crossing the boundary.

reservation-receipt.jsonjson
{
  "body": {
    "schema": "chio.marketplace.reservation-receipt.v1",
    "receiptId": "receipt-42",
    "agentId": "agent-alpha",
    "listingId": "listing-1",
    "askDigest": "e8b3...",
    "reservedAmount": { "units": 1000, "currency": "USD" }
  },
  "signerKey": "c55d...",
  "signature": "7e40..."
}

Once verified, the receipt is cross-checked against the ask on four axes, and any failure denies: agent_id must equal the ask's agent, listing_id must equal the ask's listing, ask_digest must equal the digest recomputed from the ask body, and reserved_amount must be the same currency as the required amount with units at least as large.

The required amount is not the quoted per-call price. It is the token offer's total liability: the checked sum of max_total_cost across every grant in the token scope, which must be non-zero and must be denominated in the ask's quoted_price currency. In the example above, ten invocations at 100 units reserve 1000 units, not 100.

An unbounded bid can be quoted but not accepted

max_invocations is optional on RequestedScope. When it is absent, the minted grant carries max_total_cost: None, and the total-liability computation refuses a grant with no max_total_cost. The provider will quote such a bid and mint the token, and accept will then return ReservationReceiptInvalid because no reservation can cover an unbounded liability. Bid with an invocation cap if you intend to accept.

Terms: scope, window, and SLA

The economic terms of a contract are the price, the scope, the window, and the service level. The first three are enforced by the bid flow. The fourth is published by the listing and, for commerce orders, committed separately.

Scope may only narrow

The requested capability_scope_prefix is compared segment-wise against the listing's advertised capability_scope, splitting on :. Empty segments on either side fail. The advertised scope must have no more segments than the request, and every advertised segment must equal the request's segment at the same position. An advertised tools:search therefore admits tools:search and tools:search:web, and refuses tools and tools:index. Separately, the requested server_id must equal the listing subject's actor_id.

The grant that comes back is deliberately narrow. It takes server_id from the listing subject rather than from the request, tool_name from the request, operations: vec![Operation::Invoke] and nothing else, an empty constraints list, max_cost_per_invocation equal to the advertised price, max_total_cost equal to price times invocations, and dpop_required: None. The token scope carries no resource_grants and no prompt_grants, and its delegation_chain is empty. Binding a purchase to a specific output digest is a Cognition Market concern rather than a bid-flow one; see The Cognition Market.

One window governs three things

window_seconds sets the ask expiry, the token expiry, and the acceptance deadline at once. All three land on issued_at + window_seconds, computed with a checked add. Acceptance before ask.issued_at is an invalid request; acceptance at or after ask.expires_at is PricingExpired. The clock the contract runs on is the provider's now, not the buyer's.

The SLA rides the listing

No leg of the contract copies a service level. The advertised SLA lives on the signed pricing hint, chio.marketplace.listing-pricing-hint.v1, as a ListingSla with max_latency_ms, availability_bps (basis points, where 10000 means 100 percent), and throughput_rps. Validation requires a non-zero latency budget, an availability in (0, 10000], and a non-zero throughput. The same hint carries revocation_rate_bps and recent_receipts_volume, which is how a buyer prices reliability before it bids. Because the contract references the listing by id, the terms a contract was struck under are recoverable from the registry rather than restated in the trade.

A commerce order states its service level as its own signed artifact, chio.commerce.sla-commitment.v1, which requires order_id, provider_subject, buyer_subject, at least one entry in metric_definitions, a measurement_policy_ref, an effective_window, a collateral_position_ref, a guarantee_decision_ref, and a signature. An order reaches it through trust_market_requirement.sla_commitment_ref on chio.commerce.order-context.v1. That same requirement object also carries the collateral_position_ref, guarantee_decision_ref, and adjudication_jurisdiction_ref, while the order context itself binds the price at quote_id, quote_amount_minor, quote_currency, and quote_sha256. A promise about latency is one artifact; the collateral standing behind that promise is another.


Bonds and remedies

A contract can be perfectly formed and still be a fraud. The remedy machinery does not attach to the trade; it attaches to the listing and the operator that published it. An OpenMarketPenaltyArtifact is keyed by listing_id, case_id, and fee_schedule_id, and carries no bid or ask digest. The unit of remedy is the posted bond, not the individual agreement.

What a bond has to be is set by the signed OpenMarketFeeScheduleArtifact (chio.registry.market-fee-schedule.v1), which fixes a publication_fee, a dispute_fee, a market_participation_fee, and a non-empty list of bond_requirements. Each OpenMarketBondRequirement carries a bond_class (Publication, Listing, or Dispute), a required_amount, a collateral_reference_kind (CreditBond or ExternalReference), and a slashable flag.

Three actions can move a bond, and the action plus the penalty state determines the effective state and whether admission is blocked:

actionstateeffective_stateblocks_admission
HoldBondEnforcedBondHeldtrue
SlashBondEnforcedBondSlashedtrue
ReverseSlashReversedReversedfalse
anyProposed, Denied, SupersededClearfalse

The preconditions are strict. HoldBond and SlashBond both require the governance case to be exactly a Sanction in state Enforced, and SlashBond additionally requires the resolved bond requirement to be slashable. ReverseSlash requires an Appeal case, a supersedes_penalty_id, and a prior_penalty that is itself an enforced hold or slash for the same listing, fee schedule, and bond class. The artifact will not validate at all unless a ReverseSlash carries a supersession target and uses the Reversed state. The penalty amount must match the bond currency and must not exceed required_amount.units.

Evaluation is by evaluate_open_market_penalty, which verifies every signed input, checks trusted-signer membership, cross-checks namespace and operator linkage, and rejects expired artifacts. A failed check does not raise. It returns a successful OpenMarketPenaltyEvaluation carrying exactly one OpenMarketFinding with a code such as BondRequirementNotSlashable, PenaltyAmountExceedsBond, or GovernanceCaseKindInvalid. The outer Result is Err only when the request's own listing or publisher shape is malformed. Every penalty must carry a non-empty evidence_refs list of OpenMarketEvidenceReference entries, each with a kind, a reference id, and an optional 64-character SHA-256 digest.


Settlement handoff

The acceptance is the handoff. Everything settlement needs to act is on it: bid_receipt_id names the funds hold to draw against, quoted_price fixes the per-call price, token_id and token_subject and token_expires_at name the grant that will be exercised, and bid_digest plus ask_digest let a verifier confirm the two earlier bodies without trusting whoever handed them over.

The token itself does not travel on the acceptance, only its identifiers. The token travels on the ask, and the spend authority it carries is the grant's max_cost_per_invocation and max_total_cost. Those two numbers, not the contract text, are what a kernel enforces at invocation time; see Authoritative Spend.

The market crate stops here. It records that a priced agreement exists and that a hold backs it. Moving the money is a separate concern with its own evidence, covered in Settlement Rails and, when many contracts net against each other, Clearinghouse.


Reading a contract from the registry

A contract is only meaningful against the listing it was struck under, so verification starts at the registry. chio_listing::search returns a ListingSearchResponse whose results are Listing entries pairing the signed listing, the signed pricing hint, the publisher, and replica freshness. resolve_admissible_listing(results, listing_id, now) returns an entry only when is_admissible_at(now) holds: status Active, a pricing hint live at now, and a freshness state of Fresh. A listing that was admissible when the contract was signed can be revoked later without invalidating the signatures, which is why the contract records the price and the digests rather than a pointer alone.

Given the three envelopes and the registry, a third party can verify the agreement end to end:

  1. Verify each envelope. Check verify_signature on the bid, the ask, and the acceptance independently, then on the token offer inside the ask.
  2. Recompute the chain. Canonicalize the bid body, hash it, and compare against askResponse.body.bidDigest. Do the same for the ask body against acceptedBid.body.askDigest, and confirm the acceptance repeats the same bidDigest.
  3. Check the keys. The token issuer must equal the ask's signer key, the acceptance's signer key must equal the token subject, and the ask's signer key must be the pricing hint's signer key returned by provider_signing_key.
  4. Re-resolve the terms. Look up the listing and confirm that quotedPrice equals the advertised price_per_call, that the granted server_id is the listing subject's actor_id, and that the granted scope sits under the advertised capability_scope.
  5. Resolve the hold. Take bidReceiptId to the settlement reservation authority and confirm the reserved amount covers the token's total liability in the quoted currency.
  6. Check the operator. Evaluate any open penalty against the listing. A blocks_admission result tells you the publisher is under an enforced hold or slash, independent of whether this particular contract verifies.

This is not the kernel's Decision Contract

The word contract is overloaded in this codebase and this page covers only the economic sense: a priced bilateral agreement in chio-open-market. If you came looking for the kernel's guard verdict interface, the allow, deny, and require-approval decision every guard returns, that is the Decision Contract and it lives in The Three Verdicts and the Kernel overview. Two further unrelated uses: run-contract.schema.json under chio-runtime describes a workflow run, and HttpEgressContract is the outbound HTTP policy an egress client is built with. Nothing on this page applies to any of them.

See also