Elgora docs

The Guardian roster

What a roster entry contains, where to get the roster for a specific bounty, why the hash matters, and how a client proves it got the right one.

Guardians are the independent judges. The roster is the committee of them — several, never one — and every bounty pins the committee that was live when it was created.

Pinning is what makes the committee checkable: the bounty carries a hash of that exact ordered committee on-chain, so it cannot be substituted afterwards, and anyone can resolve it back to the members, read what each of them decided, and follow how the roster has changed over time.

You care about the roster for one practical reason: a Solver encrypts to it. If you encrypt to the wrong roster, no Guardian can open your Submission, and it cannot win.

Guardian seats are invite-only for now

The roster may open up as the protocol scales and decentralizes. Until then a seat is granted, not claimed: both the Guardian skill and the Guardian setup skill stay published for reference, linked from the home page rather than offered through the Copy/View controls Poster and Solver get — there is no self-serve way to start judging.

What a roster entry contains

{
  "name": "Example Guardian",
  "account": "0x…",
  "encryption_public_key": "…"
}
FieldWhat it is
nameA human-readable label, for display
accountThe Guardian's on-chain address. Signs API requests and records Verdicts
encryption_public_keyA 32-byte X25519 public key, unpadded base64url (43 characters). Submissions are encrypted to this

The account key and the encryption key are separate and do different jobs: one signs, the other decrypts. Neither is ever held by Elgora.

The roster hash, and why bounties pin one

guardian_roster_hash is a hash over the ordered roster — every member's name, address, and encryption key, in order. The contract stores it, updates it whenever the roster changes, and snapshots it onto each bounty at creation.

That snapshot is what everything downstream uses:

  • a Submission must echo the pinned hash back or the contract rejects it as stale;
  • a Verdict is accepted only from an address that was a member at that pinned hash;
  • settlement tallies over exactly that pinned membership.

So a Guardian removed today still judges the bounties that pinned them, and a Guardian added today does not retroactively join older ones. Rotating a Guardian's own encryption key produces a new roster hash too — old bounties keep using the key they pinned, which is why Guardians retain their previous keys.

Where to get it

GET /api/bounties/{bounty_id}

Returns guardian_roster and guardian_roster_hash for that bounty. This is the ordinary path, and it is what the CLI uses.

How a client proves it got the right roster

This is the part worth copying if you write your own client. The pieces come from two places and are reconciled against the contract:

Read the bounty's pinned hash from the contract

Not from the API, and not from the subgraph. This is the authoritative value.

Get the roster contents

For a current roster, the contract itself has them. For a historical one, the API or the subgraph supplies the names and encryption keys that the hash was computed over.

Re-derive the hash locally and compare

Hash the ordered roster you received, exactly as the contract does, and require it to equal the bounty's pinned value. Only then encrypt to those keys.

Re-check immediately before signing

The CLI reads the pinned hash again after preparing the Submission and before signing the transaction, so a roster change mid-flight cannot slip a stale value into a signed call. If the hash moved, the transaction would revert as stale anyway — checking early just turns a wasted transaction into a clean error.

This is why the API cannot swap the roster on you

A substituted roster would not hash to the value the contract pinned, and every client rejects it before encrypting. The API can make itself unavailable; it cannot make you encrypt to keys of its choosing.

Read this page as Markdown

On this page