Skip to content

feat: implement full fee payer support#62

Merged
brendanjryan merged 8 commits intomainfrom
feat/full-fee-payer-support
Feb 19, 2026
Merged

feat: implement full fee payer support#62
brendanjryan merged 8 commits intomainfrom
feat/full-fee-payer-support

Conversation

@brendanjryan
Copy link
Collaborator

Port of tempoxyz/mpp-rs#71 to pympp.

Changes

Client side (client.py)

  • When feePayer: true in challenge, use expiring nonces (nonce_key=U256::MAX, nonce=0) with valid_before=now+25s for replay protection
  • Set fee_token=None and add placeholder fee_payer_signature (0x00)
  • No on-chain nonce fetch needed in fee payer mode

Server side (intents.py)

  • Add _cosign_as_fee_payer() method: decodes 0x76 tx, recovers sender via ecrecover, sets fee_token, co-signs with domain 0x78
  • _verify_transaction() co-signs locally when fee_payer account is configured on the method, falls back to external fee payer service otherwise

Factory (client.py tempo())

  • Add fee_payer parameter to tempo() and TempoMethod
  • Wire up _method backlink so ChargeIntent can access fee_payer account

Testing

  • All 234 unit tests pass
  • Tested live against mpp.sh/api/ping/paid
  • Tested pympp client → pympp example server (no fee payer) ✅
  • Tested pympp client → pympp server with fee payer co-signing on testnet ✅

Usage

# Server with local fee payer co-signing
server = Mpp.create(
    method=tempo(
        chain_id=42431,
        fee_payer=TempoAccount.from_env("FEE_PAYER_KEY"),
        currency=PATH_USD,
        recipient="0x...",
        intents={"charge": ChargeIntent()},
    ),
)

# Client (no changes needed — auto-detects feePayer from challenge)
method = tempo(
    account=TempoAccount.from_key("0x..."),
    intents={"charge": ChargeIntent()},
)

brendanjryan and others added 2 commits February 18, 2026 20:22
Port of tempoxyz/mpp-rs#71 to pympp.

Client side:
- When feePayer: true in challenge, use expiring nonces (nonce_key=U256::MAX,
  nonce=0) with valid_before=now+25s for replay protection
- Set fee_token=None and add placeholder fee_payer_signature (0x00)

Server side:
- Add _cosign_as_fee_payer() that decodes 0x76 tx, recovers sender via
  ecrecover, sets fee_token, and co-signs with domain 0x78
- _verify_transaction() co-signs locally when fee_payer account is configured
  on the method, falls back to external fee payer service otherwise

Factory:
- Add fee_payer parameter to tempo() and TempoMethod
- Wire up _method backlink so ChargeIntent can access fee_payer account

Tested end-to-end: pympp client -> pympp server with fee payer co-signing
on testnet (chain 42431).
- Add _validate_cosign_calls() to reject transactions that don't match
  the charge request (currency, recipient, amount, selector) before
  the server co-signs them. Prevents the fee payer from being used as
  an open gas relay.
- Move attrs import to top-level in client.py and intents.py (hard dep).
- Add 11 tests: cosign roundtrip, malformed input rejection, call
  validation (wrong currency/amount/recipient), and fee_payer
  propagation through tempo() factory.

try:
cosigned = tx_to_sign.sign(self.fee_payer.private_key, for_fee_payer=True)
except Exception as err:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I tighten this up at all?

@brendanjryan brendanjryan merged commit c319eae into main Feb 19, 2026
2 checks passed
@brendanjryan brendanjryan deleted the feat/full-fee-payer-support branch February 19, 2026 05:20
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