@katanga/sdk-verifier
v0.1.0
Published
Verifier-node SDK for the EP-710 dispute consensus engine on the Katanga marketplace.
Maintainers
Readme
@katanga/sdk-verifier
Verifier-node SDK for the EP-710 dispute consensus engine on the Katanga marketplace.
A verifier node:
- Watches
KatangaDisputeConsensus.VotingPeriodStartedevents. - Fetches the buyer + supplier evidence URIs (off-chain, hash-verified against the on-chain commitment).
- Runs a pluggable adjudicator to decide the outcome (
BUYER_WINS/SUPPLIER_WINS/SPLIT). - EIP-712-signs a
DisputeVotetyped-data message and posts the signature to a permissionless aggregator. - Anyone can then call
KatangaDisputeConsensus.consensusFinalize(escrowId, result, signatures[])once the threshold is met.
Install
pnpm add @katanga/sdk-verifierThe package is a workspace member; if you are inside this repo run:
pnpm install
pnpm --filter @katanga/sdk-verifier typecheck
pnpm --filter @katanga/sdk-verifier testCLI
katanga-verifier register-verifier --stake 100 # registry.registerVerifier{value: 100 FLR}
katanga-verifier exit-verifier # marks exit; stake unlocks after delay
katanga-verifier withdraw # post exit-delay
katanga-verifier claim-rewards # pull pending FLR rewards
katanga-verifier check-status # registry membership + claimable + balance
katanga-verifier list-disputes --monitor http://localhost:7110
katanga-verifier start # long-running runner (delegates to runner.ts)
katanga-verifier aggregator-serve --port 4710 # boot the reference aggregatorAll CLI commands read the same env vars as the runner — see .env.example.
Long-running runner
KATANGA_CHAIN_ID=114 \
KATANGA_PRIVATE_KEY=0x... \
KATANGA_VERIFIER_REGISTRY=0x... \
KATANGA_DISPUTE_CONSENSUS=0x... \
KATANGA_AGGREGATOR_PORT=4710 \
KATANGA_MONITOR_PORT=7110 \
pnpm --filter @katanga/sdk-verifier runnerThe runner boots in this order:
- Resolve chain config from
CHAIN_REGISTRY(sdk-core). - Build viem public + wallet clients from
KATANGA_PRIVATE_KEY. - Build the evidence fetcher (default:
ipfs://+https://transports). - Build the adjudicator from
KATANGA_ADJUDICATOR(defaultstub). - Construct
VerifierAgentand start watching events. - Optionally start
VerifierMonitor(KATANGA_MONITOR_PORT > 0). - Optionally start the reference aggregator (
KATANGA_AGGREGATOR_PORT > 0). - Wait for
SIGINT/SIGTERM; gracefully stop everything.
EIP-712 vote shape
The signed message is a DisputeVote typed-data struct, frozen by the EP-710 ABI:
struct DisputeVote {
uint256 escrowId;
uint8 result; // 0 / 1 / 2
bytes32 evidenceUriHashesAtVoteTime; // keccak256(buyerHash || supplierHash) at startVoting()
uint256 deadline; // == votingDeadline
}The EIP-712 domain pins chainId and the deployed KatangaDisputeConsensus address:
{
name: "KatangaDisputeConsensus",
version: "1",
chainId,
verifyingContract,
}Replay protection comes for free:
- Different escrow → different
escrowId→ different digest. - Different evidence set → different snapshot hash → different digest.
- Different chain → different
chainIdin domain → different digest. - Different deployed contract → different
verifyingContract→ different digest. - Past the voting deadline → contract rejects via
votingDeadlinecheck.
Evidence URI hashes — exact byte layout
Both sides (verifier signer + S1 contract) MUST agree on the byte layout, otherwise signatures silently fail to recover.
// Per-URI hash (committed on-chain by submitEvidence):
evidenceUriHash(uri) === keccak256(toBytes(uri));
// equivalent in Solidity: keccak256(bytes(evidenceURI))
// Snapshot hash (written into DisputeRecord.evidenceUriHashesAtVoteTime):
evidenceUriHashesSnapshot(buyerHash, supplierHash) ===
keccak256(concat([toBytes(buyerHash), toBytes(supplierHash)]));
// equivalent in Solidity: keccak256(abi.encodePacked(buyerHash, supplierHash))Order matters: buyer first, supplier second. Reversing yields a different hash.
If a party never submitted, the bytes32(0) sentinel is used in their slot — the snapshot still hashes deterministically.
Adjudicator interface
interface Adjudicator {
readonly name: string;
decide(ctx: AdjudicatorContext): Promise<DisputeResult>;
}Defaults shipped:
StubAdjudicator— always returnsBUYER_WINS, logsWARN: stub adjudicator — wire production verdict logic. Replace before pointing at real disputes; running with the stub will slash any verifier that disagrees.ManualAdjudicator— POSTs the context to a configurable webhook (KATANGA_ADJUDICATOR_WEBHOOK) which MUST respond with{ "result": 0 | 1 | 2 }.
Custom adjudicators implement Adjudicator and pass through VerifierAgentConfig.adjudicator.
Aggregator
The reference aggregator (Aggregator) stores signatures in-memory keyed by (escrowId, result, signer). It validates each post by recovering the signer locally and (optionally) checking the registry. It is permissionless by protocol design — multiple competing aggregators may coexist.
Endpoints:
POST /signatures— acceptsSignaturePostBody. Returns{ ok: true, recovered }.GET /disputes/:id/signatures— returns the full per-result bucket map.GET /disputes/:id/signatures?result=N— returns just theNbucket as aHex[].GET /health— unauthed health check.
All routes except /health require Authorization: Bearer <KATANGA_API_AUTH_TOKEN> when a token is configured.
Tests
pnpm --filter @katanga/sdk-verifier testThe shared dispute types live in @katanga/sdk-core/src/dispute.ts and have their own conformance tests in packages/sdk-core/test/dispute.test.ts.
Files
| Path | Purpose |
| ----------------------------------------------- | -------------------------------------------------------------- |
| src/agent.ts | VerifierAgent — event-driven state machine. |
| src/runner.ts | Long-running entry point. |
| src/cli.ts | katanga-verifier CLI. |
| src/monitor.ts | HTTP /health, /metrics, /disputes/active. |
| src/aggregator.ts | Reference signature aggregator HTTP server. |
| src/aggregator-client.ts | postSignature, fetchMajoritySignatures. |
| src/evidence-fetcher.ts | PluggableEvidenceFetcher + ipfs/https transports. |
| src/vote-signer.ts | EIP-712 sign + recover helpers. |
| src/dispute-watcher.ts | viem watchContractEvent wrappers. |
| src/adjudicator.ts | Adjudicator interface + StubAdjudicator + ManualAdjudicator. |
| src/api-auth.ts | Bearer-token helpers. |
| src/types.ts | Public + wire-shape types. |
