Enclave is now The Interfold.
Run a Ciphernode
Registration & Bonding

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 setBondOwner one 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)CallerCLI command
setBondOwner(bondOwner)Operator keyciphernode set-bond-owner
proposeBondOwner(operator, newOwner)Current bond ownerciphernode propose-bond-owner
acceptBondOwner(operator)Proposed bond ownerciphernode accept-bond-owner
bondCiphernodeFor(operator, amount)Bond ownerciphernode bond ... bond
registerOperatorFor(operator)Bond ownerciphernode register
addTicketBalanceFor(operator, amount)Bond ownerciphernode tickets ... buy
removeTicketBalanceFor(operator, amount)Bond ownerciphernode tickets ... burn
unbondCiphernodeFor(operator, amount)Bond ownerciphernode bond ... unbond
deregisterOperatorFor(operator)Bond owner or operator keyciphernode deregister
claimExitsFor(operator, maxTicket, 0)Anyoneciphernode bond ... claim --max-bond 0
claimExitsFor(operator, maxTicket, maxBond)Bond ownerciphernode bond ... claim
refreshOperatorStatus(operator)Anyoneciphernode 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 requiredCiphernodeBond FOLD, the ticket collateral for at least minTicketBalance tickets, 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 set and interfold wallet set with --config for that file. The key store is created next to that file, apart from the node keys. This page calls the file owner.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

  1. AuthorizeThe operator key sets the bond owner.
  2. BondThe bond owner bonds FOLD.
  3. RegisterThe bond owner registers the operator.
  4. TicketsThe bond owner deposits ticket collateral.
  5. 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 status

A 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: none

The 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_OWNER

The 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 0xOPERATOR

The 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 0xOPERATOR

An 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: none

If 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.

CommandSignerWhat it does
ciphernode set-bond-owner --owner <address>Operator keyAuthorizes the first bond owner.
ciphernode propose-bond-owner --operator <a> --new-owner <b>Current bond ownerProposes a new bond owner.
ciphernode accept-bond-owner --operator <address>Proposed bond ownerAccepts the position.
ciphernode bond [--operator <a>] bond --amount <FOLD>Bond ownerApproves FOLD if needed, then bonds it.
ciphernode bond [--operator <a>] unbond --amount <FOLD>Bond ownerQueues FOLD for exit.
ciphernode bond [--operator <a>] claim [--max-ticket <n>] [--max-bond <n>]Bond owner, or anyone with --max-bond 0Claims matured exits. Without limits, it claims all.
ciphernode tickets [--operator <a>] buy --amount <collateral>Bond ownerApproves the collateral if needed, then deposits it.
ciphernode tickets [--operator <a>] burn --amount <tFOLD>Bond ownerBurns tFOLD and queues the collateral for exit.
ciphernode register [--operator <a>]Bond ownerRegisters the operator.
ciphernode activate [--operator <a>]Any walletRefreshes the status of a registered operator.
ciphernode deactivate [--operator <a>] [--tickets <n>] [--bond <n>]Bond ownerBurns tickets and unbonds FOLD without a deregistration.
ciphernode deregister [--operator <a>]Bond owner or operator keyDeregisters the operator and queues all collateral.
ciphernode status [--operator <a>]AnyPrints 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 32000
⚠️

deactivate, 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 0xOPERATOR

The 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 least minTicketBalance.
  • 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 InsufficientBondOwners when 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.

ParameterMainnet valueMeaning
requiredCiphernodeBond32,000 FOLDFOLD needed to register.
ciphernodeBondActiveBps8,000 (80%)Share of the required bond needed to stay active.
ticketPrice1,000 sUSDS sharesCollateral for one ticket.
minTicketBalance1 ticketTickets needed to be active.
exitDelay2,592,000 s (30 days)Wait before a queued exit can be claimed.
sortitionSubmissionWindow600 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 minTicketBalance tickets, so that a ticket slash does not make the operator inactive.
  • Watch for OperatorActivationChanged events for your operator. The registry emits one each time the active status changes.
  • Watch for SlashProposed events for your operator, and for isActive changes to false.

Troubleshooting

ProblemCauseAction
NotBondOwner on a position callThe signer is not bondOwnerOf(operator), or no owner is set.Run set-bond-owner first, or use the bond owner configuration.
NotCiphernodeBonded on registrationThe bond is below the full requiredCiphernodeBond.Bond more FOLD.
NotRegistered when you buy ticketsThe operator is not registered.Register first.
AlreadyRegistered or CiphernodeBannedThe operator is registered already, or banned.Check isRegistered(operator) and the slashing status.
ExitInProgressA deregistration is still in its exit delay.Wait until exitDelay ends.
Active: false with bond and ticketsThe 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 selectedThe node missed events, or it registered after the E3 request.Test the RPC endpoint. Eligibility uses the state before each request.

Next steps