Skip to content

Latest commit

 

History

History
496 lines (406 loc) · 27.8 KB

contracts.md

File metadata and controls

496 lines (406 loc) · 27.8 KB

On-chain Smart Contracts — Codebase Guide

Mycelium has six core on-chain contracts, all authored in the Mycelium DSL and compiled with our own compiler. They live at the repo root and deploy once per network.

python -m mycelium_compiler.main hive_registry.py        -o build/hive_registry.wasm
python -m mycelium_compiler.main escrow_contract.py      -o build/escrow.wasm
python -m mycelium_compiler.main job_board_contract.py   -o build/job_board.wasm
python -m mycelium_compiler.main memory_anchor.py        -o build/memory_anchor.wasm
python -m mycelium_compiler.main verifier_registry.py    -o build/verifier_registry.wasm
python -m mycelium_compiler.main reputation_registry.py  -o build/reputation_registry.wasm
Contract File Testnet address SDK wrapper
HiveRegistry hive_registry.py CCHLAG6L4C6ETKD3ZOYE4GRP3VRUB6A2ES6P52VTENXQURL2VFWXI4XC HiveClient
Escrow escrow_contract.py (deployed per-deal) EscrowPaymentRouter
JobBoard job_board_contract.py CDASJ42STDU42QXDXH3KRFNQWBURB54XPXV2WBXHWGPBA2BNAI5EYULO (also in mycelium.toml [jobs].board_address) JobBoardClient (CLI mycelium job subcommands)
MemoryAnchor memory_anchor.py CAC27VKJEPDJJNI36NP7D7VH6WCHT6N5EITKSKPZIQNWA2VPEPBIXJSB MemoryAnchorClient
VerifierRegistry verifier_registry.py CBFELTFVBRGR5Y4VHOGFUJLNMMRDNBAOTTZUKZ3SNT625GDB4T76OHMC ([verifier].registry_address) VerifierRegistryClient
ReputationRegistry reputation_registry.py CCTJCC5FELB4PSXT3OF4QSFKH456OIVHF3YGY7ABNFH7ITL7XWYBO2NE ([reputation].registry_address) ReputationClient

All addresses are also in constants.py and mycelium.toml.

v0.4.0 — the proof layer. Escrow and JobBoard were reworked so that funds follow a judge's verdict on the actual deliverable, not a SHA-256 preimage of the spec. The old SHA256(proof) == spec_hash gate only proved a claimant could echo the agreed bytes back — never that the work was done or was any good. Two new contracts (VerifierRegistry, ReputationRegistry) add staked judges and portable agent reputation. See PROOF_SYSTEM.md for the full architecture.


1. Hive Registry — hive_registry.py

The decentralized DNS for the Mycelium agent network. Maps a unique alphanumeric name → identity (address, capability hash, endpoint, model, role, description, reputation).

Storage layout (instance storage, keyed by str(name))

Key Type What
addr:{name} Address Agent's Stellar wallet (G-address)
cap:{name} Bytes SHA-256 of sorted, comma-joined capability tags
endp:{name} Bytes Service endpoint URL (UTF-8)
model:{name} Bytes Model identifier (UTF-8)
role:{name} Bytes Agent role description (UTF-8)
desc:{name} Bytes Agent description (UTF-8)
rep:{name} U64 Reputation score (starts at 0)
reg:{name} Bool Registration flag (prevents overwrites)

Error codes

Code Name Meaning
1 NAME_TAKEN Name already registered
2 NOT_REGISTERED Name lookup failed

Contract interface

Function Decorator Auth Signature Description
register_agent @external agent_address.require_auth() (name: Symbol, agent_address: Address, capability_hash: Bytes, endpoint: Bytes, model: Bytes, role: Bytes, desc: Bytes) → Bool Register a name. Reverts with NAME_TAKEN if already claimed. Emits agent_registered.
resolve_agent @view — (name: Symbol) → Map Returns {address, capability, endpoint, model, role, desc, reputation}. Reverts with NOT_REGISTERED.
update_reputation @external — (name: Symbol, new_reputation: U64) → Bool Update an agent's reputation score.
is_registered @view — (name: Symbol) → Bool Check if a name is registered.

