AI-generated engineering note from BlockVectra.
"Gasless" describes an experience, not a protocol. Before writing any test, name four roles. In a demo they are often one address; in production they usually are not.
- Signer: the key that authorizes an action off-chain.
- Gas payer: in the direct EOA-relaying model used for this discussion, the transaction-sending EOA that pays native gas. Other sponsorship architectures can separate these roles.
- Token owner: the account whose tokens are spent or whose allowance is authorized.
-
Contract caller: the address the target contract sees as
msg.sender.
Many bugs come from treating two of these as one. A contract that reads the caller as the owner works when the owner sends the transaction and breaks when a relayer does.
Two token authorization patterns are useful to distinguish. A signed approval, as in ERC-2612, only changes an allowance; a transfer must still follow. A signed transfer, as in ERC-3009, binds sender, recipient, amount, validity window and nonce into one authorization that a third party can submit. Both need a token or wrapper that implements them, since plain ERC-20 does not. The official ERC-3009 page currently marks it Draft.
Assert roles, not just outcomes
A passing "transfer succeeded" check hides which account paid for what. Assert each role's balance and allowance separately, before and after, and use distinct token-owner/signer, relayer, and recipient addresses so a mix-up cannot pass by accident.
A proposed test matrix
This is a suggested plan, not a record of executed results.
| Scenario | Setup | Assert |
|---|---|---|
| Zero-native user | Owner holds tokens and no native coin; relayer pays gas | Call succeeds; owner token balance drops by the exact amount |
| Zero-token sponsor | Relayer holds gas funds but zero tokens | Relayer token balance is zero before and after |
| Actor binding | Token-owner/signer, relayer, and recipient are distinct | A signed transfer debits the authorized owner and credits the authorized recipient; a permit updates the authorized owner-to-spender allowance without transferring tokens |
| Authorization lifecycle | Valid, expired, then replayed signature | Valid works once; the others revert and leave state unchanged |
| Vault accounting | Tokens credited directly to a vault | Deposit counters match what the design says they should mean |
One public case
The 16ane repository test, pinned to one commit, has a relayer holding zero mock tANE submit a signed ERC-3009 transfer. The owner's balance falls from 10000 to 8400, the vault holds 1600, and owner principal stays 0, an accepted accounting gap because the deposit method never ran. A second test has the owner sign a permit while the relayer calls the vault's fundWithPermit wrapper. The second test expects an insufficient-allowance revert, because that wrapper treats the caller as the permit owner. That limits the wrapper, not ERC-2612 relaying in general. The tests do not assert that the owner holds zero native coin.
Verifying for real
Read balanceOf and allowance for the caller and the owner separately, and compare receipts and state deltas, one branch at a time. A simulation or a log status is not end-to-end proof. Also scope "atomic" carefully: a reverted transaction can still cost the sponsor gas, so atomicity here means application state is unchanged, not that fees come back.
If you are wiring agents to onchain actions, the BlockVectra agent integration documentation covers the integration side.
Top comments (0)