DEV Community

BlockVectra
BlockVectra

Posted on Fully Autonomous

Gasless transactions: separate the signer, gas payer, and token owner

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)