Skip to content

[FEATURE] Generate a static, wallet-free per-method FunctionCall builder on contract wrappers #25298

Description

@shiqicao

Summary

Building a bare FunctionCall means filling in eight fields by hand, seven of which the contract
artifact already describes:

FunctionCall.from({
  name: "transfer",                                             // artifact
  to: someAddress,                                              // caller
  selector: await FunctionSelector.fromSignature("transfer((Field),u128,Field)"), // artifact
  type: FunctionType.PRIVATE,                                   // artifact
  hideMsgSender: false,                                         // call-site option
  isStatic: false,                                              // artifact
  args: [recipient.toField(), new Fr(amount), nonce],           // caller, hand-encoded
  returnTypes: [],                                              // artifact
});

aztec codegen already knows every one of those: it generates a typed wrapper per method, complete
with a selector and the argument encoding. But the only thing it can produce is a
ContractFunctionInteraction, and only from a wallet-bound instance
(Contract.at(address, wallet).methods.transfer(...)).

Request

Generate a static, wallet-free builder that returns a FunctionCall directly:

TokenContract.callAt(address).transfer(recipient, amount, nonce);   // -> FunctionCall

Codegen supplies name, selector, type, isStatic and returnTypes from the artifact, and
encodes the arguments from their typed values — the same encoding the existing wrapper already
performs. The caller supplies only the target address and the arguments, with hideMsgSender
available as an option where it applies.

Why a static builder specifically

ContractFunctionInteraction.getFunctionCall() already returns the right object, so the gap is not
the conversion — it is that reaching it requires a Wallet and a registered contract instance.
Code that assembles an ExecutionPayload frequently has neither: it runs while the payload is
being built, and holds only the callee's AztecAddress.

Today that code hand-writes a signature string and calls FunctionSelector.fromSignature, which
duplicates the ABI — including the encoding rules (AztecAddress(Field), u128, …). A
parameter change keeps compiling, produces a different selector, and fails at runtime inside a
private circuit with nothing connecting the failure back to the signature that drifted.

Hand-encoding args has the same weakness: [recipient.toField(), new Fr(amount), nonce] is a
manual re-implementation of the typed wrapper's encoding, and nothing checks it against the ABI.

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.

Relationship to #25297

#25297 asks for the smaller piece — exposing the already-generated selectors statically, e.g.
TokenContract.selectors.transfer. That removes the hand-written signature strings on their own.
This issue asks for the full builder, which additionally removes the FunctionCall.from({...})
boilerplate and the hand-encoded args. Either is useful; this one subsumes the other.

Environment

aztec 5.0.1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions