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.
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.
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.
| Field | Type | Meaning |
|---|---|---|
schema | String | Must equal chio.marketplace.bid-request.v1 |
agent_id | String | The buyer. Carried forward unchanged onto the ask and the acceptance |
listing_id | String | The published listing being bid against |
max_price_per_call | MonetaryAmount | A ceiling, not an offer. units must be greater than zero |
window_seconds | u64 | Must be greater than zero. Sets both the ask expiry and the token expiry |
requested_scope | RequestedScope | The narrowing applied to the minted token |
issued_at | u64 | Unix 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.
| Field | Type | Meaning |
|---|---|---|
bid_digest | String | SHA-256 hex over the canonical JSON of the BidRequest body |
quoted_price | MonetaryAmount | A clone of the listing's advertised price_per_call, never the buyer's ceiling |
token_offer | CapabilityToken | Minted and separately signed by the issuer, bound to the buyer subject |
listing_id, agent_id | String | Copied from the resolved listing and the bid |
issued_at | u64 | The provider's now, not the bid's issued_at |
expires_at | u64 | issued_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.
| Field | Type | Meaning |
|---|---|---|
bid_digest | String | Copied from the ask, so leg one stays reachable from leg three |
ask_digest | String | Recomputed over the AskResponse body at acceptance time |
bid_receipt_id | String | The receipt id from the verified funds reservation |
quoted_price | MonetaryAmount | Copied from the ask |
accepted_at | u64 | Must fall in [ask.issued_at, ask.expires_at) |
token_id, token_subject, token_expires_at | String, PublicKey, u64 | The 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:
{
"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_idmust equal the resolved listing id, the pricing hint must name the same listing id, and the pricing and listing namespaces must match after normalization. OtherwiseListingMismatch. - Provider identity. The pricing hint's
provider_operator_idmust equal the publisher'soperator_idand the listing'snamespace_ownership.owner_id, and both the pricing hint and the listing must be signed by the namespace'ssigner_public_key. - Minting authority. The issuer keypair supplied in the
BidMintContextmust hold the public key returned byprovider_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 withAuthorityMismatch. - Token authority. At acceptance,
ask.body.token_offer.issuermust equalask.signer_key, the token signature must verify on its own, and the token window must fully cover the ask window:token.issued_atno later thanask.issued_atandtoken.expires_atno earlier thanask.expires_at.
The denials are named, and a caller can discriminate them:
BiddingError | Raised when |
|---|---|
BidSignatureInvalid | The bid envelope does not verify against its own signer key |
ListingSignatureInvalid, PricingSignatureInvalid | The listing or the pricing hint does not verify. accept reuses PricingSignatureInvalid for an ask envelope that does not verify |
ListingNotActive | Listing status is suspended, superseded, revoked, or retired |
PricingExpired | The pricing hint is outside [issued_at, expires_at). Also raised when accepted_at reaches the ask expiry |
ListingStale | The listing is inadmissible for a reason other than expired pricing, such as a replica that is not fresh |
CurrencyMismatch, BidCeilingTooLow | The bid currency differs from the advertised currency, or the ceiling is below the advertised price |
ScopeOutsideListing | The requested scope prefix is not covered by the advertised scope, or the requested server_id is not the listing subject |
WindowOutOfBounds | A zero or overflowing window, or a token window that does not cover the ask window |
TotalCostOverflow | Advertised price times max_invocations exceeds u64 |
TokenSignatureInvalid, AuthorityMismatch | The token offer does not verify, or a key does not belong where the flow requires it |
ReservationReceiptInvalid | The 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.
{
"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:
action | state | effective_state | blocks_admission |
|---|---|---|---|
HoldBond | Enforced | BondHeld | true |
SlashBond | Enforced | BondSlashed | true |
ReverseSlash | Reversed | Reversed | false |
| any | Proposed, Denied, Superseded | Clear | false |
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:
- Verify each envelope. Check
verify_signatureon the bid, the ask, and the acceptance independently, then on the token offer inside the ask. - Recompute the chain. Canonicalize the bid body, hash it, and compare against
askResponse.body.bidDigest. Do the same for the ask body againstacceptedBid.body.askDigest, and confirm the acceptance repeats the samebidDigest. - 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. - Re-resolve the terms. Look up the listing and confirm that
quotedPriceequals the advertisedprice_per_call, that the grantedserver_idis the listing subject'sactor_id, and that the granted scope sits under the advertisedcapability_scope. - Resolve the hold. Take
bidReceiptIdto the settlement reservation authority and confirm the reserved amount covers the token's total liability in the quoted currency. - Check the operator. Evaluate any open penalty against the listing. A
blocks_admissionresult 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
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
- The Open Market for the venue these three legs run inside, and how it relates to
chio-listingandchio-market. - Capability Discovery for the signed registry and the pricing hint a contract is priced against.
- Pricing Models & SLAs for the pricing and service-level shapes a listing advertises.
- Credit Facilities & Bonds for the collateral a
CreditBondreference points at. - Governance Charters for the charter and case lifecycle a
Sanctionmust reach before a bond can be slashed. - Authoritative Spend for how the grant's cost ceilings are enforced when the token is actually used.
- Outcome-Based Pricing for agreements priced on a result rather than on a per-call rate.