Relay Setup and Rewards
Relay fees are the only income an Orbinum validator earns. Ordinary transaction fees are burned by the runtime, so a validator with no relay address authors blocks and collects nothing.
Every active validator is an implicit relayer: fee-bearing unsigned extrinsics are
included as part of normal Aura authorship, and no separate relay service runs.
What you do need is an ECDSA key of your own, inserted under the evmr key
type. That key signs relay transactions, and its address is what you register
on-chain — self-service, proving possession with a signature. Neither the address
nor the key derives from your Aura mnemonic, so your relay identity is independent
of your consensus key.
registerRelayer is rejected with NotValidator unless your account is already
in the active set. Finish Apply to Join the Set first.
Insert your relay key
Generate an ECDSA key however you prefer — a wallet, subkey generate --scheme ecdsa, or an existing Ethereum key — and insert it into the node keystore:
docker exec orbinum-validator curl -s -H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0","id":1,"method":"author_insertKey",
"params":["evmr","<your ecdsa mnemonic or seed>","<0x-prefixed public key>"]
}' http://localhost:9944
The call runs inside the container: port 9944 is never published to the host,
so a curl from the host cannot reach it.
The node rejects well-known development secrets (//Alice and friends) — they are
public, so anyone could sign as you.
evmr key means no relayingThe node still authors blocks without one — consensus is unaffected — but its relay does not sign, so it earns no relay fees. This is logged at startup.
Get your address and proof
Ask the node for both in one call:
docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"relayer_getRelayInfo","params":[]}' \
http://localhost:9944
It returns your EVM address and a secp256k1 signature over the binding digest for
your account on this chain. Without an evmr key it explains what is missing
instead of returning an address.
Register it yourself
Submit relayer.registerRelayer(evmAddress, signature) signed by your validator
account — not via sudo. Pass the values from relayer_getRelayInfo verbatim.
The call is rejected with:
| Error | Meaning |
|---|---|
NotValidator | your account is not in the approved validator set yet |
BadEvmSignature | the signature does not prove you hold that address |
InvalidEvmAddress | the zero address or the precompile range (0x0..=0xffff) |
AlreadyRegistered | that EVM address already belongs to another account |
AccountAlreadyRegistered | your account already has an address bound |
It writes two on-chain indexes:
RelayerRegistry[H160] → AccountId— attributes fees from executed private operationsRelayerByAccount[AccountId] → H160— reverse lookup; enforces one address per account
Why the signature is required
A relay address is public — it is the caller of every relay transaction — and the
registry is first-come-first-served with no override. Gating on validator-set
membership alone would let any approved validator claim a peer's address, divert its
relay fees, and lock the rightful owner out permanently via AlreadyRegistered.
The signature covers a digest binding a domain separator, the chain's genesis hash,
your AccountId and the EVM address. The genesis hash prevents replay against another
chain; the AccountId prevents anyone reusing a signature observed on-chain. It is
wrapped in EIP-191 (personal_sign), so any standard wallet can produce one — you
are not tied to the node's RPC helper.
Changing or losing your address
To move to a different address: unregister_relayer(), then register_relayer with
the new address and a fresh signature.
Leaving the validator set — by sudo removeValidator or your own
deregisterValidator — clears the binding automatically. A binding must not
outlive the membership that authorised it. Re-entering means registering again.
Fees you already earned survive. PendingRelayerFees is untouched by removal and
stays claimable.
Claiming accumulated fees
Fees accumulate in PendingRelayerFees[AccountId][asset_id] as blocks are authored. Claiming converts them into a private note:
claim_shielded_fees — call index 16
- Requires a
value_proofZK proof — usegenerateFeeClaimProoffrom the SDK - Funds are fully private and spendable as any note
- The claim itself is a signed call, but spending the resulting note carries no link back to it
- Partial claims allowed; the remainder stays pending
See Fee Lifecycle for the full walkthrough.
Unregistering
To remove the EVM relay binding, the validator (not governance) calls unregister_relayer() — this is a self-service signed call. It removes both on-chain indexes. Any fees already accumulated in PendingRelayerFees remain claimable after unregistration.
pallet-relayer: relevant extrinsics
| Extrinsic | Origin | Purpose |
|---|---|---|
register_relayer(evm_address, signature) | Validator (signed) | Binds H160 → AccountId in both registry maps. Requires approved-set membership and a signature proving you hold the key |
unregister_relayer() | Validator (signed) | Removes registration; stops relayer-resolved attribution |
set_min_relay_fee(fee) | ManageOrigin (sudo / gov) | Adjusts the minimum fee required in ZK proofs. Capped by MaxMinRelayFee |
set_allowed_selectors(selectors) | ManageOrigin (sudo / gov) | Publishes the selector list consumed by off-chain relay services |
A validator that unregisters stops receiving fees resolved through the relayer registry, but still receives them as block author for any operation where no relayer resolves. To stop entirely, the validator must also stop authoring blocks.
set_allowed_selectors is advisoryThe selector list is published on-chain for off-chain relay services to read via relay_config().
It is not enforced by the runtime — it does not restrict which calls can be relayed.
On-chain events reference
Key events emitted by pallet-relayer:
| Event | Meaning |
|---|---|
RelayFeeAccumulated { relayer, asset_id, amount } | Fired on every relayed operation — the event to watch for fee telemetry |
RelayFeesConsumed { relayer, asset_id, amount } | Fees deducted from pending balance (claim step) |
RelayerRegistered { evm_address, account } | On-chain registration completed (self-service, by the validator) |
RelayerUnregistered { evm_address, account } | Validator removed their relay binding |
MinRelayFeeUpdated { new_fee } | Governance changed the minimum fee — wallets should adjust |
AllowedSelectorsUpdated { count } | Selector list republished |
Key events emitted by pallet-shielded-pool:
| Event | Meaning |
|---|---|
ValidatorFeesClaimed { validator, asset_id, amount, commitment, leaf_index } | Fees received as a private note |
Checking your status and balance
Three RPC methods answer the operational questions:
relayer_isRelayer(evm_address) → bool
relayer_pendingFees(account, asset_id) → u128 ← your unclaimed balance
relayer_registeredEvmAddress(account) → Option<H160>
relayer_pendingFees is how you check what you have earned before deciding to claim. The SDK wraps
these as client.relayerStatus.
Errors
| Error | Cause |
|---|---|
NotRegistered | unregister_relayer from an account with no registration |
AlreadyRegistered | That H160 already belongs to another account |
AccountAlreadyRegistered | This account already has an EVM address — unregister first. Hit this when rotating keys |
InsufficientPendingFees | Claiming more than the pending balance |
TooManySelectors | More than 16 selectors passed to set_allowed_selectors |
Next Steps
- Validator Operations — monitoring, backups and troubleshooting
- How It Works — end-to-end relay flow
- Fee Lifecycle — how fees accumulate and are claimed