Registration & Bonding
This page shows the on-chain steps that make a ciphernode an active operator. It is for operators
who installed the node as shown on Run a Ciphernode. You authorize a bond owner,
bond FOLD, register the operator, and buy tickets. At the end, interfold ciphernode status shows
Active: true.
The dashboard does the same steps in a browser at Run a ciphernode (opens in a new tab). It shows the live bond size, ticket price, and operator status, and it sends each transaction from the connected wallet. After setup, it also refreshes the status, burns tickets, unbonds, deregisters, claims exits, and transfers the bond owner. The CLI commands on this page are the reference path for automated and headless setups.
Operator key and bond owner
An operator position uses two roles. They can be the same wallet, but a separate cold wallet or Safe as the bond owner is recommended. Then the node never holds the key that controls the collateral.
- The operator key is the key that the node loads. It is the identity in the registry, holds the
tFOLD ticket balance, submits sortition tickets, and signs DKG proofs. Bans and slashes apply to
it. It calls
setBondOwnerone time. - The bond owner is the wallet or Safe that holds FOLD and the ticket collateral. It sends every
collateral and registration call (the
...For(operator)functions). Exit payouts go to the bond owner. The registry also sends rewards to the bond owner, or to the operator when no owner is set.
One bond owner can fund several operator keys. totalBonded(owner) returns the active and pending
FOLD of that owner across all its operators. A committee takes at most one operator from each bond
owner (see Committee limits).
Function (BondingRegistry) | Caller | CLI command |
|---|---|---|
setBondOwner(bondOwner) | Operator key | ciphernode set-bond-owner |
proposeBondOwner(operator, newOwner) | Current bond owner | ciphernode propose-bond-owner |
acceptBondOwner(operator) | Proposed bond owner | ciphernode accept-bond-owner |
bondCiphernodeFor(operator, amount) | Bond owner | ciphernode bond ... bond |
registerOperatorFor(operator) | Bond owner | ciphernode register |
addTicketBalanceFor(operator, amount) | Bond owner | ciphernode tickets ... buy |
removeTicketBalanceFor(operator, amount) | Bond owner | ciphernode tickets ... burn |
unbondCiphernodeFor(operator, amount) | Bond owner | ciphernode bond ... unbond |
deregisterOperatorFor(operator) | Bond owner or operator key | ciphernode deregister |
claimExitsFor(operator, maxTicket, 0) | Anyone | ciphernode bond ... claim --max-bond 0 |
claimExitsFor(operator, maxTicket, maxBond) | Bond owner | ciphernode bond ... claim |
refreshOperatorStatus(operator) | Anyone | ciphernode activate |
A call from the wrong wallet reverts with NotBondOwner(caller, operator).
Before you start
Make sure that you have these items:
- A node configuration with the operator key stored in it. See Run a Ciphernode. The node must run before the operator can become active, because the node acknowledges its release on-chain when it starts. The operator key needs ETH for that transaction and for ticket submissions.
- A bond owner that holds at least
requiredCiphernodeBondFOLD, the ticket collateral for at leastminTicketBalancetickets, and ETH for gas. See Economic parameters. - For a bond owner that is an EOA: a second configuration file whose stored wallet is the bond owner
key. Copy the node configuration into another directory, then run
interfold password setandinterfold wallet setwith--configfor that file. The key store is created next to that file, apart from the node keys. This page calls the fileowner.yaml.
For a Safe bond owner, send the same BondingRegistry calls through the Safe. Before
bondCiphernodeFor, approve FOLD for the BondingRegistry. Before addTicketBalanceFor, approve
the ticket collateral for the InterfoldTicketToken contract, not for the BondingRegistry. Safe
amounts are in token base units.
On Sepolia, the fee token and the ticket collateral are MockUSDC, a test USDC that anyone can
mint. The faucet sends tokens to the wallet of the configuration, so use the bond-owner
configuration: interfold --config /path/to/owner.yaml faucet --chain sepolia. One call gives
1000 USDC and 1000 FOLD, which is enough for the 100 FOLD Sepolia bond. You can also call
faucet() on the Faucet contract from the bond owner, or call mint on MockUSDC. E3 requests
on Sepolia stay paused until operators that run the 0.19.0 release register.
Read the token addresses
The CLI reads the token addresses from the BondingRegistry, so you do not need them for the CLI
steps. A Safe or a wallet needs them for the approvals. Read them with Foundry cast. Take the
BondingRegistry address from Contract addresses.
BONDING=<BondingRegistry address>
RPC=https://your-endpoint.example
# FOLD, the bond token
cast call "$BONDING" "getCiphernodeBondToken()(address)" --rpc-url "$RPC"
# InterfoldTicketToken (tFOLD), the spender for the collateral approval
TICKET=$(cast call "$BONDING" "getTicketToken()(address)" --rpc-url "$RPC")
echo "$TICKET"
# The ticket collateral token that tFOLD wraps (sUSDS on mainnet)
cast call "$TICKET" "underlying()(address)" --rpc-url "$RPC"Register the operator
- AuthorizeThe operator key sets the bond owner.
- BondThe bond owner bonds FOLD.
- RegisterThe bond owner registers the operator.
- TicketsThe bond owner deposits ticket collateral.
- ActiveThe registry marks the operator active.
The order is fixed. registerOperatorFor needs the full bond, so the bond comes first.
addTicketBalanceFor reverts with NotRegistered() before registration, so tickets come last.
Check the current status
Run this command with the node configuration:
interfold ciphernode statusA new operator shows this output:
Ciphernode status on mainnet:
Operator key: 0xOPERATOR
Bond owner: not configured
Registered: false
Active: false
Exit pending: false
Ticket balance: 0 (0 available)
Ciphernode bond: 0
Pending exits: tickets=0, bond=0
Requirements: minTickets=1, ticketPrice=1000 tFOLD, ciphernodeBond=32000 FOLD
Committees: noneThe Requirements line shows the live values of minTicketBalance, ticketPrice, and
requiredCiphernodeBond.
Authorize the bond owner
Run this command with the node configuration (the operator key):
interfold ciphernode set-bond-owner --owner 0xBOND_OWNERThe command prints Authorized bond owner 0x… for operator 0x… (tx: 0x…). The owner must not be the
zero address. It can be the operator address.
The bond owner receives every exit payout of this position. While the position is empty, the
operator key can call set-bond-owner again to correct the address. After the first bond, ticket
deposit, or registration, the call reverts with BondOwnerAlreadySet. Then only the current bond
owner can start a transfer (see Change the bond owner).
Bond FOLD
Run this command with the bond owner configuration:
interfold --config /path/to/owner.yaml ciphernode bond \
--operator 0xOPERATOR bond --amount 32000--amount is in whole FOLD, and decimals are allowed. When the FOLD allowance of the
BondingRegistry is too low, the CLI first sends an approval for this amount. The command prints
Bonded 32000 FOLD for operator 0x… (tx: 0x…).
This transaction moves FOLD from the bond owner to the BondingRegistry. The FOLD comes back only
through an unbond or a deregistration, then the exitDelay (30 days on mainnet), then a claim.
Bonded and queued FOLD can be slashed.
Register the operator
interfold --config /path/to/owner.yaml ciphernode register --operator 0xOPERATORThe command prints Registered operator 0x… on mainnet (tx: 0x…). The registry adds the operator to
CiphernodeRegistry and emits CiphernodeAdded.
Buy tickets
interfold --config /path/to/owner.yaml ciphernode tickets \
--operator 0xOPERATOR buy --amount 1000--amount is an amount of the collateral token, not a number of tickets. When the collateral
allowance of the InterfoldTicketToken is too low, the CLI first sends an approval. After the
deposit, the CLI reads availableTickets(operator) and prints the ticket count, for example
Ticket balance is now 1 tickets.
This transaction moves the collateral from the bond owner into the InterfoldTicketToken
contract. The registry mints non-transferable tFOLD to the operator key. The collateral comes back
only through the exit queue, after exitDelay.
Confirm the active status
interfold ciphernode status --operator 0xOPERATORAn active operator shows this output:
Ciphernode status on mainnet:
Operator key: 0xOPERATOR
Bond owner: 0xBOND_OWNER
Registered: true
Active: true
Exit pending: false
Ticket balance: 1000 (1 available)
Ciphernode bond: 32000
Pending exits: tickets=0, bond=0
Requirements: minTickets=1, ticketPrice=1000 tFOLD, ciphernodeBond=32000 FOLD
Committees: noneIf Active is false, see Troubleshooting.
The available tickets are floor(ticket balance / ticketPrice). With a ticket price of 1,000 sUSDS
shares, a deposit of 1,000 sUSDS shares gives one ticket, and 1,999 shares still give one ticket.
For a self-owned position, use one configuration for all steps. Set the owner to your own address,
and omit --operator: the CLI then uses the address of the configured signer.
CLI commands
All commands below accept --chain <name>. It selects the chain entry by its name. Without it,
the CLI uses the first entry in chains. For bond and tickets, put --operator and --chain
before the inner command (bond, unbond, claim, buy, or burn). The
CLI Reference lists every flag.
| Command | Signer | What it does |
|---|---|---|
ciphernode set-bond-owner --owner <address> | Operator key | Authorizes the first bond owner. |
ciphernode propose-bond-owner --operator <a> --new-owner <b> | Current bond owner | Proposes a new bond owner. |
ciphernode accept-bond-owner --operator <address> | Proposed bond owner | Accepts the position. |
ciphernode bond [--operator <a>] bond --amount <FOLD> | Bond owner | Approves FOLD if needed, then bonds it. |
ciphernode bond [--operator <a>] unbond --amount <FOLD> | Bond owner | Queues FOLD for exit. |
ciphernode bond [--operator <a>] claim [--max-ticket <n>] [--max-bond <n>] | Bond owner, or anyone with --max-bond 0 | Claims matured exits. Without limits, it claims all. |
ciphernode tickets [--operator <a>] buy --amount <collateral> | Bond owner | Approves the collateral if needed, then deposits it. |
ciphernode tickets [--operator <a>] burn --amount <tFOLD> | Bond owner | Burns tFOLD and queues the collateral for exit. |
ciphernode register [--operator <a>] | Bond owner | Registers the operator. |
ciphernode activate [--operator <a>] | Any wallet | Refreshes the status of a registered operator. |
ciphernode deactivate [--operator <a>] [--tickets <n>] [--bond <n>] | Bond owner | Burns tickets and unbonds FOLD without a deregistration. |
ciphernode deregister [--operator <a>] | Bond owner or operator key | Deregisters the operator and queues all collateral. |
ciphernode status [--operator <a>] | Any | Prints the on-chain status. |
Activate and deactivate
activate sends refreshOperatorStatus for a registered operator. The contract recomputes the
active status from the registration, the release acknowledgment, bans, the bond and the tickets. Use
it after a requirement changed or after the release was acknowledged from another process.
activate does not register: for an operator that is not registered it stops and points to
register. To register an operator again after a full exit, use register.
deactivate needs --tickets, --bond, or both. It calls removeTicketBalanceFor for the ticket
amount and unbondCiphernodeFor for the bond amount. --tickets is a tFOLD amount, not a ticket
count. The operator stays registered. For a full exit, use deregister, as shown on
Exits, Rewards & Slashing.
interfold --config /path/to/owner.yaml ciphernode deactivate \
--operator 0xOPERATOR --tickets 1000 --bond 32000deactivate, unbond, tickets burn, and deregister start the exit delay (30 days on
mainnet). The collateral cannot be claimed before the delay ends. It stays slashable while it is
queued. A position with an open slash proposal cannot unbond, burn, or deregister until the
proposal is resolved (OperatorUnderSlash).
Change the bond owner
The current bond owner proposes a new owner. The new owner then accepts:
interfold --config /path/to/current-owner.yaml ciphernode propose-bond-owner \
--operator 0xOPERATOR --new-owner 0xNEW_OWNER
interfold --config /path/to/new-owner.yaml ciphernode accept-bond-owner \
--operator 0xOPERATORThe acceptance moves the ownership of the active and pending FOLD of the position to the new owner
in one transaction. The FOLD stays in the BondingRegistry. The operator key does not take part.
The acceptance reverts with BondOwnerTransferViolatesLock when the move leaves the old owner with
less wallet-plus-bonded FOLD than its locked FOLD balance. A change of owner starts the
admission cooldown again.
Activation rules
The BondingRegistry marks an operator active only when all of these conditions are true:
- The operator is registered.
- No authorized slashing manager has banned the operator.
- The bond is at least
ceil(requiredCiphernodeBond × ciphernodeBondActiveBps / 10,000). floor(ticket balance / ticketPrice)is at leastminTicketBalance.- The acknowledged node release matches the required protocol version, and its node generation is at
least the required generation (
isNodeReleaseReady).
ciphernodeBondActiveBps is 8,000 (80%) after initialization. On mainnet, an operator therefore
stays active with at least 25,600 FOLD.
Registration and active status use different bond thresholds. registerOperatorFor needs the full
requiredCiphernodeBond. isCiphernodeBonded tests only the 80% floor. An operator between the two
reads as bonded, but registration reverts with NotCiphernodeBonded(). To test registration,
compare getCiphernodeBond(operator) with requiredCiphernodeBond().
Refresh a cached status
Governance can change ticketPrice, requiredCiphernodeBond, ciphernodeBondActiveBps, and
minTicketBalance. Each change makes every cached status read as inactive until a refresh. Anyone
can refresh a registered operator:
cast send "$BONDING" "refreshOperatorStatus(address)" 0xOPERATOR \
--rpc-url "$RPC" --interactive--interactive makes cast ask for the private key of any wallet that can pay the gas.
The call reverts with NotRegistered() for an operator that is not registered.
refreshOperatorStatuses(address[]) refreshes a batch, and it reverts when one operator in the list
is not registered. ciphernode activate refreshes one operator; the CLI has no command for a batch.
A node start also refreshes a registered operator that is not active, because its release
acknowledgment calls refreshOperatorStatus.
Committee limits for each bond owner
Sortition counts bond owners, not operator keys. For each new E3 request:
- A committee takes at most one operator from each bond owner. The owner is the owner at the
timestamp before the request (
bondOwnerAt(operator, requestBlock - 1)). When two operators of one owner submit tickets, the better score replaces the other. - The request reverts with
InsufficientBondOwnerswhen the counted active bond owners are fewer than the committee size N.
Extra operator keys under one bond owner therefore do not give more committee seats in one E3. Tickets & Sortition explains the scores.
Admission cooldown
The BondingRegistry owner can set an admission policy with
setAdmissionPolicy(cooldownEnabled, cooldownDuration, admissionsPaused). When the cooldown is on,
a new registration or a bond owner change must wait cooldownDuration seconds before the position
counts for new committees. Funding tickets later does not restart the wait. When admissions are
paused, positions that were not eligible before the pause cannot enter. An unset policy has no
cooldown.
Read the current policy:
cast call "$BONDING" \
"admissionPolicyAt(uint256)((bool,bool,uint48,uint48,bool,uint48))" "$(date +%s)" \
--rpc-url "$RPC"The first three values are cooldownEnabled, admissionsPaused, and cooldownDuration. On 25
September 2026, mainnet returned (true, false, 259200, 0, false, 0): a cooldown of 3 days.
Economic parameters
The values below are from the mainnet deployment configuration
(packages/interfold-contracts/deploy/protocol/mainnet-protocol.config.json). On 25 September 2026,
the on-chain values were the same. Governance can change them, so read them on-chain before you
deposit. Sepolia uses the same values, except for ticketPrice and requiredCiphernodeBond. The
Sepolia ticketPrice is 1 USDC (1,000,000 base units), and the Sepolia requiredCiphernodeBond is
100 FOLD. The Sepolia ticket collateral is MockUSDC.
| Parameter | Mainnet value | Meaning |
|---|---|---|
requiredCiphernodeBond | 32,000 FOLD | FOLD needed to register. |
ciphernodeBondActiveBps | 8,000 (80%) | Share of the required bond needed to stay active. |
ticketPrice | 1,000 sUSDS shares | Collateral for one ticket. |
minTicketBalance | 1 ticket | Tickets needed to be active. |
exitDelay | 2,592,000 s (30 days) | Wait before a queued exit can be claimed. |
sortitionSubmissionWindow | 600 s (on CiphernodeRegistry) | Time for nodes to submit tickets for one E3. |
The contract limits exitDelay to between 1 day and 90 days. Read the values with cast:
cast call "$BONDING" "requiredCiphernodeBond()(uint256)" --rpc-url "$RPC"
cast call "$BONDING" "ciphernodeBondActiveBps()(uint256)" --rpc-url "$RPC"
cast call "$BONDING" "ticketPrice()(uint256)" --rpc-url "$RPC"
cast call "$BONDING" "minTicketBalance()(uint256)" --rpc-url "$RPC"
cast call "$BONDING" "exitDelay()(uint64)" --rpc-url "$RPC"requiredCiphernodeBond is in token base units (18 decimals). ticketPrice is in base units of the
ticket collateral (18 decimals on mainnet, 6 decimals for USDC on Sepolia).
Good practice
- Keep the bond above the 80% floor with a margin, so that a slash does not make the operator inactive.
- Keep more than
minTicketBalancetickets, so that a ticket slash does not make the operator inactive. - Watch for
OperatorActivationChangedevents for your operator. The registry emits one each time the active status changes. - Watch for
SlashProposedevents for your operator, and forisActivechanges tofalse.
Troubleshooting
| Problem | Cause | Action |
|---|---|---|
NotBondOwner on a position call | The signer is not bondOwnerOf(operator), or no owner is set. | Run set-bond-owner first, or use the bond owner configuration. |
NotCiphernodeBonded on registration | The bond is below the full requiredCiphernodeBond. | Bond more FOLD. |
NotRegistered when you buy tickets | The operator is not registered. | Register first. |
AlreadyRegistered or CiphernodeBanned | The operator is registered already, or banned. | Check isRegistered(operator) and the slashing status. |
ExitInProgress | A deregistration is still in its exit delay. | Wait until exitDelay ends. |
Active: false with bond and tickets | The release is not acknowledged, the bond is below 80%, or the status is stale. | Start or upgrade the node, add collateral, or refresh the status. |
| The node is never selected | The node missed events, or it registered after the E3 request. | Test the RPC endpoint. Eligibility uses the state before each request. |