Summary
When a server's WWW-Authenticate header includes a tempo/session challenge, tempo request unconditionally routes into a session-only payment path, even if the same response also offers tempo/charge challenges the client fully supports. If the session challenge is TIP-1034-legacy (protocol v1 — i.e. methodDetails carries no sessionProtocol marker), the currently-registered session method rejects it and the request fails outright with E_PAYMENT, despite compatible charge challenges being right there in the same header. There's no fallback from the session-only path back to the generic method set.
Environment
wallet-cli (tempo launcher) 1.x, tempo-wallet 0.7.0, tempo-request 0.7.0
- Linux x86_64 (WSL2)
- Tempo mainnet, chainId 4217
mppx 0.8.9 (the payment method library wallet-cli builds on)
Reproduction
Target: a live MPP server on Tempo mainnet that emits 2 tempo/charge challenges plus 1 tempo/session challenge in the same 402, where the session challenge is protocol v1 (no sessionProtocol marker in methodDetails):
curl -s -D - -o /dev/null "https://api.onesource.io/api/chain/block-number?network=ethereum"
HTTP/1.1 402 Payment Required
Www-Authenticate: Payment id="...", realm="api.onesource.io", method="tempo", intent="charge", request="<base64>", expires="...", description="api.onesource.io API call"
Www-Authenticate: Payment id="...", realm="api.onesource.io", method="tempo", intent="charge", request="<base64>", expires="...", description="api.onesource.io API call"
Www-Authenticate: Payment id="...", realm="api.onesource.io", method="tempo", intent="session", request="<base64>", expires="...", description="api.onesource.io API call"
Decoding the session challenge's request field (base64url JSON, no secrets):
{
"amount": "1000",
"currency": "0x20C000000000000000000000b9537d11c60E8b50",
"description": "api.onesource.io API call",
"methodDetails": {
"chainId": 4217,
"escrowContract": "0x33b901018174DDabE4841042ab76ba85D4e24f25",
"minVoucherDelta": "1000"
},
"recipient": "0x19B8e99079A5558ff4460357b0a78e14a7F600B7",
"suggestedDeposit": "10000"
}
Note there is no sessionProtocol key under methodDetails — this is a v1-style session challenge.
Three probes against this endpoint, all on 0.7.0:
| Identity |
Command |
Result |
| Default passkey wallet (P-256 access key, funded 0.72 USDC.e) |
tempo request <url> |
Fails: E_PAYMENT: No method found for challenges: tempo.charge, tempo.charge, tempo.session. Available: tempo.session |
Funded secp256k1 EOA via --private-key |
tempo request <url> --private-key <key> --max-spend 0.02 |
Same failure — identity type is irrelevant to the bug |
Fresh, unfunded key via --private-key |
tempo request <url> --private-key <key> |
Fails earlier, at balance-check: Insufficient balance for session deposit: available=0 required=0.01 — confirms the client parses the v1 challenge and computes the deposit from suggestedDeposit fine; only the method-matching step rejects it |
For comparison, the same wallet against the same endpoint on the last pre-regression release, tempo-wallet/tempo-request 0.4.4, pays successfully via the charge challenge. So the regression window is the 0.6.0 rewrite onward.
Root cause analysis
(All file/line references below are against wallet-cli HEAD 9a94aab, package.json version 0.7.0, and mppx@0.8.9.)
-
src/commands/request.ts:502-506 — if any session challenge is present in the header, payAndRetryRequest short-circuits into the session-only path before the generic path is ever reached:
const sessionChallenge = sessionChallengeFromHeader(header);
if (sessionChallenge)
return withSessionLock(request.url, () =>
paySessionAndRetryRequest(paymentRequiredResponse, request, options, sessionChallenge),
);
-
src/commands/request.ts:559-567, inside paySessionAndRetryRequest, the Mppx instance is built with only the session method registered:
const payment = Mppx.create({
methods: [
tempoSession({
...identity.methodOptions,
...(options.maxSpend ? { maxDeposit: options.maxSpend } : {}),
}),
],
polyfill: false,
});
The generic path at src/commands/request.ts:518-521, which registers tempo(methodOptions) (charge-capable) alongside tempo.subscription(...), is never reached once a session challenge is present.
-
mppx@0.8.9 src/tempo/session/client/Session.ts:56-63 — the modern session method that wallet-cli imports (session as tempoSession from mppx/client) only accepts protocol-v2 challenges:
canHandleChallenge({ challenge }) {
return (
Constants.getMethodDetail(
challenge.request.methodDetails,
Constants.MethodDetailKeys.sessionProtocol,
) === Constants.SessionProtocols.v2
)
},
mppx also ships a legacy session method (src/tempo/legacy/client/Session.ts:340-350) whose canHandleChallenge accepts undefined or v1 — but wallet-cli never registers it.
-
mppx@0.8.9 dist/internal/AcceptPayment.js:93 (also dist/client/Mppx.js / dist/client/internal/Fetch.js, same filter) is where challenge-to-method matching happens:
.filter((method) => method.canHandleChallenge?.({ challenge }) ?? true)
With only the modern session method registered and a v1-shaped challenge, this filter yields zero candidates for tempo.session, and — because paySessionAndRetryRequest never registers a charge method at all — zero candidates for tempo.charge too. Mppx.createCredential then throws:
No method found for challenges: tempo.charge, tempo.charge, tempo.session. Available: tempo.session
Net effect: the CLI's own routing decision (step 1) removes the charge methods from consideration before mppx's method matcher (step 4) ever gets a chance to fall back to them.
Expected behavior
When the session-only path finds no method able to handle the session challenge (or, more directly, whenever a session challenge is present alongside charge challenges), the CLI should fall back to the generic method set so the charge challenges — which it already supports — get honored instead. Failing that, at minimum it should surface a clear, actionable error (e.g. "server offers only a session protocol v1 challenge, which this client version doesn't support — no compatible payment method found") rather than the current message, which reads as a total capability mismatch even when two compatible charge challenges were on offer.
Impact
Any server still on session protocol v1 that also advertises charge (the expected state during a v1→v2 migration window) hard-fails every tempo request call, with no workaround short of pinning to tempo-wallet/tempo-request 0.4.4. Because the CLI auto-updates, this affects all current users hitting such servers, not just ones who opt in to a new version.
We're also adding session protocol v2 support server-side on our end, so this report is about the missing graceful degradation for the transition period, not a request to keep v1 alive indefinitely.
Summary
When a server's
WWW-Authenticateheader includes atempo/sessionchallenge,tempo requestunconditionally routes into a session-only payment path, even if the same response also offerstempo/chargechallenges the client fully supports. If the session challenge is TIP-1034-legacy (protocol v1 — i.e.methodDetailscarries nosessionProtocolmarker), the currently-registered session method rejects it and the request fails outright withE_PAYMENT, despite compatible charge challenges being right there in the same header. There's no fallback from the session-only path back to the generic method set.Environment
wallet-cli(tempo launcher) 1.x,tempo-wallet0.7.0,tempo-request0.7.0mppx0.8.9 (the payment method librarywallet-clibuilds on)Reproduction
Target: a live MPP server on Tempo mainnet that emits 2
tempo/chargechallenges plus 1tempo/sessionchallenge in the same 402, where the session challenge is protocol v1 (nosessionProtocolmarker inmethodDetails):Decoding the
sessionchallenge'srequestfield (base64url JSON, no secrets):{ "amount": "1000", "currency": "0x20C000000000000000000000b9537d11c60E8b50", "description": "api.onesource.io API call", "methodDetails": { "chainId": 4217, "escrowContract": "0x33b901018174DDabE4841042ab76ba85D4e24f25", "minVoucherDelta": "1000" }, "recipient": "0x19B8e99079A5558ff4460357b0a78e14a7F600B7", "suggestedDeposit": "10000" }Note there is no
sessionProtocolkey undermethodDetails— this is a v1-style session challenge.Three probes against this endpoint, all on 0.7.0:
tempo request <url>E_PAYMENT: No method found for challenges: tempo.charge, tempo.charge, tempo.session. Available: tempo.session--private-keytempo request <url> --private-key <key> --max-spend 0.02--private-keytempo request <url> --private-key <key>Insufficient balance for session deposit: available=0 required=0.01— confirms the client parses the v1 challenge and computes the deposit fromsuggestedDepositfine; only the method-matching step rejects itFor comparison, the same wallet against the same endpoint on the last pre-regression release,
tempo-wallet/tempo-request0.4.4, pays successfully via the charge challenge. So the regression window is the 0.6.0 rewrite onward.Root cause analysis
(All file/line references below are against
wallet-cliHEAD9a94aab,package.jsonversion0.7.0, andmppx@0.8.9.)src/commands/request.ts:502-506— if any session challenge is present in the header,payAndRetryRequestshort-circuits into the session-only path before the generic path is ever reached:src/commands/request.ts:559-567, insidepaySessionAndRetryRequest, theMppxinstance is built with only the session method registered:The generic path at
src/commands/request.ts:518-521, which registerstempo(methodOptions)(charge-capable) alongsidetempo.subscription(...), is never reached once a session challenge is present.mppx@0.8.9src/tempo/session/client/Session.ts:56-63— the modern session method thatwallet-cliimports (session as tempoSessionfrommppx/client) only accepts protocol-v2 challenges:mppx also ships a legacy session method (
src/tempo/legacy/client/Session.ts:340-350) whosecanHandleChallengeacceptsundefinedorv1— butwallet-clinever registers it.mppx@0.8.9dist/internal/AcceptPayment.js:93(alsodist/client/Mppx.js/dist/client/internal/Fetch.js, same filter) is where challenge-to-method matching happens:With only the modern session method registered and a v1-shaped challenge, this filter yields zero candidates for
tempo.session, and — becausepaySessionAndRetryRequestnever registers a charge method at all — zero candidates fortempo.chargetoo.Mppx.createCredentialthen throws:Net effect: the CLI's own routing decision (step 1) removes the charge methods from consideration before mppx's method matcher (step 4) ever gets a chance to fall back to them.
Expected behavior
When the session-only path finds no method able to handle the session challenge (or, more directly, whenever a session challenge is present alongside charge challenges), the CLI should fall back to the generic method set so the charge challenges — which it already supports — get honored instead. Failing that, at minimum it should surface a clear, actionable error (e.g. "server offers only a session protocol v1 challenge, which this client version doesn't support — no compatible payment method found") rather than the current message, which reads as a total capability mismatch even when two compatible charge challenges were on offer.
Impact
Any server still on session protocol v1 that also advertises charge (the expected state during a v1→v2 migration window) hard-fails every
tempo requestcall, with no workaround short of pinning totempo-wallet/tempo-request0.4.4. Because the CLI auto-updates, this affects all current users hitting such servers, not just ones who opt in to a new version.We're also adding session protocol v2 support server-side on our end, so this report is about the missing graceful degradation for the transition period, not a request to keep v1 alive indefinitely.