Proposal: x/wauth module

Native Authorization-Based Transfer Module for the Cosmos SDK

Summary

This topic proposes x/wauth (WAuth => “with authorization”), a new Cosmos SDK module that introduces off-chain-signed, on-chain-settled token transfers. The design is semantically equivalent to Ethereum’s EIP-3009 (transferWithAuthorization) but implemented as a first-class Cosmos SDK module with no EVM dependency. A working reference implementation targeting cosmos/evm is available and production-ready.


Motivation

The Cosmos SDK’s transfer model requires the token sender to submit and pay for every transaction. This is the right default, but it makes an entire class of application patterns difficult or impossible to implement cleanly:

  • Meta-transactions / gas abstraction: end users should not need to hold the native fee token to interact with an application.

  • Delegated and pull payments: a recipient or relayer should be able to settle a pre-authorized payment without requiring another round-trip to the sender.

  • Machine-to-machine and API-triggered payments: services built on the x402 pay-per-request standard need to settle blockchain payments in response to HTTP events, not interactive wallet sessions.

  • Subscription and batch billing: a billing relayer needs to settle multiple pre-signed authorizations in a single transaction.

EVM chains address this with EIP-2612 (permit) and EIP-3009 (transferWithAuthorization). These are now ubiquitous building blocks in that ecosystem. Cosmos has x/authz and x/feegrant, which solve adjacent problems (persistent delegated execution grants and fee sponsorship, respectively), but neither provides a stateless, single-use signed payment primitive. x/wauth fills that gap.


Design

Authorization Structure

An Authorization is a signed, off-chain payment intent with the following fields:

Field Type Description
from sdk.AccAddress Spender (signs the authorization off-chain)
to sdk.AccAddress Recipient
coins sdk.Coins Multi-denom transfer amount
nonce [32]byte Cryptographically random, single-use value
valid_after time.Time Optional activation time
valid_before time.Time Required expiry deadline
chain_id string Prevents cross-chain replay attacks
memo string Relayer annotation (excluded from signing digest)

The memo field is deliberately excluded from the signing digest, sha256(gogoproto.Marshal(authorization_with_memo_cleared)) so relayers can annotate transactions without invalidating the user’s signature.

Message Types

The module exposes four messages, mirroring the EIP-3009 interface:

Message Submitter EIP-3009 Analogue
MsgTransferWithAuthorization Relayer (facilitator) transferWithAuthorization
MsgReceiveWithAuthorization Recipient receiveWithAuthorization
MsgMultiTransferWithAuthorization Relayer (batch extension - no EVM equivalent)
MsgCancelAuthorization Original signer cancelAuthorization

MsgTransferWithAuthorization

A third-party facilitator submits a user-signed authorization. The chain verifies the sender’s signature, checks the nonce, validates the time window, and calls bankKeeper.SendCoins. The facilitator pays gas; the sender’s funds move without any on-chain action from the sender.

MsgReceiveWithAuthorization

The designated recipient submits the authorization. The receiver field must match authorization.to, preventing front-running by other parties.

MsgMultiTransferWithAuthorization

A batch message supporting up to 100 (authorization, signature) pairs. Each item is validated and settled independently; per-item failures are reported in the response without rolling back the entire batch. This is essential for billing relayers settling many subscriptions in a single block.

MsgCancelAuthorization

Marks a nonce as consumed without executing a transfer, allowing users to revoke an outstanding authorization before it is settled.

Replay Protection

Each nonce is a 32-byte cryptographically random value (not a counter). After settlement or cancellation, the nonce is recorded in the module’s KV store keyed by (sender_address, nonce). Nonce records carry a prune_after timestamp (valid_before + nonce_prune_grace_period) to allow safe cleanup without a pruning race condition.

Signature Verification

The signing digest is sha256(proto_marshal(authorization_without_memo)). Verification supports:

  • secp256k1 (Cosmos-native): standard VerifySignature over the 32-byte SHA-256 digest.

  • eth_secp256k1 (EVM-compatible): native compact signature verification using standard ECDSA semantics.

Signatures are 64-byte compact (R+S) or 65-byte with recovery bit; the recovery bit is stripped before verification.

Module Parameters (governance-controlled)

