Summary
aztec codegen already emits a selector for every method:
// generated TokenContract.d.ts
methods: {
transfer: ((to: AztecAddressLike, amount: (bigint | number), _nonce: FieldLike)
=> ContractFunctionInteraction) & Pick<ContractMethod, 'selector'>;
...
}
but methods is an instance member. The generated statics are only at(address, wallet),
deploy(...), artifact, artifactForPublic and storage — so reaching .selector requires
Contract.at(address, wallet), i.e. a wallet and a registered contract instance.
Anything assembling an ExecutionPayload outside a wallet-bound instance therefore cannot use the
selectors that were generated for it, and falls back to restating the ABI by hand:
FunctionCall.from({
name: "transfer",
to: this.#target, // an AztecAddress, not a Contract
selector: await FunctionSelector.fromSignature("transfer((Field),u128,Field)"),
type: FunctionType.PRIVATE,
hideMsgSender: false,
isStatic: false,
args: [recipient.toField(), new Fr(amount), this.#authwitNonce],
returnTypes: [],
});
Why this is a problem
The signature string duplicates the ABI, including the encoding rules (AztecAddress → (Field),
u128, …). Change a parameter type or add an argument and the string still compiles, still yields
a selector, and fails at runtime inside a private circuit with nothing pointing at the drift —
and regenerating the contract wrapper does not flag it, because nothing links the two.
The information needed to prevent this is already generated. It just isn't reachable without a
wallet.
Why the existing paths don't apply
contract.methods.foo.selector — needs an instance, so needs a wallet.
ContractFunctionInteraction.getFunctionCall() returns exactly the right object, but likewise
needs Contract.at(address, wallet).
FunctionSelector.fromNameAndParameters(name, parameters) works from the artifact and needs no
wallet, but the caller has to locate the FunctionAbi itself, which is the boilerplate the
codegen exists to remove.
A custom FeePaymentMethod has none of what these need: getExecutionPayload() runs while the
payload is being assembled and typically holds only the callee's AztecAddress.
Proposal
Expose the already-generated selectors statically, without a wallet:
TokenContract.selectors.transfer // FunctionSelector, no instance required
The fuller version of this — a static builder that returns a complete FunctionCall, removing the
surrounding boilerplate and the hand-encoded args too — is #25298. This issue is the smaller
piece: it removes the hand-written signature strings on their own, and is useful independently.
Who benefits
Anyone assembling an ExecutionPayload outside a wallet-bound contract instance: custom
FeePaymentMethod implementations (the sponsored-FPC pattern and anything like it), account
entrypoints, and batching/multicall helpers.
In one such implementation we carry three hand-written signature strings, and they are the only
place in that codebase where an ABI is restated by hand.
Environment
aztec 5.0.1.
Summary
aztec codegenalready emits a selector for every method:but
methodsis an instance member. The generated statics are onlyat(address, wallet),deploy(...),artifact,artifactForPublicandstorage— so reaching.selectorrequiresContract.at(address, wallet), i.e. a wallet and a registered contract instance.Anything assembling an
ExecutionPayloadoutside a wallet-bound instance therefore cannot use theselectors that were generated for it, and falls back to restating the ABI by hand:
Why this is a problem
The signature string duplicates the ABI, including the encoding rules (
AztecAddress→(Field),u128, …). Change a parameter type or add an argument and the string still compiles, still yieldsa selector, and fails at runtime inside a private circuit with nothing pointing at the drift —
and regenerating the contract wrapper does not flag it, because nothing links the two.
The information needed to prevent this is already generated. It just isn't reachable without a
wallet.
Why the existing paths don't apply
contract.methods.foo.selector— needs an instance, so needs a wallet.ContractFunctionInteraction.getFunctionCall()returns exactly the right object, but likewiseneeds
Contract.at(address, wallet).FunctionSelector.fromNameAndParameters(name, parameters)works from the artifact and needs nowallet, but the caller has to locate the
FunctionAbiitself, which is the boilerplate thecodegen exists to remove.
A custom
FeePaymentMethodhas none of what these need:getExecutionPayload()runs while thepayload is being assembled and typically holds only the callee's
AztecAddress.Proposal
Expose the already-generated selectors statically, without a wallet:
The fuller version of this — a static builder that returns a complete
FunctionCall, removing thesurrounding boilerplate and the hand-encoded
argstoo — is #25298. This issue is the smallerpiece: it removes the hand-written signature strings on their own, and is useful independently.
Who benefits
Anyone assembling an
ExecutionPayloadoutside a wallet-bound contract instance: customFeePaymentMethodimplementations (the sponsored-FPC pattern and anything like it), accountentrypoints, and batching/multicall helpers.
In one such implementation we carry three hand-written signature strings, and they are the only
place in that codebase where an ABI is restated by hand.
Environment
aztec 5.0.1.