Permissioned key composer deep dive
Technical detail of the AllOf/AnyOf/MessageFilter/ClobPairIdFilter/SubaccountFilter composer used to register Botely trading keys on-chain.
The composer tree
dYdX v4 uses a generic 'authenticator' system from the accountplus module. An authenticator is a tree of composers (logical AND/OR) over leaf predicates (signature checks, message-type filters, subaccount filters, market filters). When a transaction is signed by an authenticator-holding key, the chain evaluates the tree against the transaction's contents โ and accepts only if the tree evaluates true.
Our registered tree is exactly: AllOf [ SignatureVerification(trading-key-pubkey), AnyOf [MessageFilter('/dydxprotocol.clob.MsgPlaceOrder'), MessageFilter('/dydxprotocol.clob.MsgCancelOrder'), MessageFilter('/dydxprotocol.clob.MsgBatchCancel')], SubaccountFilter('0'), AnyOf [ClobPairIdFilter(ETH-USD-id), ClobPairIdFilter(SOL-USD-id), ClobPairIdFilter(BNB-USD-id)] ].
Encoding format on the wire
The composer is serialized as a base64-encoded JSON tree. The outermost AllOf is a 'composer' node โ its `config` field is the base64 of a JSON array of child nodes. Each leaf filter has a `config` field that is base64 of either: a public key (for SignatureVerification), a message type URL (MessageFilter), a subaccount number as ASCII (SubaccountFilter), or a clob_pair_id as ASCII (ClobPairIdFilter).
Mismatches in encoding fail safe: the chain rejects an authenticator it can't decode. We verified the encoding byte-for-byte against the dYdX-published CLI tool โ same bytes, same on-chain effect.
What each filter does
SignatureVerification: checks that the trading-key's public part matches what was signed. This is what makes the key delegated โ the chain matches the signing key against the authenticator's whitelist of allowed keys.
MessageFilter: checks that the transaction's message types are within the AnyOf list. Anything else (transfer, withdraw, governance, etc.) is rejected.
SubaccountFilter: checks the subaccount number in the message body equals the filter's value (here '0').
ClobPairIdFilter: for orders, checks the clob_pair_id in the message body equals the filter's value (here one of ETH/SOL/BNB).
Why AllOf at the top
Every authenticator condition must hold. If we had used OR, a transaction passing any one filter would succeed โ defeating the purpose of restriction. With AllOf, every filter has veto power.
The AnyOf groups inside are for the disjunctive 'one of these is fine' parts (any of the 3 allowed message types, any of the 3 allowed markets). The combined effect: only 9 (3 ร 3) combinations are permitted: place/cancel/batch-cancel ร ETH/SOL/BNB, all on subaccount 0.