Parameter Default Constraint
max_valid_window_seconds 3600 (1 hour) ≤ 2,592,000 (30 days)
nonce_prune_grace_period_seconds 600 (10 min) ≥ 0
enabled_denoms [] (all allowed) Optional governance allowlist

### Relationship to Existing Modules

Module Purpose Difference from x/wauth
x/authz Persistent, re-usable execution grants Grants persist until revoked; x/wauth authorizations are single-use and stateless until settled.
x/feegrant Fee sponsorship for the grantee’s transactions Pays gas for on-chain txs; does not settle token transfers.
x/bank Core transfer primitive x/wauth calls bankKeeper.SendCoins — it is a composable layer above, not a replacement.

x402 Compatibility

x/wauth is designed as the settlement layer for Coinbase/x402, a protocol for HTTP-native blockchain payments. In this model:

  1. A client signs an Authorization offline and includes it in an HTTP request header.

  2. The API server or a payment processor verifies the signature, serves the request, and submits MsgTransferWithAuthorization to settle.

  3. The chain settles the payment atomically on the next block.

This enables pay-per-request APIs, metered subscriptions, and IoT micropayments without requiring the client to interact with the chain directly during the payment flow. The MsgMultiTransferWithAuthorization batch message supports settlement aggregation across many requests per block.


Security Considerations

  • Replay protection: enforced by 32-byte random nonces (not counters) to prevent ordering attacks. Cross-chain replay is blocked by the mandatory chain_id field in the signing payload.

  • Front-running on ReceiveWithAuthorization: the receiver address is locked in the signed payload. A front-runner submitting MsgTransferWithAuthorization would succeed as a facilitator (funds still go to the correct recipient) but cannot redirect funds.

  • Griefing via cancellation: MsgCancelAuthorization is signed by the originating account, so only the sender can cancel their own nonces.

  • Time window bounds: max_valid_window_seconds (default 1h) is enforced on-chain to prevent authorizations with indefinitely long validity windows.

  • Denom allowlist: chain operators can restrict x/wauth to specific token denominations via governance.

  • Blocked addresses: bankKeeper.BlockedAddr is respected; module accounts and other restricted addresses cannot receive funds through x/wauth.


Reference Implementation

A complete, tested implementation is available targeting cosmos/evm. It includes:

  • Protobuf definitions for all message and query types

  • Keeper with nonce store and governance-controlled params

  • Full ValidateBasic and keeper-level validation

  • CLI commands for all message types and queries

  • Unit and integration tests

I am prepared to port this to cosmos/cosmos-sdk as a standalone module or as an extension under x/bank.


Questions for the Team

  1. Scope: Should this be proposed as a core SDK module, or a standalone maintained module? If core, which repository is the right target (cosmos-sdk or cosmos/evm)?

  2. Signing standard: The current digest is sha256(proto_marshal(auth)). Is there interest in aligning with a Cosmos-native typed signing standard (e.g. SignDoc-compatible) to support hardware wallets and Ledger out of the box?


I would be pleased to provide any additional information upon request. I would also greatly appreciate any feedback or suggestions regarding potential future directions.


Tillo Tulkinbekov

Blockchain Engineer, Gurufin - South Korea

tulkinbekov@gurufin.com / t.tulkinbekov@gmail.com

3 Likes

Looks like a profound work, but still some question:
I guess the initiator/receiver that will trigger the release needs at least an already initialized wallet (with sequence number). This might turn out a major problem in real environment: The service won’t work out of the box, or at least will be limited to the small batch of current cosmos users.

I created a similiar concept using cosmwasm too, that allows usual webapps to hand out Tokens to users.
The webapp doesn’t need to run a local signing or broadcasting client in the backend, its just creating the message that will trigger the escrow contract to release the funds mentioned in the tx

Here is the schematics:

WebApp

Composes the voucher message and decides the Token amount that shall
be sent to the user

The ECDSA signature is derived from this execution message body and is being attached below

The WebApp holds the ECDSA private key in his .ENV Variable only, no backend client necessary

The contract holds the ECDSA public key only
-it checks if the attached signature matches the message body
-it checks if the nounce has been used already
-it checks if the expiry date has been exceeded

if all conditions are met, the message gets executed and funds are being transferred
from the contract treasury to the user

otherwise the message is being dropped

Hi! Thanks for sharing this.

Can I ask what you’re using it for today, or plan to use it for?