`resolveNetwork` read one field (`methodDetails.chainId`) and branched for two
methods, then fell back to `${method}:unknown` for everything else. Solana's
charge draft puts the cluster in `methodDetails.network` and obliges clients to
reject a challenge whose cluster differs from their configured one — so a
devnet challenge and a mainnet challenge both lifted as `solana:unknown`, and
the comparison the spec requires could not fail. The one field a client must
check was discarded into a value that names no network.
Resolution now follows the owning method spec:
- solana `methodDetails.network` ∈ {mainnet, devnet, localnet}, default mainnet
- lightning `methodDetails.network` ∈ {mainnet, regtest, signet}, default mainnet
- stellar `methodDetails.network` — REQUIRED, already CAIP-2, validated and passed through
- tempo `methodDetails.chainId` — OPTIONAL, spec'd default 42431 now applied
- evm `methodDetails.chainId` — REQUIRED, rejected when absent
Lightning is the same defect as Solana with a milder consequence, which is why
the severity ranking hid it. Its spec carries an identical enumerated `network`
field with a reject-on-mismatch clause.
`${method}:unknown` is removed rather than forbidden. Every member of
BLOCKCHAIN_CHARGE_METHODS now resolves or throws, so the sentinel has no
producer left — deleting an unreachable branch cannot regress a caller, whereas
turning a reachable one into an error can.
Adds `FromMppChargeOptions.expectedNetwork`, which enforces the spec's
reject-on-mismatch clause at the lift. The second parameter still accepts a
bare `number` as the legacy `now` argument.
Also: the catch-all rejection told callers that hedera, usdc and nearintents
"have no payTo/asset exposed in the Charge request". All three are specified
blockchain charge methods with real payTo fields; they are unmapped because
their request shapes are nested or cross-chain. The message now says that.
Header spec references are cited per area instead of as one repo-wide baseline.
The old single commit was wrong in both directions — the module already
implemented a change newer than its declared baseline while lagging the method
drafts by 22 commits.
Verification:
cd typescript && pnpm vitest run test/compat-mpp-network-resolution.test.ts
30 new tests, observed red against HEAD (23 failed / 7 passed) before the fix.
Full suite 1132 passed (29 files) · tsc --noEmit clean · build clean.
Refs: DAN-848
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The defect
resolveNetworkread exactly one field —methodDetails.chainId— and branched for exactly two methods. Everything else fell through to`${method}:unknown`.The Solana charge draft puts the cluster in
methodDetails.networkand makes it safety-critical:So a devnet challenge and a mainnet challenge both lifted into s402 requirements as
solana:unknown. The one field the spec obliges a client to compare was discarded into a value that cannot fail the comparison, because it names no cluster.This is a defect, not upstream drift. The field predates the module's declared spec baseline by six weeks — a baseline bump would have closed the sibling findings and left this one exactly as it is.
What changed
Resolution now reads whichever field the owning method spec names:
solanamethodDetails.networkmainnetsolana:{mainnet|devnet|localnet}lightningmethodDetails.networkmainnetlightning:{mainnet|regtest|signet}stellarmethodDetails.networktempomethodDetails.chainIdtempo:{chainId}evmmethodDetails.chainIdeip155:{chainId}Lightning is Solana's twin. Its draft carries an identical enumerated
networkfield with the same spec'd default and its own reject-on-mismatch clause (SHOULD rather than MUST). It was demoted to a prose footnote because its consequence is milder — the severity ranking hid a second instance of the same bug.${method}:unknownis removed rather than forbidden. Every member ofBLOCKCHAIN_CHARGE_METHODSnow resolves or throws, so the sentinel has no producer left. That ordering is the point: deleting an unreachable branch cannot regress a caller, whereas turning a reachable one into an error can. Doing it the other way round would have converted Lightning from working-though-degraded into a hard failure — a regression introduced by the fix rather than found by it.FromMppChargeOptions.expectedNetworkenforces the spec's reject-on-mismatch clause at the lift, so a caller that knows its configured network cannot forget the comparison downstream. The second parameter still accepts a barenumberas the legacynowargument.Two things this also corrects
The rejection message.
hedera,usdcandnearintentsare specified blockchain charge methods with realpayTofields, and the catch-all told callers they "have no payTo/asset exposed in the Charge request" — sending the reader to look for a problem that is not there. They are unmapped because their request shapes are nested (methodDetails.evm.chainId) or cross-chain, which is what the message now says.The header's spec baseline, which was wrong in both directions: it declared one repo-wide commit while the module already implemented a change newer than that baseline and lagged the method drafts by 22 commits. A single baseline asserts uniform conformance a partial implementation never has, so citations are now per area, with the unsynced methods named.
Verification
mainbefore the fix (23 failed / 7 passed) by revertingsrc/and re-running.tsc --noEmitclean ·pnpm buildclean.tempoxyz/mpp-specs @ f9506cd).The new tests live in their own file on purpose.
compat-mpp.test.tsgreps positive forsolana— five hits — but every one is inside aparseMppAcceptPaymenttest and no Solana challenge had ever reachedfromMppChargeChallenge. The gap was camouflaged rather than absent, which is how it survived review.Deliberately not in this PR
fromMppChargeChallengerejects the Lightning draft's own charge example, because it requiresrecipientfor the whole blockchain set while Lightning's spec declares it OPTIONAL ("the invoice payee is implied by the BOLT11 invoice"). Relaxing a public validation is a different risk class from resolving an identifier, and it carries a real design question — whatpayTobecomes for an invoice-authoritative method. Filed separately with the repro.hedera/usdc/nearintents(DAN-848 criteria 3–4).usdcnests its chain id per chain family andnearintentsis cross-chain with a CAIP-2originNetworkplus a distinct destination asset, so a singlenetworkfield is ambiguous for it. Neither fits the flat translation without a decision.Refs: DAN-848
🤖 Generated with Claude Code