Skip to content

Commit 9238e23

Browse files
fix: default chain_id to 4217 (mainnet), matching mppx and mpp-rs (#108)
* fix: default chain_id to 4217 (mainnet), matching mppx and mpp-rs When chain_id was omitted from tempo(), the challenge's methodDetails lacked a chainId field. The Rust CLI (mpp-rs) strictly requires chainId and rejects challenges without it: 'Malformed payment request: missing chainId'. Both mppx and mpp-rs default to 4217 (mainnet). This makes pympp consistent so servers work out of the box without explicitly passing chain_id. * chore: add changelog * fix: remove hardcoded testnet fee payer URL, require explicit config The DEFAULT_FEE_PAYER_URL (sponsor.moderato.tempo.xyz) was a testnet-only service silently used as fallback when no fee payer was configured. With chain_id now defaulting to mainnet (4217), this would route mainnet transactions to a testnet sponsor. Align with mppx and mpp-rs: require explicit fee payer configuration (either a feePayer account on the method, or a feePayerUrl in methodDetails). Raise a clear error if neither is set. * test: update tests for chain_id=4217 default and fee payer changes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 3126eaa commit 9238e23

7 files changed

Lines changed: 51 additions & 27 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pympp: patch
3+
---
4+
5+
Defaulted `chain_id` to 4217 (mainnet) in the `tempo()` function, removing the need to pass it explicitly. Updated docs and example code accordingly.

src/mpp/methods/tempo/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
2121
server = Mpp.create(
2222
method=tempo(
23-
chain_id=42431,
2423
intents={"charge": ChargeIntent()},
2524
),
2625
)

src/mpp/methods/tempo/_defaults.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
TESTNET_CHAIN_ID = 42431
1414
TESTNET_RPC_URL = "https://rpc.moderato.tempo.xyz"
1515

16-
# Testnet only — the fee payer service sponsors gas on testnet.
17-
# On mainnet, the server itself must pay gas or provide its own fee payer.
18-
DEFAULT_FEE_PAYER_URL = "https://sponsor.moderato.tempo.xyz"
19-
2016
# Chain ID -> default currency mapping
2117
# Mainnet defaults to USDC, testnet defaults to pathUSD
2218
DEFAULT_CURRENCIES: MappingProxyType[int, str] = MappingProxyType(

src/mpp/methods/tempo/client.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from mpp import Challenge, Credential
1313
from mpp.methods.tempo._attribution import encode as encode_attribution
1414
from mpp.methods.tempo._defaults import (
15+
CHAIN_ID,
1516
CHAIN_RPC_URLS,
1617
RPC_URL,
1718
default_currency_for_chain,
@@ -303,7 +304,7 @@ def tempo(
303304
intents: dict[str, Intent],
304305
account: TempoAccount | None = None,
305306
fee_payer: TempoAccount | None = None,
306-
chain_id: int | None = None,
307+
chain_id: int = CHAIN_ID,
307308
rpc_url: str | None = None,
308309
root_account: str | None = None,
309310
currency: str | None = None,
@@ -320,8 +321,8 @@ def tempo(
320321
(server-side). When set, the server signs with domain
321322
``0x78`` and broadcasts directly — no external fee payer
322323
service needed.
323-
chain_id: Tempo chain ID (4217 for mainnet, 42431 for testnet).
324-
Resolves the RPC URL automatically from known chains.
324+
chain_id: Tempo chain ID (default: 4217 for mainnet, use 42431
325+
for testnet). Resolves the RPC URL automatically from known chains.
325326
rpc_url: Tempo RPC endpoint URL. Overrides the URL resolved
326327
from ``chain_id``. Defaults to mainnet if neither is set.
327328
root_account: Root account address for access key signing.
@@ -350,7 +351,7 @@ def tempo(
350351
)
351352
"""
352353
if rpc_url is None:
353-
rpc_url = rpc_url_for_chain(chain_id) if chain_id else RPC_URL
354+
rpc_url = rpc_url_for_chain(chain_id)
354355

355356
if currency is None:
356357
currency = default_currency_for_chain(chain_id)

src/mpp/methods/tempo/intents.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from mpp import Credential, Receipt
1616
from mpp.errors import VerificationError
17-
from mpp.methods.tempo._defaults import DEFAULT_FEE_PAYER_URL, PATH_USD, rpc_url_for_chain
17+
from mpp.methods.tempo._defaults import PATH_USD, rpc_url_for_chain
1818
from mpp.methods.tempo.schemas import (
1919
ChargeRequest,
2020
CredentialPayload,
@@ -366,7 +366,12 @@ async def _verify_transaction(
366366
if self.fee_payer is not None:
367367
raw_tx = self._cosign_as_fee_payer(raw_tx, request.currency, request=request)
368368
else:
369-
fee_payer_url = request.methodDetails.feePayerUrl or DEFAULT_FEE_PAYER_URL
369+
fee_payer_url = request.methodDetails.feePayerUrl
370+
if not fee_payer_url:
371+
raise VerificationError(
372+
"No fee payer configured: set feePayer on the tempo() method "
373+
"or provide a feePayerUrl in methodDetails"
374+
)
370375

371376
sign_response = await client.post(
372377
fee_payer_url,

tests/test_mpp_create.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,12 @@ async def test_charge_override_currency_recipient(self) -> None:
239239
assert result.request["recipient"] == "0xother"
240240

241241
@pytest.mark.asyncio
242-
async def test_charge_defaults_currency_to_pathusd(self) -> None:
243-
"""Currency defaults to pathUSD when chain_id is not set."""
244-
from mpp.methods.tempo import PATH_USD
242+
async def test_charge_defaults_currency_to_usdc(self) -> None:
243+
"""Currency defaults to USDC when chain_id defaults to mainnet (4217)."""
244+
from mpp.methods.tempo import USDC
245245

246246
method = tempo(intents={"charge": ChargeIntent()})
247-
assert method.currency == PATH_USD
247+
assert method.currency == USDC
248248

249249
@pytest.mark.asyncio
250250
async def test_charge_defaults_currency_to_usdc_on_mainnet(self) -> None:

tests/test_tempo.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -612,9 +612,10 @@ async def test_client_builds_sponsored_transaction(self, httpx_mock: HTTPXMock)
612612
intents={"charge": ChargeIntent()},
613613
)
614614

615+
# eth_chainId
615616
httpx_mock.add_response(
616617
url="https://rpc.test",
617-
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},
618+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
618619
)
619620
httpx_mock.add_response(
620621
url="https://rpc.test",
@@ -1191,10 +1192,10 @@ def test_tempo_factory_stores_chain_id(self) -> None:
11911192
method = tempo(chain_id=42431, intents={"charge": ChargeIntent()})
11921193
assert method.chain_id == 42431
11931194

1194-
def test_tempo_factory_chain_id_defaults_none(self) -> None:
1195-
"""tempo() without chain_id should default to None."""
1195+
def test_tempo_factory_chain_id_defaults_mainnet(self) -> None:
1196+
"""tempo() without chain_id should default to 4217 (mainnet)."""
11961197
method = tempo(intents={"charge": ChargeIntent()})
1197-
assert method.chain_id is None
1198+
assert method.chain_id == 4217
11981199

11991200
def test_tempo_factory_chain_id_resolves_rpc(self) -> None:
12001201
"""tempo(chain_id=42431) should resolve testnet RPC URL."""
@@ -1275,9 +1276,10 @@ async def test_client_falls_back_to_method_rpc_for_unknown_chain(
12751276
intents={"charge": ChargeIntent()},
12761277
)
12771278

1279+
# eth_chainId
12781280
httpx_mock.add_response(
12791281
url="https://rpc.custom",
1280-
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},
1282+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
12811283
)
12821284
httpx_mock.add_response(
12831285
url="https://rpc.custom",
@@ -1323,9 +1325,10 @@ async def test_client_ignores_non_numeric_chain_id(self, httpx_mock: HTTPXMock)
13231325
intents={"charge": ChargeIntent()},
13241326
)
13251327

1328+
# eth_chainId
13261329
httpx_mock.add_response(
13271330
url="https://rpc.custom",
1328-
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},
1331+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
13291332
)
13301333
httpx_mock.add_response(
13311334
url="https://rpc.custom",
@@ -1415,8 +1418,12 @@ async def test_access_key_builds_keychain_signature(self, httpx_mock: HTTPXMock)
14151418
intents={"charge": ChargeIntent()},
14161419
)
14171420

1418-
# Mock RPC: chain_id, nonce, gas_price, estimateGas
1419-
for _ in range(4):
1421+
# Mock RPC: chain_id (4217=0x1079), nonce, gas_price, estimateGas
1422+
httpx_mock.add_response(
1423+
url="https://rpc.test",
1424+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
1425+
)
1426+
for _ in range(3):
14201427
httpx_mock.add_response(
14211428
url="https://rpc.test",
14221429
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},
@@ -1455,9 +1462,15 @@ async def test_access_key_with_fee_payer(self, httpx_mock: HTTPXMock) -> None:
14551462
intents={"charge": ChargeIntent()},
14561463
)
14571464

1458-
for _ in range(4):
1465+
# Mock RPC: chain_id (4217=0x1079), nonce, gas_price, estimateGas
1466+
# Challenge chainId=4217 resolves to rpc.tempo.xyz
1467+
httpx_mock.add_response(
1468+
url="https://rpc.tempo.xyz",
1469+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
1470+
)
1471+
for _ in range(3):
14591472
httpx_mock.add_response(
1460-
url="https://rpc.test",
1473+
url="https://rpc.tempo.xyz",
14611474
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},
14621475
)
14631476

@@ -1469,7 +1482,7 @@ async def test_access_key_with_fee_payer(self, httpx_mock: HTTPXMock) -> None:
14691482
"amount": "1000000",
14701483
"currency": "0x20c0000000000000000000000000000000000000",
14711484
"recipient": "0x742d35Cc6634c0532925a3b844bC9e7595F8fE00",
1472-
"methodDetails": {"feePayer": True, "chainId": 1},
1485+
"methodDetails": {"feePayer": True, "chainId": 4217},
14731486
},
14741487
realm="test.example.com",
14751488
request_b64="e30",
@@ -1492,7 +1505,12 @@ async def test_no_root_account_uses_regular_signing(self, httpx_mock: HTTPXMock)
14921505
intents={"charge": ChargeIntent()},
14931506
)
14941507

1495-
for _ in range(4):
1508+
# Mock RPC: chain_id (4217=0x1079), nonce, gas_price, estimateGas
1509+
httpx_mock.add_response(
1510+
url="https://rpc.test",
1511+
json={"jsonrpc": "2.0", "result": "0x1079", "id": 1},
1512+
)
1513+
for _ in range(3):
14961514
httpx_mock.add_response(
14971515
url="https://rpc.test",
14981516
json={"jsonrpc": "2.0", "result": "0x1", "id": 1},

0 commit comments

Comments
 (0)