Mechanize the verdict.
Accord is a general-purpose, Schelling-point arbitration primitive on Solana — the "Kleros of Solana." Any Solana program (the Arbitrable) files a subjective Dispute via two CPI calls; the Accord draws stake-weighted Jurors (VRF), collects commit-reveal votes, and emits a Ruling governed by game-theoretic incentives instead of trusted humans.
It is a standalone, reusable product: the Accord has no knowledge of the filing program's domain. Dispute resolution becomes composable infrastructure.
your program ──create_dispute()──► Accord ──draws jurors, runs commit/reveal──► Ruling
▲ │
└────────────────────────────get_ruling()────────────────────────────────────┘
- Schelling Point = honesty. Jurors converge on the truthful answer because voting coherently with the majority is the profitable strategy. No central authority picks judges.
- Party-agnostic Arbitrable interface. Integrate with two CPI calls:
create_dispute()→get_ruling(). The Accord never learns your domain. - Permissionless Subaccords. Specialized Juror pools (automotive, freelancing, NFTs, …). Anyone can register one; each defines its own staking token, min stake, windows, and slash factor.
- Per-Subaccord staking token. Each pool picks the SPL token Jurors stake (USDC by default). Stake is the anti-sybil mechanism and the coherence-slashing substrate.
- Verifiable sortition. Stake-weighted Juror draw over a bonded Merkle-Sum-Tree snapshot, seeded by committed VRF — manipulation-resistant and fraud-proofable on-chain (ADR-0008/0009).
- Commit-reveal + exponential appeals. Secret votes prevent vote-copying so the Schelling Point forms independently; each appeal doubles the panel + 1 (3 → 7 → 15 → 31), making bribery prohibitively expensive.
Important
Project status. The on-chain program (programs/accord) implements the
full v1 instruction set with a LiteSVM unit-test per instruction. The
TypeScript SDK (packages/sdk) and the jest/ Surfpool integration suite
(tests/) are scaffolded and under active development (Codama codegen —
ADR-0010). See Project Status. This is pre-mainnet,
unaudited software — do not secure real value with it yet.
- Tech Stack
- Prerequisites
- Getting Started
- Architecture
- The Arbitrable Interface
- Environment Variables
- Available Commands
- Testing
- Project Status
- Deployment
- Troubleshooting
- Contributing
- License
- Further Reading
- Program language: Rust (Anchor framework)
- Framework: Anchor
1.0.2 - Runtime: Solana
3.1.10(BPF; host Rust viarust-toolchain.toml= stable) - Randomness: Magicblock / Solana VRF (
ephemeral-vrf-sdk 0.4.1) - Token layer: SPL Token + Associated Token (
anchor-spl 1.0.2) - SDK: TypeScript (
@solana/web3.js,@anchor-lang/core) — Codama + Solana Kit codegen pipeline (ADR-0010, in progress) - Docs: MkDocs Material (
apps/docs/) - Package manager: pnpm
9.15.0(workspaces) + Cargo (Rust workspace) - Lint/format:
rustfmt,clippy,tsc --noEmit, Prettier, ESLint, markdownlint, gitleaks (via pre-commit)
- Rust (stable) —
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Node.js 20+ and pnpm 9.x —
corepack enable && corepack prepare pnpm@9.15.0 --activate - Solana CLI + Anchor — installed automatically by
make prep(below) - Poetry (only for the docs site) —
curl -sSL https://install.python-poetry.dev | python3 -
Tip
make prep installs Solana 3.1.10 (via solana-install) and Anchor
1.0.2 (via avm) for you — you do not need to pin them manually.
git clone https://github.com/xeroc/accord.git
cd accordmake prepThis runs solana-install init 3.1.10, installs Anchor 1.0.2 through avm,
and runs pnpm install across the workspace. Re-run only when toolchain
versions change.
make buildmake build runs anchor build (compiles programs/accord to
target/deploy/accord.so and emits the IDL) followed by pnpm -r run build
(the SDK and any apps). The first build downloads and compiles the Solana BPF
toolchain — expect a few minutes.
The provider defaults to localnet with ~/.config/solana/id.json
(see Anchor.toml). Generate a keypair if you don't have one:
solana-keygen newSwitch clusters with the Solana CLI:
solana config set --url localhost # local validator / Surfpool
solana config set --url devnet # devnetFast in-process unit tests (no validator needed):
make test_unitFull end-to-end suite against a live validator (start Surfpool first):
make run_surfpool # in a separate terminal
make test_surfpoolSee Testing for the two-harness philosophy.
.
├── programs/
│ └── accord/ # The on-chain arbitration program (Anchor)
│ ├── src/
│ │ ├── lib.rs # #[program] instructions + account contexts
│ │ ├── state.rs # Account structs, enums, PDA proof types
│ │ ├── constants.rs # Size bounds, windows, PDA seed prefixes
│ │ ├── errors.rs # AccordError codes
│ │ └── events.rs # Emitted events for off-chain indexers
│ ├── tests/ # LiteSVM unit tests (one file per instruction)
│ ├── accord.qedspec # Formal-verification spec (qedgen)
│ ├── SPEC.md # v1 build spec (account model, state machine)
│ └── security-checklist.md
├── packages/
│ └── sdk/ # @accord/sdk — TypeScript SDK (codegen, in progress)
├── tests/ # jest + Surfpool integration suite
├── apps/
│ └── docs/ # MkDocs Material docs site (domain TBD)
│ ├── docs/
│ │ ├── adr/ # Architecture Decision Records (0001–0010)
│ │ ├── integration/ # Arbitrable integration guide
│ │ ├── reference/ # Accounts, instructions, state machine, errors
│ │ └── security/ # Fraud proofs, sortition/VRF, circuit breaker
│ └── mkdocs.yml
├── formal_verification/ # Lean / qedgen harness
├── CONTEXT.md # Domain language (ubiquitous-language glossary)
├── PROJECT.md # Project rationale (the "why")
├── BRAND.md # Brand model
├── Cargo.toml # Rust workspace
├── Anchor.toml # Anchor workspace + provider + test script
├── Makefile # Build / test / lint orchestration
├── pnpm-workspace.yaml # TS workspace globs (apps/*, packages/*, tests)
└── tsconfig.base.json # Shared TS compiler options
Note
The root package.json intentionally has no scripts block. The Makefile
orchestrates builds; lint/test fan out via pnpm's recursive filter. Don't add
root scripts — they'd duplicate the Makefile.
The dispute lifecycle is a state machine advanced by permissionless cranks (anyone can move it forward when a window elapses):
stateDiagram-v2
[*] --> Created: create_dispute (Arbitrable CPI)
Created --> SnapshotPosted: post_snapshot (bonded root)
SnapshotPosted --> Drawable: finalize_snapshot (challenge window passes)
Drawable --> Drawn: draw (VRF-seeded sortition)
Drawn --> Committed: commit (hash(vote, salt, juror))
Committed --> Revealed: reveal ({vote, salt})
Revealed --> RoundResolved: finalize_round (tally)
RoundResolved --> Final: finalize_dispute (no appeal / max reached)
RoundResolved --> Drawn: appeal → new round (2N+1 jurors)
Final --> [*]: get_ruling (lazy read by Arbitrable)
Odd Juror counts (3 / 7 / 15 / 31) make ties impossible.
Every account stores its canonical bump so handlers reuse the same PDA
without re-deriving. Large accounts (Round) are #[zero_copy] (AccountLoader)
to fit BPF's stack.
| Account | Seeds | Purpose |
|---|---|---|
Subaccord |
["subaccord", creator, risk_type] |
A specialized Juror pool: staking token, windows, alpha, authority |
JurorStake |
["stake", subaccord, juror] |
A Juror's staked capital + active_draws lock count |
Dispute |
["dispute", filer, nonce] |
A case: options, evidence hash, state, final_ruling |
Round |
["round", dispute, round_idx] |
Per-round jurors, commits, reveals, result (zero-copy) |
Snapshot |
["snapshot", dispute, round_idx] |
Bonded Merkle-Sum-Tree root over the Juror set |
AppealBond |
["bond", dispute, round_idx] |
Custody record for one appeal bond |
PendingUpdate |
["update", subaccord, nonce] |
Timelocked Subaccord parameter update (48h) |
PauseState |
["pause"] |
Singleton program-level circuit breaker |
| token vaults | Subaccord-PDA-owned SPL accounts | Stake pool + fee pool |
The draw is the security-critical path (ADR-0003, 0008, 0009):
- Snapshot. An off-chain indexer posts a Merkle-Sum-Tree root over the
Subaccord's Juror set + cumulative stakes, bonded at
1 × max-appeal-fee.anchor_slotfreezes the Juror set at post time so the draw is provably fair. - Challenge window (1 day). Anyone can
challenge_snapshotwith a fraud proof (duplicate leaf, wrong stake, omission, unsorted tree). A proven fraud voids the root and pays the poster's bond to the challenger; a false challenge pays the challenger's bond to the poster. - VRF.
request_vrfasks the VRF oracle for randomness; the oracle's identity-signedcommit_vrf_callbacklands the result. Only the VRF program can call the callback. - Draw. A permissionless cranker submits the drawn Jurors' membership
proofs. The program verifies each proof against the finalized root, checks
the sortition criterion (
cum_before ≤ r_i < cum_after), enforces the inflation guard (JurorStake.amount ≥ leaf.stake), and enforces distinctness.
Inherited from Kleros (live since 2019, 1000+ disputes):
- Fee: filer pays
N · fee_per_juror; appellant paysN_new · fee_per_juror+ bond. - Slash: each Incoherent Juror loses
α · min_stake(flat; ADR-0003). - Redistribution: forfeited fees + slashed stake → Coherent Jurors, equal split.
- Non-reveal penalty: ≥ the Incoherent penalty (forces reveal).
- Appeal bond: forfeited to Coherent Jurors of the final round if the appeal does not flip the prior Ruling; returned if it flips.
- Cross-round settlement: every round is re-settled against the final Ruling.
The Accord stores only an evidence hash on-chain (ADR-0006). A Subaccord-designated Evidence Operator re-encrypts the evidence for the drawn Jurors off-chain:
claimant ──encrypt(evidence, operator_pubkey)──► encrypted blob ──► off-chain store
on-chain Accord: evidence_hash only
dispute filed + Jurors drawn
▼
evidence_operator service: decrypt → re-encrypt per drawn Juror (+ optional watermark)
▼
Juror decrypts, verifies cleartext vs on-chain evidence_hash
Your program integrates with two CPI calls. The Accord handles everything else.
// 1. File the dispute
let dispute = accord::create_dispute(
ctx.accounts.clone(),
vec![option_a_hash, option_b_hash], // 2+ option hashes
evidence_hash, // commitment to the evidence
nonce, // caller-chosen, for PDA uniqueness
fee, // jurors_per_dispute * fee_per_juror
)?;
// 2. Read the ruling (lazy — call whenever, after finalization)
let ruling: Option<u8> = accord::get_ruling(ctx.accounts.dispute)?;import { Accord } from "@accord/sdk";
// File a dispute
const { dispute } = await accord.createDispute({
subaccord: subaccordAddress,
options: [hashOption("Yes"), hashOption("No")],
evidenceHash: evidenceCommitment,
nonce: 1n,
fee: requiredFee,
});
// Later: read the ruling (0 = option A, 1 = option B, null = not final)
const ruling = await accord.getRuling(dispute);Note
The full instruction surface (24 instructions) is documented in the
Protocol Reference and
programs/accord/SPEC.md. Integrators normally only need create_dispute
and get_ruling; the rest are permissionless cranks.
The program is configured on-chain (per-Subaccord params), not via env vars. Local development needs only Solana CLI config:
| Variable | Description | Example |
|---|---|---|
ANCHOR_WALLET |
Path to the provider keypair (defaults to Solana CLI config) | ~/.config/solana/id.json |
RPC_URL |
Cluster RPC endpoint (or use solana config set --url) |
localhost:8899 |
Anchor.toml pins the provider:
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"All orchestration lives in the root Makefile. The root package.json has no
scripts by design.
| Command | Description |
|---|---|
make prep |
Install Solana 3.1.10 + Anchor 1.0.2 (via avm), then pnpm install |
make build |
anchor build (programs) then pnpm -r run build (packages/apps) |
make test |
Rust unit tests + jest suite against a local validator (anchor test) |
make test_unit |
LiteSVM Rust unit/TDD tests (fast, no validator) |
make run_surfpool |
Start a Surfpool local fork (separate terminal) |
make test_surfpool |
Full suite against a running Surfpool instance |
make lint |
Lint every workspace that declares a lint script |
make clean |
Remove build artifacts and node_modules |
cd programs/accord && cargo test |
Rust unit tests in isolation |
cd packages/sdk && pnpm run build |
Build the SDK |
cd tests && npx jest -t "<name>" |
Run a single integration test by name |
cd apps/docs && poetry run mkdocs serve |
Serve the docs site locally (localhost:8000) |
Per-package lint auto-fix (where defined):
pnpm --filter @accord/sdk run lint:fixThe project uses two complementary harnesses (decision veridao-8ys4):
- Location:
programs/accord/tests/*_litesvm.rs - Run:
make test_unit - What it is:
anchor-litesvm0.4.xruns the real compiled.soin-process — no validator. One freshAnchorLiteSVMcontext per test. Each instruction has a test file covering happy-path, authority, reinit guard, timelock, arithmetic, and closure cases.
- Location:
tests/*.spec.ts - Run:
make run_surfpool(start the fork), thenmake test_surfpool - What it is: the real validator behaviour — CPI chains, VRF, token
transfers. Long-running (
testTimeout: 120000).
Every feature/instruction follows RED → GREEN → REFACTOR. The failing test
ships first; no exceptions. A milestone is completed only when all its leaf
tests are green.
Important
The no-entrypoint feature quirk. The program's entrypoint! symbol
collides with a builtin when the crate is statically linked into the test
binary. Rust tests therefore build accord with --features no-entrypoint
(types only). The .so — built separately via cargo build-sbf /
anchor build with the entrypoint — is what LiteSVM loads. All
*_litesvm.rs files are gated with #![cfg(feature = "no-entrypoint")] so
anchor build (which doesn't pass the feature) skips them during IDL gen.
make test_unit handles both steps.
| Component | Status | Notes |
|---|---|---|
programs/accord (on-chain) |
✅ Implemented | Full v1 instruction set + per-instruction LiteSVM tests |
Formal verification (accord.qedspec) |
Four economic invariants modeled; pending VRF/param-bounds binding | |
@accord/sdk (TypeScript) |
🚧 Scaffolded | Codama codegen pipeline in progress (ADR-0010); facade stub only |
tests/ (jest/Surfpool) |
🚧 Scaffolded | Harness configured; integration specs in progress |
apps/docs (MkDocs) |
✅ Live | Full integration guide, protocol reference, security docs, ADRs |
| Security audit | ❌ Not started | Pre-mainnet; do not secure real value yet |
The program ID is RokLJyruq34Ubtaj8mFnQETKcZpNCbW6k6xsgrMoHEe, kept in sync
across declare_id!, Anchor.toml, and target/deploy/accord-keypair.json
via anchor keys sync.
# Devnet
solana config set --url devnet
anchor build
anchor deploy --provider.cluster devnet
# Verify the deployed program
solana program show RokLJyruq34Ubtaj8mFnQETKcZpNCbW6k6xsgrMoHEeThe upgrade authority is a Squads multisig at launch; after a sufficient
audit it is set to None (frozen, immutable). The on-chain PauseState
singleton (seeds ["pause"]) is a separate circuit breaker: pause() is
instant and authority-gated; unpause() is timelocked
(propose_unpause → execute_unpause after UNPAUSE_TIMELOCK_SLOTS) so a
freeze is always recoverable on a known schedule. While paused, create_dispute
/ stake / appeal revert; in-flight disputes resolve normally.
Bundle the pause-singleton init with deploy (front-running is an ops concern):
# initialize_pause — the caller becomes the pause authority (the Squads multisig).
# Invoke it once, ideally bundled with the deploy tx (front-running is an ops
# concern). There is no Makefile target yet — call the instruction directly via
# the SDK / a small script, e.g.:
# accord.methods.initializePause().accounts({...}).rpc()Cause: Solana CLI < 3.x bundles platform-tools v1.48 / cargo 1.84, which
can't parse edition2024 manifests.
Fix: make prep installs Solana 3.1.10, which drops the flag. If you
must invoke cargo build-sbf directly on an older CLI, pass
--tools-version v1.52. (anchor build manages its own toolchain and is
unaffected.)
The .so must be built before the unit tests load it. make test_unit
does both; if you run cargo test directly, build first:
cargo build-sbf --manifest-path programs/accord/Cargo.toml
cargo test --manifest-path programs/accord/Cargo.toml --features no-entrypointanchor keys syncThis rewrites declare_id! and Anchor.toml [programs.*] from
target/deploy/accord-keypair.json.
On Anchor 1.0.2 + Solana 3.x deps, IDL generation is unblocked end-to-end
via the idl-build feature (see programs/accord/Cargo.toml). If you hit an
older Anchor, ensure the crate declares idl-build in [features].
Integration tests need a running validator. Start Surfpool first:
make run_surfpool # keeps running; use a separate terminal
make test_surfpoolEnsure the Solana BPF toolchain and system libs are present. make prep
handles the Solana side; for host crates you need a working rustc (stable)
and standard build essentials (build-essential / Xcode CLT).
- Read first:
CONTEXT.md(domain language) → this README →apps/docs/docs/adr/(the why behind every locked decision). - TDD only. Write the failing test first, then implement to pass.
- Lint is law. Run
make lint(and the relevant test) before committing. Pre-commit hooks (fmt,cargo-check,markdownlint,gitleaks,detect-private-key) run automatically. - Track work with beans. This repo uses the
beansCLI for issue tracking. Checkbeans list --json --readybefore assuming docs reflect reality — active milestones may supersede code state. Include relevant bean IDs in commit messages (the bean prefix isaccord-per.beans.yml). - ADRs are immutable once deployed. A superseded decision gets a new ADR that references the old one.
Install the git hooks:
pip install pre-commit
pre-commit installUNLICENSED (private) — see package.json. The program crate
(programs/accord) ships under the workspace license.
- Docs site: docs (domain TBD) — Quickstart, Integration Guide, Protocol Reference, Security, ADRs
CONTEXT.md— domain language / ubiquitous-language glossaryPROJECT.md— project rationale (the "why")BRAND.md— brand modelprograms/accord/SPEC.md— v1 build spec (account model, state machine, economics, edge cases)programs/accord/security-checklist.md— security audit authority (findings citefile:line)- ADRs (
apps/docs/docs/adr/):- 0001 Schelling-point Accord replaces hired-judge committee
- 0002 Per-Subaccord staking token, no Accord token in v1
- 0003 Draw — Merkle snapshot, off-chain sortition, distinct Jurors
- 0004 Party-agnostic; appeal is permissionless
- 0005 Subaccord authority — pubkey, 48h timelock
- 0006 Evidence — on-chain hash, trusted re-encryption operator
- 0007 Upgrade authority — Squads multisig, then freeze
- 0008 Snapshot trust hardening — anchor-slot, fraud predicates, sortition
- 0009 Stake-weighted verifiable sortition — MST, committed VRF
- 0010 SDK — Codama codegen + Solana Kit facade
ACCORD
An accord, not a committee.