Skip to main content

Relayers

Private operations — unshield and private_transfer — are submitted as unsigned Substrate extrinsics. There is no signing account, so there is no public address to correlate with the private operation.

The fee is not a gas payment. It is a value committed inside the ZK proof as a public input and deducted from the user's note, which is what lets someone with no public balance spend privately.


There is no relayer to be

Every active validator relays. Including a fee-bearing unsigned extrinsic is ordinary Aura block authorship — there is no relay service, no endpoint to run, and no extra process on the node.

What a validator does need is an EVM address registered on-chain, so the chain knows where to credit the fee. That is one signed extrinsic, done once — see Relay Setup and Rewards.

No registered address means no income

A validator without one still authors blocks and still relays. The fee simply goes to the block author by the fallback below — which, often enough, is somebody else.


Who gets credited

The chain takes the fee recipient from the dispatch origin, never from calldata. That is the only place it cannot be forged: a relayer field would be an unauthenticated claim, and anyone could take a propagated proof, resubmit it naming themselves, and collect a fee they never paid for.

Three shapes, all either authenticated or deliberately anonymous:

OriginCreditedWhy
Relayed(addr)that addressEVM precompile — addr signed the transaction
Signed(who)who's registered addressthe signature proves who submitted
Nonethe block authorunsigned; nobody is named

The third row is the common case, and the second has a wrinkle worth knowing:

An unregistered submitter is not an error

A signed submitter with no registered address resolves to "nobody" rather than failing, and the fee falls back to the block author. Relaying is deliberately not gated on registration — failing here would reject a user's transaction over someone else's misconfiguration.


Watching where fees go

Two events exist specifically so that attribution is auditable:

EventFires whenWhy you care
RelayFeeDiverted { requested, credited, asset_id, amount }the call named an address that resolves to no registered relayerThis is where your missing fees went. requested is the address that was named, credited is who actually received it
SelfRelayedFee { author, asset_id, amount }an authenticated relayer resolved and turned out to be the block authorLegitimate on its own — with N authors in rotation it happens about 1/N of the time

SelfRelayedFee is published because it is the only on-chain trace of the one attack origin-based attribution does not prevent: an author can ignore its own pool ordering and include a copy of another node's submission pointed at itself. A single event proves nothing. A rate sustained well above 1/N across many sessions does, and the response is off-chain — validatorSet.removeValidator.

The plain fallback — no relayer named, fee to the author — does not emit SelfRelayedFee. It happens on every unrelayed call and carries no claim about who relayed anything, so reporting it would bury the signal.


Where this lives

ComponentResponsibility
pallet-shielded-poolverifies the proof, moves the tokens, attributes the fee
pallet-relayerthe address registry and the pending-fee ledger
pallet-zk-verifierholds the verification keys the proof is checked against
ShieldedPoolPrecompilethe EVM entry point — see precompile reference

Next Steps