Skip to main content

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.

This is step 5, and it comes last

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.

No evmr key means no relaying

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

ErrorMeaning
NotValidatoryour account is not in the approved validator set yet
BadEvmSignaturethe signature does not prove you hold that address
InvalidEvmAddressthe zero address or the precompile range (0x0..=0xffff)
AlreadyRegisteredthat EVM address already belongs to another account
AccountAlreadyRegisteredyour account already has an address bound

It writes two on-chain indexes:

  • RelayerRegistry[H160] → AccountId — attributes fees from executed private operations
  • RelayerByAccount[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 deregisterValidatorclears 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:

Private ZK note

claim_shielded_fees — call index 16

  • Requires a value_proof ZK proof — use generateFeeClaimProof from 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

ExtrinsicOriginPurpose
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
Unregistering does not stop all fees

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 advisory

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

EventMeaning
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:

EventMeaning
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

ErrorCause
NotRegisteredunregister_relayer from an account with no registration
AlreadyRegisteredThat H160 already belongs to another account
AccountAlreadyRegisteredThis account already has an EVM address — unregister first. Hit this when rotating keys
InsufficientPendingFeesClaiming more than the pending balance
TooManySelectorsMore than 16 selectors passed to set_allowed_selectors

Next Steps