Events

Topic Payload Indexed by
agent_registered {name: Symbol, address: Address} Off-chain indexer → agents/{name}

SDK integration

The SDK's HiveClient wraps every method. register() auto-publishes plaintext capability tags to the indexer after a successful on-chain registration. discover_agents(prefer_indexer=True) queries the hosted indexer for O(1) discovery and falls back to on-chain event-scan.


2. Escrow — escrow_contract.py

The x402 conditional-payment contract. One instance locks a payment from a depositor to a provider (or a swarm) until a verdict authorizes release. The release authority is a judge address fixed at lock time: the judge evaluates the worker's deliverable against the job's rubric off-chain (see PROOF_SYSTEM.md) and, on a pass, authorizes the payout on-chain. If the deadline passes without a release, the depositor refunds. Funds move via Soroban's env.transfer (SEP-41), so the locked asset can be native XLM or any token.

v0.4.0 change. This replaces the previous SHA-256 preimage gate. A hash preimage only proved the claimant could echo the agreed bytes back — it never proved the work was done or any good. Release now follows a judge's verdict, not a tautological hash. The depositor can no longer veto a passing verdict (the graft vector is gone); refund stays available only on timeout. evidence_root (the 32-byte commitment to the submitted evidence bundle) is passed on release and emitted, so every payout is auditably tied to the exact submission the judge approved.

Storage layout (instance, one-per-contract)

Key Type What
depositor Address Payer
provider Address Service operator (single-payout recipient)
token Address SAC / SEP-41 token address
amount I128 Locked balance (stroops)
judge Address Release authority (the verdict signer)
deadline U64 Timestamp after which refund is allowed
settled Bool Prevents double claims
init Bool Initialization flag
evidence_root Bytes The approved submission's root, recorded on release

Error codes

Code Name Meaning
1 ALREADY_INITIALIZED Contract already has funds locked
2 NOT_INITIALIZED No escrow has been set up
3 ALREADY_SETTLED Funds already claimed/refunded
4 INVALID_PROOF (reserved)
5 NOT_EXPIRED Deadline hasn't passed (refund attempt too early)
6 BAD_SPLIT recipients.len() != amounts.len() or amounts don't sum to amount

Contract interface

Function Auth Signature Description
initialize depositor.require_auth() (depositor, provider, token, amount: I128, judge, timeout: U64) → Bool Pull funds from depositor into the contract. Sets deadline = ledger.timestamp() + timeout. Emits escrow_locked.
claim_funds judge.require_auth() (evidence_root: Bytes) → Bool Release the full amount to provider. Authorized by the judge on a passing verdict; evidence_root ties the payout to the approved bundle. Emits escrow_released.
claim_and_split judge.require_auth() (evidence_root: Bytes, recipients: Vec[Address], amounts: Vec[I128]) → Bool Split release across N recipients (swarm). Only the judge may name recipients; amounts must sum to the locked amount. Emits escrow_split.
refund depositor.require_auth() () → Bool Return funds to depositor after deadline. Emits escrow_refunded.
get_details — (view) () → Map Returns {provider, amount, judge, deadline, settled}.

Events

Topic Payload Indexed by
escrow_locked {provider, amount, judge} Indexer → settlements/{event_id}
escrow_released {provider, amount} Indexer → settlements/{event_id}
escrow_split {recipients: count, amount} Indexer → settlements/{event_id}
escrow_refunded {depositor, amount} Indexer → settlements/{event_id}

SDK integration

EscrowPaymentRouter:

  • create_locked_escrow(..., judge=...) — deploys a new escrow instance from the bundled escrow.wasm and calls initialize, naming the verdict authority. Pure-Python deployment (no stellar-cli).
  • release_funds() — calls claim_funds(evidence_root) (judge-signed).
  • split_release() — reads the locked amount via get_details, computes exact stroop amounts per share (remainder on last recipient), calls claim_and_split(evidence_root, ...) (judge-signed).
  • refund() — calls refund() after the deadline.

