Skip to content

feat(meta-tx): sponsored meta transactions for native Substrate accounts [ignore benchmarks] - #1505

Open
iamyxsh wants to merge 2 commits into
masterfrom
feat/meta-tx-sponsorship
Open

feat(meta-tx): sponsored meta transactions for native Substrate accounts [ignore benchmarks]#1505
iamyxsh wants to merge 2 commits into
masterfrom
feat/meta-tx-sponsorship

Conversation

@iamyxsh

@iamyxsh iamyxsh commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What this is

A POC of sponsored meta transactions for native Substrate accounts — a user signs an intent
off-chain, anyone submits it and pays the fee, and the call executes under the signer's origin.

This is the Substrate counterpart of the EVM call-permit path. Opening it for feedback on
direction before hardening.

Why only Substrate accounts

While scoping this I tested what already exists. The EVM side already does most of what was
asked for:

  • A signer with 0 HDX / 0 WETH / 0 DAI is already fully sponsored end to end — the paymaster
    pays everything (measured: ~0.09 HDX).
  • Batching already works under one signature — verified at 14 and at 50 legs.

So the remaining gap was native Substrate accounts only, and that is all this PR adds.

Why it is small

On the EVM side "the relayer pays" needed the whole EvmFeePayer override, because EVM gas is
charged to source. On the Substrate side the relayer signs and submits the extrinsic, so
ChargeTransactionPayment already charges them. Nothing to build.

That leaves three jobs: verify a signature, dispatch under the signer's origin, bump a nonce.

What is in the payload

blake2_256 over (tag, call, signer, nonce, deadline, genesis_hash, spec_version).

Genesis hash and spec version are the Substrate analogue of EIP-712's domain separator — without
them an intent replays across forks, and across runtime upgrades that change call encoding.

Behaviour worth reviewing

  • The nonce is consumed even when the inner call fails. A rejected intent must not stay
    replayable. The inner outcome is reported in the Dispatched event rather than as an
    extrinsic error. This mirrors the EVM permit path.
  • The runtime CallFilter applies — verified by an integration test that pauses
    Currencies::transfer and confirms a signed intent for it does not execute.
  • Batching falls out for freeutility.batch_all is just a valid inner call.

The relayer

scripts/meta-tx-relayer/ — an HTTP service that accepts intents, screens them against a
pallet allowlist, dry-runs, and submits. ERC-4337's bundler-plus-paymaster.

It is deliberately not in the node. The relayer holds a funded key and signs. RPC nodes are
public and replicated, so a spending key in one means every operator sponsors everyone. The
repo's own precedent agrees: liquidation-worker-support exposes an RPC that only serves data,
and the actor that signs lives outside. A node RPC remains possible if sponsorship ever needs
to be consensus-adjacent; it does not.

npm run demo generates a brand-new zero-balance account, signs an intent, and shows the
balance still zero afterwards.

Tests

22 total, all passing.

  • 16 unit (pallets/meta-tx/src/tests.rs): valid path and origin, wrong signer, call swapped
    after signing, deadline swapped after signing, replay, nonce ahead, expired, on-deadline
    boundary, nonce consumed on inner failure, batch_all all-or-nothing both ways, unsigned
    origin rejected, every payload field proven load-bearing, per-signer nonce independence.
  • 6 integration (integration-tests/src/meta_tx.rs), through the full fee pipeline so the
    sponsorship claim is real: zero-balance signer fully sponsored, batching, replay, expiry,
    call filter respected, meta nonce independent of the system nonce.

Known gaps — deliberate for a POC

  • Weights are a placeholder. weights.rs is hand-set and needs scripts/benchmarking.sh
    before this carries value on mainnet. The benchmark currently measures the reject path.
  • Wallet UX. There is no EIP-712 equivalent on Substrate, so a user signs an opaque 32-byte
    hash. Fine for a relayer-operated flow, not fine for arbitrary users. This may be the real
    blocker for production and is worth discussing.
  • No deferred execution ("sign now, fire later"). Separate feature with its own storage,
    expiry and ordering questions. Not built pending confirmation it is wanted.
  • No call scoping beyond the existing CallFilter. A signed call is a broad grant. A
    ProxyType-style filter is the obvious next step if that is a concern.
  • No revocation. Bumping one's own meta-nonce would do it; cheap to add now, painful later.
  • Relayer is minimal — in-memory rate limits, key from env, no auth. See its README.

Questions

  1. Is the Substrate gap the right target, given the EVM side already works?
  2. Deferred execution — wanted, or out of scope?
  3. Is opaque-hash signing acceptable, or does this need a readable-payload story first?

@github-actions

Copy link
Copy Markdown

Crate versions that have not been updated:

  • hydradx-runtime: v434.0.0

New crates:

  • pallet-meta-tx: v1.0.0

Crate versions that have been downgraded:

  • runtime-integration-tests: v1.105.0 -> v1.104.0
  • pallet-dca: v1.18.2 -> v1.18.1

Runtime version has not been increased.

@github-actions

Copy link
Copy Markdown

Quick benchmark at commit 1c09b0e has been executed successfully.
View results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant