Skip to content

MPP-Tempo

Pay a paywalled API with PathUSD or USDC on the Tempo L2 using the MPP (Machine Payment Protocol) with method=tempo.

Prerequisites

Requirement Notes
Tempo wallet Any eth_account.LocalAccount — same key format as x402
PathUSD balance (testnet) Request from faucet.tempo.xyz for Moderato testnet
USDC balance (mainnet) Bridge or acquire USDC on the Tempo mainnet (chain ID 42430)
MPP-Tempo endpoint A server that sends WWW-Authenticate: Payment method="tempo" on 402

Environment variables

export TEMPO_PRIVATE_KEY=0x<your-private-key>
export TEMPO_MERCHANT_ENDPOINT=https://your-api.com/paid

Testnet: Tempo Moderato

Moderato is Tempo's public testnet (chain ID 42431). PathUSD is the faucet-funded stablecoin.

import asyncio
import os
from eth_account import Account
from routeweiler import Routeweiler, Funding
from routeweiler.trace.sink_sqlite import TraceSink

async def main():
    wallet = Account.from_key(os.environ["TEMPO_PRIVATE_KEY"])

    async with Routeweiler(
        funding=[Funding.tempo_pathusd_moderato(wallet=wallet)],
        trace_sink=TraceSink.sqlite("rw.db"),
        agent_id="tempo-agent",
    ) as client:
        response = await client.get(os.environ["TEMPO_MERCHANT_ENDPOINT"])
        print(f"HTTP {response.status_code}: {response.json()}")

asyncio.run(main())

Mainnet: Tempo with USDC

async with Routeweiler(
    funding=[Funding.tempo_usdc(wallet=wallet)],  # chain ID 42430, USDC
    trace_sink=TraceSink.sqlite("rw.db"),
) as client:
    response = await client.get("https://your-api.com/paid-endpoint")

Custom RPC or token

Use TempoFundingSource directly when you need a non-default RPC URL or token:

from routeweiler.funding.tempo import TempoFundingSource, EthAccountTempoSigner
from eth_account import Account

wallet = Account.from_key(os.environ["TEMPO_PRIVATE_KEY"])
funding = TempoFundingSource(
    signer=EthAccountTempoSigner(wallet=wallet, chain_id=42431),
    network="tempo-moderato",
    asset="pathusd",
    rpc_url="https://rpc.moderato.tempo.xyz",  # or your own RPC
)

What the flow looks like

GET https://your-api.com/paid
  ← 402 Payment Required
    WWW-Authenticate: Payment id="live-abc123",
                              realm="api.example.com",
                              method="tempo",
                              intent="charge",
                              request="<b64url JCS payload>",
                              expires="2026-01-01T00:00:00Z"

  Routeweiler:
    1. Parses MPP-Tempo challenge (charge_id, amount, recipient, chain_id)
    2. Checks policy and budget
    3. Builds a Tempo 0x76 transaction (EIP-2718 type 0x76)
    4. Signs it locally with EthAccountTempoSigner
    5. Sends Authorization: Payment <b64url credential>

GET https://your-api.com/paid
  Authorization: Payment <b64url credential with signed tx>
  ← 200 OK
    Payment-Receipt: <b64url JCS receipt with tx hash>

The 0x76 transaction type is specific to Tempo. The merchant submits it to the Tempo RPC (eth_sendRawTransaction) and returns the receipt once the tx is mined.

What to check in the trace

import sqlite3, json

conn = sqlite3.connect("rw.db")
rows = conn.execute(
    "SELECT * FROM trace_events WHERE selected_rail = 'mpp-tempo'"
).fetchall()
conn.close()

for row in rows:
    p = json.loads(row["payload"])
    payment = p["payment"]
    print(f"Tx hash: {payment['proofValue']}")       # 0x<64 hex>
    print(f"Amount: {payment['amountNative']} base units")
    print(f"Currency: {payment['amountNativeCurrency']}")  # CAIP-19 token address
    print(f"Settlement latency: {payment['settlementLatencyMs']} ms")

proofType will be "txid". The proofValue is a valid Tempo on-chain tx hash. You can verify it at explore.testnet.tempo.xyz (Moderato testnet) or the Tempo mainnet explorer.

Error handling

from routeweiler import (
    SigningError,
    MppChargeFailedError,
    MppReceiptVerificationError,
    NoFeasibleRailError,
)

try:
    response = await client.get(url)
except MppChargeFailedError as exc:
    print(f"Merchant rejected the charge: {exc}")
except MppReceiptVerificationError as exc:
    print(f"Settlement receipt invalid: {exc}")
except SigningError as exc:
    print(f"Tempo transaction signing failed: {exc}")
except NoFeasibleRailError:
    print("No MPP-Tempo funding source matched this challenge.")

See also