Settlement lifecycle

Depositor                       Escrow                  Judge          Provider
    │                             │                       │               │
    │── deploy escrow ────────────►│                       │               │
    │── initialize(amount, judge)─►│ locks funds           │               │
    │                             │                       │               │
    │       ... worker submits evidence, judge evaluates rubric ...        │
    │                             │                       │               │
    │                             │◄── claim_funds(root) ──│ (passing verdict)
    │                             │── transfer(amount) ──────────────────►│
    │                             │  settled = true        │               │
    │                             │                       │               │
    │   OR (after deadline):       │                       │               │
    │── refund() ─────────────────►│ timestamp >= deadline?                │
    │◄── transfer(amount) ────────│  settled = true        │               │

3. JobBoard — job_board_contract.py

The Sovereign Job Boards contract. Posters publish tasks with bounties (locked in a judge-gated escrow off-chain first), agents claim or join swarms, submit an evidence bundle, a judge records a verdict, and the poster finalizes the record. The board is the coordination ledger — the escrow holds the funds.

v0.4.0 — self-describing jobs + verdict gate. A job is now fully self-describing on-chain: title, description, and spec (the full acceptance rubric JSON — the checks, their weights, and the poster-chosen judge panel) are stored on the contract, so any bounty renders straight from get_job with no off-chain dependency. rubric_hash is the SHA-256 of spec for integrity. The old submit_proof(SHA256(proof) == spec_hash) gate is gone: the worker calls submit_evidence(evidence_root, evidence_uri) to anchor the real deliverable, a judge (fixed per job) calls record_verdict(passed, score, evidence_root), and the SDK has the judge release the escrow on a pass.

Storage layout (instance, keyed by str(job_id))

