Enrolling a country
How a country's authorities come to exist on a chain that is already running: their keys made in their own offices, their groups created by transaction, and their powers granted by the foundation in one decision three custodians sign.
You need: the country's offices named and staffed, two to five people per office who can each open a link on their own device, three of the foundation's five custodians reachable, and somebody with a funded key who can broadcast.
You will end with: one x/group account per office, a role grant per office
scoped to that country and to nothing else, a jurisdiction record for each
office's own account, the first institutions placed so they can be admitted, and
a signed record naming every address and who granted what.
The steps, in the order they happen:
| Command | Who signs | |
|---|---|---|
| 1 | ceremony host — once per office |
each super user, in their own browser |
| 2 | ceremony country init |
nobody; it reads and refuses |
| 3 | ceremony country groups |
any funded key |
| 4 | ceremony country confirm |
nobody; it reads the chain back |
| 5 | ceremony country grants |
the foundation, M-of-N |
| 6 | ceremony country verify |
nobody; it reads the chain back |
| 7 | ceremony country seed |
the foundation, M-of-N |
| 8 | ceremony country admit |
the country's payments office, M-of-N |
| 9 | ceremony country record |
everybody, on paper |
ceremony country validator slots in wherever a validator needs placing. Each
step refuses until the previous one's evidence is in the dossier.
Why this is a separate ceremony
The key ceremony exists because one account mattered more than any other and had been created by a line of shell. This one exists for the opposite reason: because there are going to be a great many of these, in a hurry, and the thing that goes wrong at volume is not carelessness about a single key. It is a procedure with a step in it that looks optional.
The two share everything that touches key material. An office's super users
generate their keys in their own browsers, from the same ceremony host, over the
same code, with the same possession signatures, the same group assembly and the
same eighty-bit fingerprint read aloud before anybody signs anything. If you have
run the hosted foundation ceremony you have already run three quarters of this
one.
What differs is the far end, and it differs completely. The foundation's ceremony
produces a genesis fragment, because the foundation has to exist at height
zero — x/constitution refuses to start a chain whose recovery destination is
unset. A country's ceremony produces transactions and proposals, because the
chain is up, blocks are being made, and money is already moving.
That one difference is where all the danger in this document lives.
The one thing that will bite you
An x/group policy address is derived from the group policy sequence number
and nothing else. Not the members. Not the threshold. Not the admin. Not the
chain id. ceremony address --seq N prints it offline, and that is a real
property the launch runbook depends on.
On a live chain it is a trap.
Suppose this tool predicted an office's address, and a grant were composed naming
it. Whoever created a group policy first owns that address. The grant would still
be a real grant — PAYMENTS_AUTHORITY over a whole country, or
ENFORCEMENT_AUTHORITY, signed by the foundation, every signature valid, sitting
in the registry. And nothing downstream would notice, because every later check
reads the same predicted address and agrees with it.
So the enrolment is two phases, and the boundary between them is not negotiable:
- Create the group.
ceremony country groupswrites the transactions. It writes no address and composes no grant. - Read the address back.
ceremony country confirmtakes the chain's own answers and checks that the policy at that address really is this office — the same members, the same threshold, administering itself. - Then compose the grant.
ceremony country grantsrefuses outright for any office whose address has not been read back. There is no flag that relaxes it and no--seqto fall back on.
If you remember one thing from this document, remember that an office's address comes from the chain and is checked against its membership. Everything else here is bookkeeping by comparison.
Who admits a country, and what that cost
The foundation does — the 3-of-5 from the key ceremony, which is to say the
account x/constitution pins as enforcement_recovery_destination.
This used to be governance and nobody else, and the argument for that was good: there is a difference between using a power and deciding who holds one, and every widening of who may act ought to cost a public vote. What broke it was arithmetic. Enrolling one country is a group per office, two to five grants across those offices, and a jurisdiction record per office — a sequence that has to land in order. Under governance-only that was five or six separate proposals, each able to pass, fail or time out on its own, for one decision a room had already taken. The predictable result of that is a bundle proposal nobody reads.
What was given up is publicity and delay. The validator set no longer has a
veto over who administers a perimeter, and the appointment no longer sits in
public for a voting period before it takes effect. What replaces those is
narrower: three of five custodians from five organisations, on chain, with the
grant attributed in granted_by and the height in granted_at_height.
Chain-wide is still governance and nobody else. The foundation admitting a
country and the foundation manufacturing authority over every country are
different acts, and only the first was decided. * is refused from the foundation
by the chain, before the constitution is even consulted, and refused again by this
tool.
The residual risk, stated plainly because it is not closed: the foundation can
grant one office the same role in every country, one grant at a time, and arrive
where a chain-wide grant would by a longer road. It is bounded by the assigned
country list rather than prevented, and it is enumerable —
blockchaind query alias role-holders <CC> for each country,
blockchaind query alias chain-wide-grants for the exceptions.
Before the day
The offices
Decide them first, on paper, and decide the roles with them. The
roles and perimeter design has the five a
country's offices can hold: registry authority, monetary authority, payments
authority, enforcement authority and supervisor. There is a sixth,
ROLE_FOUNDATION_ADMINISTRATOR, and it is not one of these — it is chain-wide or
nothing, a grant naming a country is refused, and it belongs to
a different ceremony. A payments country needs
payments authority and enforcement authority at a minimum, and the tool
refuses without both unless you write down why.
Why those two. Without a payments authority nothing in the country can be admitted to the rail, so no account in the country can be onboarded or placed, and the country is enrolled and inert. Without an enforcement authority money moves in a perimeter and no office can stop it. If the country genuinely is registry-only or supervision-only, say so in the config:
"waivers": [{"rule": "payments-minimum", "reason": "land registry pilot; no payments licence yet"}]
The rule still refuses. What gets past it is a sentence that ends up on the signed
record, which is the same arrangement as preflight's
--network-acknowledged.
One person, one office
An office of two-of-three sharing a member with the office that supervises it is not the separation the roles describe. The tool refuses a super user who appears in two of a country's offices, because once both groups exist the arrangement is invisible: each policy looks correct on its own.
The threshold, and the minimum it may never fall below
Per office, and the same rules as the foundation's: never one (that is a key with extra steps), never unanimity (one person unreachable freezes the office permanently). Two-of-three and three-of-five are the shapes that work.
Decide two numbers per office and write both down, because they are different claims:
| What it is | Where it comes from | |
|---|---|---|
| The threshold | what the office is | the group file its super users signed |
| The minimum | what the office may never fall below | minimum in the config, decided before the day |
The minimum is the one that is new and the one that does the work. It goes onto
every grant as required_shape, and from then on the chain resolves the office's
group policy on every authority action and refuses when the office has fallen
below it. That is the difference between multisig as a fact checked once and
multisig as a state:
- An office is its own admin — that is what makes its membership changeable by its
own members and by nobody else. So a properly-ceremonied three-of-five can vote
itself to a one-of-one the next morning, and before this it went on freezing
accounts with nothing notified and nothing refused.
x/constitution's ante gate pins the foundation's shape and has never had anything to say about an office's. - Both numbers are floors, and only floors. Three-of-six is fine — a member joined. Four-of-six is fine — the office decided to be more careful. Three-of-four is refused: a member left and was not replaced, which moves sixty per cent to seventy-five and walks towards unanimity. One-of-five is refused for the obvious reason.
- "How many signatures" means how many people, not the threshold number.
x/groupcounts weight, so a group with a threshold of three whose members weigh 3, 1, 1, 1 and 1 is a one-of-five: the first member reaches the threshold alone. The chain takes the members in descending weight and counts how few of them can reach the threshold, which for the equal weights every ceremony produces is exactly the threshold.
ceremony country init refuses an office whose signed group file does not reach
its minimum, before any group exists on the chain. It also refuses a minimum of
one signature, and a unanimous minimum — the chain holds an office to its
minimum, so a floor of three-of-three is a promise that the office may never
survive a lost key.
Choose the minimum at or below the shape you are actually standing up. A country agreeing three-of-five and generating three-of-five gets a grant it satisfies exactly, and one lost key takes the office out of service until it is replaced — which is the intended behaviour and is why departures and replacements are one decision. A country wanting slack can agree three-of-five and stand up four-of-seven; the chain holds it to the three-of-five.
What the foundation needs to be able to do
Two separate things, held in two places with two different costs to change. Check both before the day, because discovering the second one late means a governance cycle in the middle of a ceremony.
| The foundation must | Where that is decided | Cost to change |
|---|---|---|
| grant a role in a country | it is the address x/constitution pins as enforcement_recovery_destination |
a constitutional amendment |
| record a jurisdiction | it holds ROLE_FOUNDATION_ADMINISTRATOR at the chain-wide * scope |
one governance proposal |
The second is the one people miss. An office's group account was onboarded by no participant, so nobody but a foundation administrator or governance may record where it is — and that record is part of what makes the office real. So:
blockchaind query alias chain-wide-grants
If the foundation's policy address does not hold that grant, have it granted by a
governance proposal, once, before enrolling any country. It is not part of an
enrolment and it should not be. ceremony country grants checks it and refuses
rather than composing a proposal that would be voted through and then fail. See
appointing a foundation administrator.
One mechanism where there used to be two. That second row used to read
"it is in alias.params.foundation_administrators" — a repeated field on a
module parameter, edited by MsgUpdateParams, with no relationship to the grant
registry at all beyond sharing the word "foundation". So an operator standing up
a country had to check two unrelated kinds of state and satisfy them by two
unrelated messages, and holding one without the other produced a proposal that
passed and an office that could not work. Both facts now live in the same
registry as the offices' own grants: one query enumerates them, MsgGrantRole
and MsgRevokeRole write both, and every grant carries granted_by and
granted_at_height.
What has not collapsed is the two costs, and the table still has two rows for
that reason. Who may grant a role inside a country is a constitutional invariant;
who may place an account that no participant onboarded is a chain-wide grant that
governance alone may make. An enrolment needs both, and ceremony country grants
requires a file for each.
The required_shape on that grant is worth reading while you are there.
MsgSetJurisdiction resolves the foundation's own group policy and holds it to
whatever the grant records, so a foundation that has fallen below its recorded
M-of-N will pass every check this tool can make and still be refused by the
chain — the file says what shape is required and nothing about the group's shape
today.
The sequence
1. Generate each office's keys
One ceremony host per office, with the country and the roles set:
go build -o ceremony ./tools/ceremony
./ceremony host --public-url https://pay.yamalelegal.com/ceremony/ --out ./senegal-payments
The coordinator's setup screen takes the office name, the roster, the threshold, the chain id — and now a country and a list of roles. Fill them in. They are not decoration:
The country and the roles are inside the parameters fingerprint every super user
reads before generating a key. That is the point. Without them the sequence is:
three people are told they are generating keys for Senegal's payments authority,
they compare a fingerprint, they sign — and then a config pairs their group with a
grant of enforcement authority over Nigeria, with every signature still verifying.
With them in the fingerprint, a config that disagrees with what they signed is a
refusal, and ceremony country init is where it refuses.
Leave the country blank and it is a foundation ceremony. ceremony country refuses
a foundation ceremony used as an office, and says why: those custodians attested to
holding the chain's recovery destination, and nothing they signed mentions
administering a country.
Everything else is the hosted ceremony as documented: the words appear once and are never transmitted, a link speaks for one person, the page will not let anybody attest to a group their own key is not in. Read the hosted ceremony before running one, and in particular read which of the two paths is stronger — the same trade applies here, and for a country whose enforcement authority will be able to freeze accounts, the air-gapped path is still the stronger one.
What each office ends with is group.json in its own directory, and a record its
super users sign. Keep the group fingerprint. It is written into the enrolment
record later and it is what ties an on-chain office back to the ceremony that made
its keys.
2. Write the enrolment config
{
"ceremony": "Senegal enrolment",
"chain_id": "yamale-testnet-1",
"country": "SN",
"foundation": "yml1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzs3xm8uj",
"offices": [
{
"name": "Senegal payments authority",
"roles": ["ROLE_PAYMENTS_AUTHORITY", "ROLE_ENFORCEMENT_AUTHORITY"],
"group": "./senegal-payments/group.json",
"minimum": {"signatures": 3, "members": 5}
},
{
"name": "Senegal lands commission",
"roles": ["ROLE_REGISTRY_AUTHORITY"],
"group": "./senegal-lands/group.json",
"minimum": {"signatures": 2, "members": 3}
}
]
}
Note what is not in it: no thresholds, no member addresses, no policy addresses. The first two come from the group files, which the offices' super users signed for; the third comes from the chain. A config field for any of them would be a field somebody could fill in wrongly, and two of the three decide who ends up holding a country's authority.
minimum is the exception, and it is not the same kind of thing. An actual
threshold in a config is an observation, and one that could contradict the
keys — a config claiming three-of-five over a group file that says one-of-one
would be a config that lies about who holds an authority. A minimum is a
constraint: the decision the country took before anybody generated a key, and
what the ceremony holds the signed group file against. It is required per office,
with no default, because a default of "no minimum" would silently produce exactly
the state it exists to prevent.
./ceremony country init --config senegal.json
This prints every office, its threshold, the minimum it may never fall below, its
members with their fingerprints, its roles — and no address for anything,
which is not an omission. It writes country-SN.json, the dossier, which every
later step reads and rewrites.
Read the printed fingerprints against the records the offices signed. This is the last moment at which a wrong group file costs nothing.
It is also where a shape that does not match the decision is refused:
Senegal payments authority must be at least 3-of-5 and ./senegal-payments/group.json
was generated as 2-of-3: its threshold of 2 is below the 3 signatures this enrolment
requires.
That is the tool doing its job, and the two ways out are both deliberate: run the office's ceremony again with the agreed threshold, or change the minimum in the config and have the change agreed. Not silently — the chain will hold the office to whichever number ends up on the grant, for as long as the grant exists.
3. Create the groups
./ceremony country groups --dossier country-SN.json --from operator
Two files per office and the exact command line for each. Broadcast them.
Any funded key can broadcast these, and that is safe rather than sloppy.
--group-policy-as-admin makes the policy its own admin, so the account that
signs keeps nothing: it cannot change the membership, the threshold, or who
administers the office afterwards. Step 4 checks that it came out that way rather
than assuming it.
4. Read the addresses back
For each office:
blockchaind query tx <hash> -o json > tx.json
# the policy address is in the EventCreateGroupPolicy in that file
blockchaind query group group-policy-info <address> -o json > policy.json
blockchaind query group group-members <group id> -o json > members.json
./ceremony country confirm --dossier country-SN.json \
--office "Senegal payments authority" \
--tx tx.json --policy policy.json --members members.json
Three files, and yes, that is more copying than fetching would be. tools/ceremony
makes no outbound network connections, and the key ceremony guide makes that claim
to people who are about to type a seed phrase in front of it. One net/http call
here would cost the claim to save a paste.
What it buys is that the address is verified rather than trusted. A fetched address would be believed because it came over a socket. This one is checked against what the office is:
| Refusal | Why it matters |
|---|---|
| the transaction created no group policy | you queried the wrong transaction |
| the policy is for a different address than the transaction created | the grant would land on whichever one the tool believed |
| the group id disagrees between the three documents | one of them is about something else |
| the policy is not its own admin | somebody outside can rewrite the membership, so the threshold is advisory |
| the threshold is not what the super users attested to | the office on the chain is not the office they agreed to |
| the members are not exactly the office's roster | this is the whole check — see below |
| the address is the foundation's own | the foundation would be appointing itself |
The member check is set equality both ways. Not "the office's members are among the group's" — a group with a fourth member is a two-of-four. Not "the group's are among the office's" — a group missing one is a two-of-two, which concentrates more authority in whoever remains than the ceremony gave them. And a member with weight 2 is refused, because equal weight is what makes "two of three" mean what it says.
Note the trap in the transaction result. A broadcast reporting code: 0 has
been accepted into a mempool. It has not executed. The tool refuses a document
with height: 0 for exactly this reason and tells you to query it instead. This
is not hypothetical — see the next section, where the same trap has a much worse
version.
5. The enrolment proposal
blockchaind query constitution invariants -o json > invariants.json
blockchaind query alias chain-wide-grants -o json > chain-wide-grants.json
./ceremony country grants --dossier country-SN.json \
--proposer <a foundation custodian> \
--invariants invariants.json --chain-wide-grants chain-wide-grants.json
Both files are required and they are the two rows of
the table above, read off the
chain rather than assumed: --invariants because a proposal built for any
address but the one x/constitution pins would be voted through by three
custodians and then refused, and --chain-wide-grants because placing an
office's own account needs the foundation to hold
ROLE_FOUNDATION_ADMINISTRATOR, which is a governance decision rather than a
constitutional one.
One proposal for the whole country, carrying, in this order:
MsgSetJurisdictionper office, placing its own group account in the country, signed by the foundation as a foundation administrator;MsgGrantRoleper office per role, scoped to the country, never to*.
One proposal and not one per grant, deliberately. x/group executes a
proposal's messages together or not at all, so this is the only shape in which
"the country is enrolled" is a thing that either happened or did not. Split up,
the state in between is a country with a payments authority and no enforcement
authority — money moving in a perimeter nobody can stop. The cost is a longer
document for three custodians to read, and the summary states every grant in words
so that a custodian who reads only the summary has read the proposal.
Submit it, and three custodians vote. Use the foundation console — it decodes a proposal into words, counts the votes, and composes the commands to run where the keys are.
Read the proposal on your own node before voting. Not this tool's summary of it, and not the console's:
blockchaind query group proposals-by-group-policy <foundation> -o json
6. Verify that it actually happened
This step is not optional and here is why.
blockchaind tx group exec on a proposal that has not reached its threshold
returns code: 0. Not an authorization error. Not a failed transaction. A
clean, successful transaction that does nothing at all. The only signal anywhere
is an event attribute:
EventExec.result = PROPOSAL_EXECUTOR_RESULT_NOT_RUN # two of five
EventExec.result = PROPOSAL_EXECUTOR_RESULT_SUCCESS # three of five
So a script that checked the exit status, or even query tx | .code, would
conclude the foundation had acted on two signatures. And an executed proposal is
pruned, so query group proposal <id> afterwards returning "not found" is
success rather than a fault.
Do not infer. Read the registry:
blockchaind query alias role-grants <office policy address> -o json > grants.json
blockchaind query alias jurisdiction <office policy address> -o json > placed.json
./ceremony country verify --dossier country-SN.json \
--office "Senegal payments authority" \
--grants grants.json --jurisdiction placed.json
It checks that every role the dossier describes is present, in this country, and granted by the foundation — that last part as much as the first, because a grant of the right role in the right country made by some other authority is somebody else's act, and recording it on this enrolment's record would put your signature on it.
Grants the office holds that this enrolment did not make are reported, not refused. An office may legitimately have been granted something by governance. An office holding a chain-wide grant is the single most important thing a reader of the record could want to know, so it appears on the screen rather than being filtered out.
7. Place the first institutions
This is the step nothing announces, and skipping it produces a chain that looks broken.
The country's payments authority now holds a perfectly good grant. It still cannot
admit the first bank in its own country. x/paymsg's delegated approval path calls
the perimeter check on the applicant, and the perimeter check refuses a target
the chain cannot place before it consults any grant. The applicant has no
jurisdiction record. It may not declare its own. And there is no approved
participant in the country yet to record one, because that is what is being
attempted.
So the foundation records it, once, for the seed:
./ceremony country seed --dossier country-SN.json \
--proposer <a foundation custodian> --account <applicant>
Another foundation proposal, three votes, and then:
blockchaind query alias jurisdiction <applicant> -o json > placed.json
./ceremony country seed --dossier country-SN.json --account <applicant> --verified placed.json
Every account after these is placed by the participant that onboarded it, which is where the record belongs: the participant did the KYC and is the only party that knows the answer. The seed is a bootstrap and not a pattern. A deployment that kept using it would have the foundation asserting the country of accounts it has never met, which is exactly the arrangement the perimeter design rejects.
8. The payments authority admits them
./ceremony country admit --dossier country-SN.json \
--proposer <an office super user> --applicant <bank>
A proposal to the office's group, not the foundation's. This is what enrolling the country bought: licensing a payment service provider in Senegal is decided by Senegal's payments authority, M-of-N, and not by the foundation and not by a chain-wide governance vote.
The tool refuses to compose it until two things have been read back off the chain — the office's grant, and the applicant's placement — because both failures look identical from the outside. A payments authority whose grant never executed refuses exactly as one that was never appointed does.
Then verify, as always:
blockchaind query paymsg get-approved-participant <bank> -o json > admitted.json
./ceremony country admit --dossier country-SN.json --applicant <bank> --verified admitted.json
9. The record
./ceremony country record --dossier country-SN.json --config record.json
{
"location": "Dakar",
"started_at": "2026-09-01T09:00:00Z",
"completed_at": "2026-09-01T15:30:00Z",
"proposal_id": "4",
"custodians": ["Amara Okafor", "Chipo Mwale", "Eshe Njoroge"],
"participants": [
{"name": "R. Lead", "role": "enrolment lead", "organisation": "Yamale Foundation"},
{"name": "S. Scribe", "role": "scribe", "organisation": "Yamale Foundation"},
{"name": "O. Observer", "role": "independent observer", "organisation": "External Auditors LLP"}
],
"notes": []
}
Every address, threshold, grant and fingerprint in the rendered record is read out of the dossier — which is to say out of the chain's own answers, verified. Nothing is retyped. An enrolment record carrying a mistyped office address would be a document whose only purpose is to detect mistyped office addresses.
It refuses to render if any office is unconfirmed or any grant unverified. A record signed at that point would state that a country's authorities hold powers nobody has checked they hold, and an accepted-but-unexecuted proposal looks exactly like that.
One claim in it is typed rather than read, and the record says so: which three custodians voted. That is a fact on the chain, in the proposal's votes, and this tool cannot read it. The record names the proposal so a reader can check.
Print it. Everybody signs the paper copy before leaving — every office's super users, the lead, the scribe and the observer.
Checking somebody else's enrolment
Every claim in the record is a query. This is the whole point of putting the addresses in it.
# the foundation is who the record says it is, and cannot be changed by a vote
blockchaind query constitution invariants
# each office is the group the record describes
blockchaind query group group-members <group id>
blockchaind query group group-policies-by-group <group id>
# and holds exactly the roles the record lists, granted by the foundation
blockchaind query alias role-grants <office policy address>
# everybody who may act in the country, from the country's end
blockchaind query alias role-holders SN
# and everybody no border bounds, which should be a very short list
blockchaind query alias chain-wide-grants
The last two are the supervisory pair. role-holders SN deliberately does not
fold in the chain-wide grants: a country's own list should show what that country
granted, and mixing the exceptions in would hide them among the ordinary entries
of every country at once.
What an authority can and cannot do once enrolled
The perimeter is not advisory and it is not checked once at appointment. It is
AssertScope, in every message server that acts on an account, against current
state, every time.
So Senegal's enforcement authority can open a case against an account recorded in
SN and is refused — ErrOutOfScope, naming the role and the country — against
an account recorded anywhere else. An account the chain cannot place at all is
refused to everybody, including the holder of a chain-wide grant, and that refusal
comes before any grant is consulted.
That last one is worth internalising, because it is the shape of most surprises here: a great many failures that look like "my authority does not work" are actually "the account you named has no jurisdiction record".
It has one consequence that is a real loss rather than a diagnosis, and it
belongs here rather than in a footnote. MsgEmergencyRelease is scoped to the
country the case's target is recorded in, so an account the chain cannot place
cannot be released by that message at all — there is no office whose
perimeter reaches it. What is left for that account is the provisional freeze
lapsing by itself and a governance MsgReverseCase. Both are slower and both
exist, and the way to not need them is to make sure every account an authority
might act on has been placed.
What each of the five roles actually confers
Stated here because an operator otherwise grants a role and then spends an afternoon finding out what it does. Two rows of this table used to say "nothing yet"; both of them now do something, and the second one does something an enrolment has to plan for.
| Role | What consults it |
|---|---|
ROLE_PAYMENTS_AUTHORITY |
x/paymsg — admitting a participant |
ROLE_MONETARY_AUTHORITY |
x/stablecoin — approving an issuer |
ROLE_REGISTRY_AUTHORITY |
x/land — parcels, transfers, freezes |
ROLE_ENFORCEMENT_AUTHORITY |
x/enforcement — opening a case, and the emergency freeze and release |
ROLE_SUPERVISOR |
x/alias — the payload-reader set, and the precondition on appointing a regulator |
A country's enforcement office can now act, and this page used to say it could
not. It said the office held a grant no message let it use, because MsgOpenCase
required a bonded validator and a group policy account cannot be one, and
MsgEmergencyFreeze required the signer to be x/enforcement's
emergency_authority parameter. Both of those gates are gone. MsgOpenCase now
accepts either a bonded validator or a holder of this role covering the target's
country, and the emergency messages are authorised by this role and by nothing
else — the parameter is retired.
So Senegal's enforcement office, holding a grant scoped to SN, can:
- open a case against an account recorded in
SN, for a freeze or for a seizure, recorded on the case under its own group address; - freeze immediately with
MsgEmergencyFreeze, which opens a real case, imposes the same provisional freeze and goes into the same voting queue; - release a freeze with
MsgEmergencyRelease, scoped to the country the case's target is recorded in rather than to anything the message names; - withdraw a case it opened, on identity alone — which still works if the grant is revoked afterwards, because withdrawing is de-escalation.
What it still cannot do is decide anything. A case is resolved by two thirds
of bonded voting power on MsgVoteCase, and an office that is not a validator has
no vote to cast. It can stop money in SN for a day, in public, under its own
name; the validator set decides whether any of it is taken, and only governance
can reverse a case that passed. See the enforcement guide.
ROLE_SUPERVISOR is a reading entitlement, and the shape of it is decided by a
fact about gRPC. A query carries no signer, so the chain cannot gate a read by
role and any design that pretended to would be worse than an empty role. What it
can do is publish the set a sender must seal to. A holder of the role covering
SN is entitled to be a viewing-key recipient of the encrypted payload of every
payment whose declared settlement jurisdiction is SN — the same declaration that
decides which authority may act on a cross-border payment. The set is published by
Query/PayloadReaders, which is not in the CLI; reach it over gRPC or at
GET /yamale/blockchain/alias/v1/payload_readers/{country}. It returns the
country's appointed regulator first, then every supervisor granted in that country
or chain-wide, each with its current viewing key, deduplicated on the address.
Two consequences worth knowing before granting it. A country's appointed regulator
must hold this role covering that country: MsgAppointRegulator refuses an
appointee that does not, so the most powerful grant the confidentiality design
makes cannot go to an account holding nothing in the perimeter registry. It is
checked on the write and not re-read afterwards, so revoking a sitting regulator's
supervisor grant does not unappoint it — an appointment that could evaporate would
leave a country's payments sealed to an authority the chain no longer lists, with
nothing saying when it stopped.
And the chain cannot force a sender to seal to a supervisor. The envelope is built off-chain and the chain only ever sees a hash of the plaintext, so a sender that ignores the published set produces a payload the supervisor can never open and nothing on chain detects it. That is a limit of where the ciphertext lives rather than a gap in the registry, and it is the reason the role confers nothing else at all: a supervisor may not open a case, freeze, seize, register or validate land, admit an issuer or a participant, grant a role, correct a jurisdiction, or appoint a regulator.
Validators are a different registry, and the difference is real
x/validatorgov collects a validator's jurisdiction in its application, alongside
its legal entity and beneficial owner, and validates it against the same assigned
country list. It does not write it into x/alias.
That is not an oversight and it is not being fixed by merging them. The two facts have different provenance:
- a validator declaring Senegal is a signed claim by its operator;
- Senegal's participant recording Senegal is a finding by whoever did the KYC.
Merging either direction destroys something. Overwriting the declaration with the record throws away the signature that makes a false declaration an offence. Treating the declaration as a record makes the registry a place where accounts place themselves, which is the one thing it exists to prevent.
So they are reconciled and the disagreement is made visible:
blockchaind query validatorgov jurisdiction-reconciliation
Every approved validator, its declared country, its recorded country if it has
one, who recorded it, and a state: AGREE, DISAGREE, or UNRECORDED — plus
counts, so "three disagree" is visible without scanning. The zero value of that
enum is reserved as unspecified, like every enum on this chain, so an unfilled row
can never read as agreement.
UNRECORDED is the ordinary state for a validator nobody has placed, and it is a
real answer rather than a fault: a validator's operator account can be put in a
jurisdiction the same way any other account is, and until somebody does, no
authority's perimeter contains it.
To place one, the enrolment has a command of its own, and it reads the reconciliation first:
blockchaind query validatorgov jurisdiction-reconciliation -o json > reconciliation.json
./ceremony country validator --dossier country-SN.json \
--proposer <a foundation custodian> \
--candidate <the validator's operator account> \
--reconciliation reconciliation.json
A foundation proposal, for the same reason the first institutions need one: a
validator's operator account is nobody's payment customer, so no approved
participant acts for it, and MsgSetJurisdiction accepts only that participant, a
foundation administrator or governance.
It refuses to place a validator in a country it did not declare, and that is
why it wants the reconciliation rather than just an address. Placing one against a
different declaration would manufacture the disagreement that query exists to
reveal, with the foundation's signature on it. If the two really do differ, that
is a finding for a person: either the validator declared a country it does not
answer to, or somebody is about to record the wrong one. The declaration is signed
by the operator, so if it is the declaration that is wrong, that is what has to be
corrected — through x/validatorgov, by whoever signed it.
It also refuses a candidate that is not an approved validator, because an
application is not an approval; and where the two already agree it says there is
nothing to do rather than composing a proposal that changes nothing. Where the
state is DISAGREE it will compose one — correcting a recorded country is exactly
a foundation administrator's job — but it labels the proposal a correction and
names on screen whose finding is being overwritten.
What it never does is copy one registry into the other. It writes the x/alias
record and leaves the declaration where it is. The two remain two, and the
reconciliation query remains the thing that shows them side by side.
When something goes wrong
A group was created with the wrong members. Do not grant anything to it.
confirm will refuse it, which is the tool doing its job. Create the group again
from the same group.json — the office's keys are untouched — and confirm the new
one. The abandoned group policy sits on the chain holding nothing; note it in the
record.
The enrolment proposal was accepted and did not execute. verify refuses and
names the missing grant. Look at the proposal rather than the transaction: it may
not have reached three votes, it may have expired, or one message in it may have
failed and taken the rest with it. Re-submitting the same proposal is ordinary and
safe — granting the same triple again rewrites the attribution and the height and
changes nothing else, deliberately, so that a proposal resubmitted after a timeout
does what an operator expects.
An office's keys are compromised. Revoke first, ask questions afterwards. The foundation may revoke what the foundation may grant, so this is one 3-of-5 vote rather than a governance cycle — that asymmetry is deliberate, because the reason to revoke in a hurry is that somebody is abusing an authority, and granting is the act that wants friction rather than removing.
blockchaind query alias role-grants <office policy address>
# then a foundation proposal carrying MsgRevokeRole for each grant, named exactly
Revoking a grant that was never made is an error rather than a quiet success, on purpose: "nothing to revoke" is how a proposal that named the wrong country passes while leaving the authority it meant to remove in place.
A super user leaves. The office's group administers itself, so a
MsgUpdateGroupMembers proposal to that office, voted by its own threshold. The
foundation is not involved and does not need to be. Read
replacing a custodian first — the ratchet
described there applies to any M-of-N: dropping three-of-three to three-of-two is
not "one short", it is a veto for everybody left. Departures and replacements are
one decision.
Do it as one message. Removing a member on the understanding that a replacement will be added next week takes the office below its minimum, and the office stops being able to act the moment that proposal executes.
When an office falls below its minimum
The office is still an office. Its grants are still in the store, its queries still answer, and every action it attempts is refused:
yml1dlszg2s… was granted its authority as 3-of-5 and is now 3-of-4. An office may
grow; it may not fall below the shape it was granted under. Restore it with a
MsgUpdateGroupMembers or a MsgUpdateGroupPolicyDecisionPolicy voted by the office
itself, or have the grant re-made: an office's M-of-N and the shape its authority
requires do not agree
Note the shapes in it: the threshold is untouched at three. One member left and was not replaced, and that alone is the refusal — which is the half of this rule that surprises people.
An office acts through its own group, so that text arrives where nobody looks
first. The transaction's code is 0. An x/group proposal that fails in
execution produces a successful transaction carrying
cosmos.group.v1.EventExec with result: PROPOSAL_EXECUTOR_RESULT_FAILURE and
the refusal in its logs attribute:
blockchaind query tx <exec hash> -o json # look at events, not at code
blockchaind query group proposal <id> -o json # a failed proposal is not pruned
That is error code 23, ErrOfficeShape, from x/alias. Note what it is not:
it is not ErrOutOfScope (code 19), which means this office never held that
authority here. The two send you to different places, which is why they are
different codes — code 19 is a question about the grant, code 23 is a question
about the group.
Diagnose it. Two queries, and the second is the one that matters:
blockchaind query alias role-grants <office policy address> -o json
# required_shape on each grant is what the office is held to
blockchaind query group group-policy-info <office policy address> -o json
blockchaind query group group-members <group id> -o json
# the threshold it has now, and how many members hold a weight
If required_shape is absent from a grant, that grant predates this rule and
constrains nothing — see below.
Fix it: the office restores itself. No foundation involvement, no governance, no re-grant. The office proposes to itself and votes with its own threshold:
# a member left and was not replaced — add the replacement
blockchaind tx group submit-proposal <proposal.json> # MsgUpdateGroupMembers
# or the threshold was lowered — put it back
blockchaind tx group submit-proposal <proposal.json> # MsgUpdateGroupPolicyDecisionPolicy
The authority comes back on the next block, with nothing else to do. That is the whole point of recording the requirement rather than revoking on discovery: the chain refuses while the office is short and stops refusing when it is not, and nobody has to be watching either way.
The one case the office cannot vote its way out of is the one where it has fallen
so far that it cannot reach its own threshold — a three-of-five that lost three
keys cannot pass anything, including its own repair. The refusal says so
(no set of members can act at all), and the way out is the foundation revoking
the grants and the country running the office's ceremony again.
Relax the requirement instead. A country that has genuinely decided its enforcement authority is a two-of-three now, not a three-of-five, changes the grant rather than the group. It is a foundation act and it takes two messages, in one proposal:
MsgRevokeRole { holder, role, jurisdiction }
MsgGrantRole { holder, role, jurisdiction, required_shape: 2-of-3 }
Two rather than one, because a re-grant may raise a recorded requirement and
may never lower or drop one. The reason is the resubmission: the obvious way to
re-make a grant is to compose it from the proposal summary, the summary does not
list required_shape, and a single MsgGrantRole with the field omitted would
remove the pin while claiming to change nothing. x/group executes a proposal's
messages together or not at all, so the revoke-and-grant pair is atomic and the
office is never briefly unpinned. Attempting it as one message is refused:
yml1dlszg2s… already holds ROLE_PAYMENTS_AUTHORITY in GH with a required shape of
3-of-5, and this grant records none. Omitting required_shape on a re-grant would
remove the pin; to relax it deliberately, revoke the grant and make a new one in
the same proposal
And a grant requiring more than the office is refused before it is written, which is the other half of the same rule:
a grant requiring 3-of-5 cannot be made to yml1c799jdd…, which is 1-of-1. Either
the office is not the one this grant was meant for, or it has to be brought up to
3-of-5 before it can hold the authority
Grants made before this existed. A grant with no required_shape is treated
as recording no requirement, and its holder can shrink to a single key without
losing anything. That is deliberate — the alternative is a chain upgrade that
disables every existing authority at once — and it is a real gap until each one is
re-granted. To close it for an office, one foundation proposal per grant carrying
required_shape; adding a requirement to a grant that had none is an ordinary
re-grant and needs no revoke first. blockchaind query alias role-holders <CC>
lists who to do it for.
x/constitution's ante gate still protects the foundation's group shape by a
different mechanism: it refuses the transaction that would change it, rather than
refusing the actions afterwards. The difference is deliberate. There is one
foundation, its shape is in the constitution, and a gate can read it on every
transaction; there are as many offices as there are countries times offices, their
shapes are on their grants, and an ante gate would not see an office acting through
an interchain account or x/authz at all.
Full reference: x/alias for the grants, the jurisdictions and every error code; x/validatorgov for the reconciliation query; x/constitution for what governance can and cannot change. All generated from the source.