Relayers
Private operations in Orbinum — unshield and private_transfer — are submitted as unsigned Substrate extrinsics (ensure_none). There is no signing account, so there is no public address that can be correlated with the private operation.
The fee for including the transaction is not a standard gas payment. It is embedded directly inside the ZK proof as a public input, deducted from the user's note value. The block author — always an Aura validator — includes the unsigned extrinsic and receives the embedded fee, attributed automatically by pallet-shielded-pool.
This means every Aura validator is an implicit relayer. There is no separate relay process, no relay service URL, and no special node configuration required to handle private transactions.
What the relay model solves
unshield and private_transfer use ensure_none — there is no account to deduct gas from. The embedded fee in the ZK proof compensates the validator who authors the block.
If the user submitted from a public account, that account could be correlated with the private operation. Unsigned extrinsics avoid this entirely — the authorization is the ZK proof itself.
Validator registration
To receive fee attribution, a validator must register an EVM H160 address in pallet-relayer. This is done through the register_relayer(evm_address) extrinsic, which is restricted to active Aura validators by T::IsValidator::contains(&who).
The validator's consensus identity. Used by Aura for block authorship.
- Receives and accumulates fees in
PendingRelayerFees - Signs the
register_relayerextrinsic - Identified on-chain as the block author
An EVM address controlled by the validator, registered for fee claiming.
- Receives tokens via
claim_relay_fees_to_evm - Linked to the AccountId via
register_relayer - Not used for signing relay transactions
Both identities are linked on-chain when the validator calls register_relayer(evm_address). This writes two indexes:
RelayerRegistry[H160] → AccountId— used to attribute fees when a relayed call is executedRelayerByAccount[AccountId] → H160— used to transfer claimed fees to the EVM account
The register_relayer extrinsic is rejected with NotValidator if the caller is not an active Aura validator. There is no whitelist-based or open relay registry.
Where the fee comes from
The relay fee is not a standard gas payment. It is a value committed inside the ZK proof itself:
// Unshield circuit constraint:
note_value === amount + fee
// Transfer circuit constraint:
input_sum === output_sum + fee
The circuit enforces that fee is deducted from the user's note. Changing the fee value after proof generation causes verification to fail. The fee is a public signal — visible to anyone — but immutable once proved.
After on-chain verification, pallet-shielded-pool identifies the current block author's AccountId and increments PendingRelayerFees[AccountId][asset_id]. The fee remains physically inside the shielded pool until the validator claims it.
System component overview
Key components
| Component | Location | Role |
|---|---|---|
pallet-relayer | frame/relayer/ | Registry, fee accounting, RelayerInterface |
pallet-shielded-pool | frame/shielded-pool/ | Executes the private op, attributes fee to block author |
pallet-zk-verifier | frame/zk-verifier/ | Verifies the Groth16 proof on-chain |
| ShieldedPool precompile | precompiles/shielded-pool/ | EVM path: bridges user-signed EVM tx into Substrate |