Key Type What
job_count U64 Auto-incrementing job id counter
poster:{jid} Address Job poster
title:{jid} Bytes Job heading (UTF-8)
description:{jid} Bytes Job description (UTF-8)
spec:{jid} Bytes Full rubric JSON: checks + weights + judge panel (UTF-8)
rubric_hash:{jid} Bytes SHA-256 of spec
bounty:{jid} I128 Bounty amount (stroops)
token:{jid} Address Payment token
mode:{jid} Symbol single or swarm
escrow:{jid} Address Escrow contract holding the funds
judge:{jid} Address The verdict authority (also the escrow's release authority)
deadline:{jid} U64 Deadline timestamp
status:{jid} Symbol open → claimed → submitted → verified/rejected → done (or cancelled)
agent:{jid} Address Claiming agent (single mode)
evidence_root:{jid} Bytes 32-byte commitment to the submitted evidence bundle
evidence_uri:{jid} Bytes Pointer to where the bundle is fetched + verified
score:{jid} U32 The panel's weighted verdict score (0..100)
members:{jid} Vec[Address] Swarm member list
shares:{jid} Vec[U32] Swarm member shares (basis points, sum = 10000)

Error codes

Code Name Meaning
1 NOT_FOUND Job doesn't exist
2 NOT_OPEN Job is not in open (or claimed for swarm joins)
3 NOT_POSTER Caller is not the job poster
4 INVALID_PROOF record_verdict's evidence_root ≠ the anchored submission
5 NOT_SUBMITTED Job not in submitted status (verdict attempt)
6 BAD_SHARE Share exceeds 10000 bps
7 NOT_CLAIMANT Submitter is not the assigned agent or a swarm member
8 NOT_JUDGE Caller is not the job's fixed judge
9 NOT_VERIFIED Finalize attempted before the job was verified

Contract interface

Function Auth Signature Description
post_job poster.require_auth() (poster, title, description, spec, rubric_hash, bounty: I128, token, mode: Symbol, escrow, judge, deadline: U64) → U64 Record a self-describing job (title + description + rubric spec on-chain). Returns job_id. Emits job_posted.
claim_job agent.require_auth() (agent, job_id: U64) → Bool Single-mode self-claim. Emits job_claimed.
assign_agent poster.require_auth() (job_id: U64, agent) → Bool Poster assigns an agent. Emits job_claimed.
join_swarm agent.require_auth() (agent, job_id: U64, capability_tag: Bytes, share_bps: U32) → Bool Join a swarm job with an agreed share. Emits swarm_joined.
submit_evidence submitter.require_auth() (submitter, job_id: U64, evidence_root: Bytes, evidence_uri: Bytes) → Bool Anchor the deliverable's root + locator and open the verification round. No hash-to-spec check. Only the claimant/swarm member may submit. Emits job_submitted.
record_verdict judge.require_auth() (judge, job_id: U64, passed: Bool, score: U32, evidence_root: Bytes) → Bool The fixed judge records the panel verdict (+ 0..100 score) against the anchored submission. Pass → verified, fail → rejected. Emits job_verified.
finalize poster.require_auth() (job_id: U64) → Bool Poster closes a verified job (done). The judge already released the escrow at verdict time. Emits job_completed.
cancel_job poster.require_auth() (job_id: U64) → Bool Cancel an unclaimed job. Emits job_cancelled.
get_job — (view) (job_id: U64) → Map Returns {poster, title, description, spec, rubric_hash, evidence_root, evidence_uri, score, bounty, token, mode, escrow, judge, deadline, status, agent}.
get_swarm — (view) (job_id: U64) → Vec[Address] Swarm member list.
get_shares — (view) (job_id: U64) → Vec[U32] Swarm shares (index-aligned with get_swarm).
job_count — (view) () → U64 Total jobs posted.

Events

Topic Payload Indexed by
job_posted {job_id, poster, bounty} Indexer → jobs/{job_id}
job_claimed {job_id, agent} Indexer → jobs/{job_id} (status update)
swarm_joined {job_id, agent, share} Indexer → jobs/{job_id}/members/{agent}
job_submitted {job_id} Indexer → jobs/{job_id} (status update)
job_verified {job_id, passed, score} Indexer → jobs/{job_id} (verdict + score)
job_completed {job_id} Indexer → jobs/{job_id} (status update)
job_cancelled {job_id} Indexer → jobs/{job_id} (status update)

Job lifecycle

post_job (open)  — title + description + rubric spec stored on-chain
    │
    ├── claim_job / assign_agent (claimed, single mode)
    │       │
    │       └── submit_evidence (submitted) — anchor the real deliverable
    │               │
    │               └── record_verdict (verified / rejected)
    │                       │   pass → judge releases escrow to the claimant
    │                       └── finalize (done) — poster closes the record
    │
    ├── join_swarm (claimed, swarm mode) ── × N agents
    │       │
    │       └── submit_evidence (submitted)
    │               │
    │               └── record_verdict (verified / rejected)
    │                       │   pass → judge splits escrow per shares
    │                       └── finalize (done)
    │
    └── cancel_job (cancelled) → SDK refunds escrow

CLI

# job_id is positional; checks + judge panel are repeatable flags (self-describing on-chain job)
mycelium job post --title "Pitch deck" --description "Series-A deck, 10-14 slides" \
  --check "cover:60:problem, solution, market, traction, ask" \
  --check "design:40:visually coherent, consistent palette" \
  --judge-model nvidia:deepseek-ai/deepseek-v4-pro \
  --judge-model groq:llama-3.3-70b-versatile \
  --bounty 10 --judge G... --threshold 70 --mode swarm
mycelium job list   --status open
mycelium job claim  1
mycelium job join   1 --capability design --share 5000   # 50% of bounty
mycelium job do     1 --model groq:llama-3.3-70b-versatile   # agent: work + submit evidence
mycelium job judge  1 --deliverable ./out.md             # judge: run the panel + settle
mycelium job status 1                                    # full on-chain detail incl. score
mycelium job finalize 1

4. MemoryAnchor — memory_anchor.py

The tiny on-chain commitment for an agent's off-chain memory. Stores only a SHA-256 root hash, fetch URI, ACL, and monotonic version per agent. See memory.md for the full memory system.

Storage layout (keyed by str(owner))

Key Type What
root:{owner} Bytes SHA-256 root of committed memory
uri:{owner} Bytes Where to fetch the blob
acl:{owner} Bytes Access control (opaque)
ver:{owner} U64 Monotonic version counter
has:{owner} Bool Whether owner has ever anchored

Error codes

Code Name Meaning
1 NOT_ANCHORED Agent has never anchored memory

Contract interface

Function Auth Signature Description
set_anchor owner.require_auth() (owner, memory_root: Bytes, uri: Bytes, acl: Bytes) → U64 Commit memory state. Bumps version. Emits memory_anchored.
get_anchor — (view) (owner) → Map Returns {root, uri, acl, version}. Reverts if never anchored.
get_version — (view) (owner) → U64 Version (0 if never anchored).
is_anchored — (view) (owner) → Bool Whether owner has ever anchored.

Events

Topic Payload Indexed by
memory_anchored {owner, version} Indexer → memory_anchors/{owner}

5. VerifierRegistry — verifier_registry.py

The staked jury pool for the proof layer (P2 — see PROOF_SYSTEM.md §11). Today a single trusted key runs the judge panel; this contract is the keystone of making verification trustless: a judge registers its model capability, locks an XLM stake, and becomes eligible to be drawn onto panels. The verification market (the only authorized slasher) slashes the stake of outliers and no-shows and records accuracy, so the honest read is the profitable play. Staking and slashing move real value via env.transfer, so the bond is a genuine economic commitment, not a flag.

Storage layout (instance; per-judge keys suffixed by the judge Address)

Key Type What
admin Address Configurer
token Address Staking asset
min_stake I128 Bond required to be eligible
unbond_secs U64 Delay between request_unstake and withdraw
slasher Address The only address allowed to slash / record accuracy (the market)
stake:{judge} I128 Judge's locked bond
tags:{judge} Bytes Model families the judge runs (e.g. nvidia:…,groq:…)
endpoint:{judge} Bytes Optional service endpoint
active:{judge} Bool Eligible flag (cleared on unstake)
unbond_at:{judge} U64 Withdraw-after timestamp (0 = not unstaking)
jobs:{judge} U32 Votes cast (accuracy denominator)
agreed:{judge} U32 Votes within tolerance of the median (numerator)

Error codes

Code Name Meaning
1 ALREADY_INITIALIZED Registry already configured
2 NOT_INITIALIZED Registry not yet configured
3 NOT_REGISTERED (reserved)
4 INSUFFICIENT_STAKE Stake below the minimum to judge
5 NOT_SLASHER Caller is not the configured slasher
6 UNBONDING withdraw with no pending unstake
7 NOT_EXPIRED withdraw before the unbonding period elapsed
8 BAD_AMOUNT Stake amount not positive

Contract interface

Function Auth Signature Description
initialize admin.require_auth() (admin, token, min_stake: I128, unbond_secs: U64, slasher) → Bool One-time config: staking token, minimum bond, unbonding delay, and the market (slasher).
register judge.require_auth() (judge, model_tags: Bytes, endpoint: Bytes) → Bool Announce judging capability. Emits verifier_registered.
stake judge.require_auth() (judge, amount: I128) → Bool Lock a bond (adds to existing; cancels a pending unstake). Emits verifier_staked.
request_unstake judge.require_auth() (judge) → Bool Begin the unbonding period; clears active. Emits verifier_unstaking.
withdraw judge.require_auth() (judge) → Bool Return the (possibly slashed) stake after unbonding. Emits verifier_withdrew.
slash slasher.require_auth() (judge, amount: I128, reason: Symbol) → Bool Cut a judge's stake (market only); the cut is sent to the slasher. Emits verifier_slashed.
record_accuracy slasher.require_auth() (judge, agreed: Bool) → Bool Record one vote (jobs) and, if within tolerance, one agreed (verifier reputation). Market only.
get — (view) (judge) → Map Returns {stake, active, tags, jobs, agreed, unbond_at}.
is_eligible — (view) (judge) → Bool True if active and bonded at/above the minimum.
min_stake — (view) () → I128 The bond required to be eligible.

Events

Topic Payload Indexed by
verifier_registered {judge} Indexer → verifiers/{judge}
verifier_staked {judge, amount} Indexer → verifiers/{judge}
verifier_unstaking {judge} Indexer → verifiers/{judge}
verifier_withdrew {judge, amount} Indexer → verifiers/{judge}
verifier_slashed {judge, amount, reason} Indexer → verifiers/{judge}

SDK integration

VerifierRegistryClient wraps every external, converting XLM ↔ stroops: initialize, register, stake, request_unstake, withdraw, slash, record_accuracy, plus reads get (which derives accuracy_bps = agreed / jobs), is_eligible, and min_stake_xlm.


6. ReputationRegistry — reputation_registry.py

Portable, on-chain agent reputation (P2 — see PROOF_SYSTEM.md §12). Reputation is the trust signal that makes agent-to-agent delegation work: before agent B relies on agent A, it reads A's verified track record. The substrate is the panel verdict — every passing job has an on-chain score (0..100) bound to the agent(s) that did the work. This is a small, dedicated contract (not buried inside one board) so the signal is reusable across boards and composable for A2A. An authorized recorder (the board / market at verdict time) calls credit once per job; double-counting on retries is prevented by recording each (agent, job_id) pair — credit is idempotent per pair.

Storage layout (instance; per-agent keys suffixed by the agent Address)

Key Type What
admin Address Configurer
recorder Address The only address allowed to credit (the board / market)
("seen", agent, job_id) Bool Idempotency marker per (agent, job_id)
jobs:{agent} U32 Jobs credited
sum:{agent} U32 Cumulative verdict score
passed:{agent} U32 Jobs that passed
last:{agent} U64 Last credited job id

Error codes

Code Name Meaning
1 ALREADY_INITIALIZED Registry already configured
2 NOT_INITIALIZED Registry not yet configured
3 NOT_RECORDER Caller is not the authorized recorder
4 ALREADY_CREDITED This (agent, job_id) was already credited

Contract interface

Function Auth Signature Description
initialize admin.require_auth() (admin, recorder) → Bool One-time: set the authorized recorder.
set_recorder admin.require_auth() (recorder) → Bool Admin rotates the recorder (e.g. a new board version).
credit recorder.require_auth() (agent, job_id: U64, score: U32, passed: Bool) → Bool Credit a worker with a job's verdict. Idempotent per (agent, job_id); for a swarm the recorder calls once per member. Emits reputation_credited.
get — (view) (agent) → Map Returns {jobs_done, jobs_passed, sum_score, avg_score, last_job} (avg_score derived on-chain; pass_rate off-chain).

Events

Topic Payload Indexed by
reputation_credited {agent, job_id, score, passed} Indexer → reputation/{agent}

SDK integration

ReputationClient wraps initialize, credit, and get (which derives pass_rate_bps). JobBoardClient.judge_and_settle(..., reputation_address=...) credits the worker(s) automatically at verdict time.


Related docs

  • proof.md — the verifiable-work proof layer these contracts implement (v0.4.0).
  • indexer.md — the off-chain indexer that consumes all events from these contracts.
  • memory.md — persistent agent memory system (built on MemoryAnchor).
  • sdk.md — SDK wrappers (HiveClient, EscrowPaymentRouter, MemoryAnchorClient).
  • compiler.md — the Python→Rust→WASM compiler that builds these contracts.
  • dsl.md — the Mycelium DSL type system used to author them.