Resolver committee governance: onboarding and offboarding
This is the human, off-chain half of running a resolver committee. See
DEPLOYMENT.md for the on-chain
mechanics (update_resolvers, propose_rotation, vote_rotation) and
ROTATION_DESIGN.md for why self-rotation works the way it
does. Neither of those documents says who should hold a resolver seat, how that
person secures their signing key, what they’re expected to do when a dispute
lands, or what actually happens the day a resolver needs to be replaced. This
document is that operational layer, for anyone running a real (non-toy)
deployment.
None of this is enforced by the contract. initialize, update_resolvers, and
propose_rotation accept any distinct addresses; nothing on-chain checks who
controls them, how the key was generated, or whether the person behind it agreed
to anything. The guarantees below come entirely from the deployment operator
choosing to follow this process and documenting that they have.
Onboarding a new resolver
Selection criteria
V1_MAINNET_PARAMETERS.md
already establishes the two properties that matter most for anyone on the
committee: reachability within challenge_window_secs, and independence from
the other resolvers (no shared employer, custody provider, or coordination
channel that turns several addresses into one correlated point of failure).
Onboarding is where those properties get checked before a seat is handed out,
not after:
- Domain competence. A resolver has to be able to actually judge the
claims this specific deployment adjudicates. What that means varies by
deployment: for a freelance-escrow use case (see
demos/freelance-escrow) it might mean familiarity with the kind of work being disputed; for an oracle-style deployment it might mean the ability to verify a claimed real-world outcome from public sources. Generic trustworthiness isn’t a substitute for being able to tell a good assertion from a bad one in the deployment’s actual domain. - Demonstrated reachability. Ask for evidence, not a promise: a track
record of availability on a timescale compatible with
challenge_window_secsfor this deployment. Someone who is highly qualified but unreachable within the window is worse for the committee than someone less qualified who reliably responds. - Independence from the existing committee and from likely parties. Screen for shared employer, shared custody provider, and shared off-chain coordination (the same group chat, the same on-call rotation) with current resolvers, per the composition guidance already linked above. Also screen for relationships to parties likely to assert or dispute on this deployment; see the conflict-of-interest section below for why this matters beyond what the contract itself checks.
- No conflicting role in the deployment. A resolver shouldn’t also be an
operator with an economic stake in outcomes (e.g. the deployer taking a cut
of disputed bonds) beyond the ordinary
finalize_reward_bpsmechanism, which applies uniformly and isn’t resolver-specific.
Key generation and custody
The resolver’s signing key is what resolve, propose_rotation, and
vote_rotation authenticate against, so its custody model should match what
the resolver seat is actually worth to an attacker: the value of the disputes
it’s likely to swing, not the deployment’s total value locked.
- Plain single key (
stellar keys generate). What the canonical testnet deployment uses today, and reasonable for a low-value or clearly-labeled testnet/staging deployment where the committee itself is a stopgap (see DEPLOYMENT.md’s own caveat aboutresolver1/resolver2/resolver3). Fastest to set up, weakest guarantee: a single lost or leaked key is a fully compromised resolver seat with no recourse except rotating them out. - Hardware wallet. Recommended default once a deployment has any real
value at stake. The resolver holds one address’s worth of signing power on
a device that never exposes the private key to a networked machine.
Meaningfully raises the bar against remote compromise without adding
operational complexity for the resolver’s day-to-day
resolvecalls, and doesn’t require coordinating with anyone else to vote. - Multisig behind the resolver’s address. Appropriate when a resolver seat represents an organization rather than an individual (e.g. a curated public list, or the “broad, visible representation” case V1_MAINNET_PARAMETERS.md describes for 7+-member committees), or when a single resolver’s vote carries enough weight that no one person should be able to cast it unilaterally. Adds coordination overhead to every single vote, which cuts against reachability within the challenge window; weigh that cost against the deployment’s actual value at stake before choosing this over a hardware wallet.
Whichever model is chosen, the resolver (not the deployment operator) should control the key material. The operator’s role is validating that a reasonable custody model is in place before proposing the address for the committee, not holding the key on the resolver’s behalf.
Commitments a resolver makes
Before an address is proposed for initialize, update_resolvers, or
propose_rotation, the person behind it should explicitly agree to:
- A response-time commitment compatible with
challenge_window_secs. State it in concrete terms (e.g. “will vote on an open dispute within N hours of being notified”) rather than “will be reachable,” so both sides can later judge objectively whether it was kept. This is the off-chain counterpart to the reachability criterion above, made auditable. - Conflict-of-interest disclosure, both at onboarding and ongoing.
resolverejects a vote where the resolver is also the assertion’sasserterordisputer(SelfVote, added in PR #203, closing #165). That check is necessarily narrow. It only catches the resolver being a direct party to the specific dispute in front of them. It cannot catch a resolver with an undisclosed financial or personal relationship to one of the parties, since the contract has no way to know about relationships that don’t show up as an on-chain address match. A resolver should disclose any such relationship to the rest of the committee (and recuse informally by voting neither way) the moment they become aware a specific dispute implicates it. This disclosure is not enforced by the contract and depends entirely on the resolver’s own good faith. - Notice before going dark. A resolver who knows they’ll be unreachable for an extended period (travel, planned unavailability) should say so ahead of time, so the committee can decide whether to route around it or start a replacement conversation before a dispute actually stalls.
None of these commitments are enforceable by the contract; they’re operating norms the deployment should document and hold resolvers to. Keeping a record of who agreed to what (and when) is what makes a later removal-for-cause defensible instead of an admin’s unilateral judgment call.
Offboarding and replacement
What triggers a replacement
- Inactivity. A resolver who repeatedly misses their response-time
commitment, or who has gone dark without notice. Note the distinction from
the contract’s own liveness fallback:
reclaim_stalled_dispute(when a deployment hasset_stall_timeoutconfigured) unwinds a single stalled dispute by returning both bonds with no winner — it resolves that one case, it does not remove the unresponsive resolver from the committee. Persistent inactivity is a reason to replace the resolver, not just a reason to reclaim the dispute they stalled. - Compromised key. Any signal that a resolver’s signing key may have leaked or been used without their authorization. Treat this as urgent: see the admin-override guidance below.
- Conflict of interest. A disclosed (or discovered) relationship to a party that makes continued service on the committee inappropriate, whether or not any specific vote was actually affected.
- Resignation. A resolver who no longer wants or is able to serve.
Who initiates it
Either the affected resolver themselves (resignation, disclosed conflict, planned unavailability) or another committee member who observes a problem (inactivity, a suspected compromise, an undisclosed conflict they’ve learned of). The deployment operator (the admin key holder) can also initiate a replacement, but for anything short of a compromised key or a stalled committee, initiating through the committee’s own self-rotation path is preferable; see the choice of path below.
Choosing self-rotation vs. the admin override
Both paths write the same committee; the choice is about who should be making the decision and how urgently it needs to happen, not a technical constraint (both are documented in full in DEPLOYMENT.md):
- Use self-rotation (
propose_rotation/vote_rotation) for the routine cases: resignation, a disclosed conflict of interest, planned unavailability, or inactivity the rest of the committee can still act on without the outgoing resolver’s cooperation. This keeps the decision inside the committee, requires no admin involvement, and is the expected path for anything that isn’t an emergency. A resolver proposes their own removal (or another resolver proposes it, naming the affected seat), and the rest vote. - Use the admin override (
update_resolvers) only for the emergency cases: a compromised key that needs to be cut off immediately, or a committee that has lost the ability to reach majority at all (more than(n-1)/2resolvers simultaneously unreachable, uncooperative, or otherwise unable to vote). This is exactly the deadlock scenariodocs/src/ROTATION_DESIGN.mdcalls out: a compromised or deadlocked committee cannot be expected to self-heal by vote, which is why the admin path exists at all and stays pause-exempt. Using it for a routine, uncontested resignation is a bad default even though it’s technically capable of doing the same thing — it reintroduces the single-admin-key trust path that self-rotation exists to avoid, for a case that didn’t need it.
A real deployment needs to decide, in advance, who holds the admin key that can exercise this override and under what conditions they’re expected to use it (per V1_MAINNET_PARAMETERS.md’s own framing) — not discover the answer for the first time during an incident.
Practical notes on executing a replacement
- A rotation only ever affects disputes opened after it executes; the
outgoing resolver keeps their vote on any dispute already snapshotted
against the old committee (see
update_resolversin CONTRACT.md anddocs/src/ROTATION_DESIGN.md’s section on the per-dispute snapshot). If a resolver is being removed for a compromised key or an active conflict, check whether they’re a snapshotted voter on any currentlyDisputedassertion; removing them from the live committee does not retroactively remove their vote there. - Confirm the incoming resolver’s key and custody model (per the onboarding
section above) before the rotation executes, not after — once
vote_rotationreaches majority, the swap is immediate. - Document the reason for every replacement (which of the four triggers
above applied) alongside the on-chain
RotationExecuted/ResolversUpdatedevent. The event proves that the committee changed; it doesn’t record why, and that reasoning is what makes the deployment’s dispute history auditable later.