Skip to content

feat(x402-e2e): post-commit lifecycle scenarios + PR6 cleanups#76

Merged
levalleux-ludo merged 33 commits into
mainfrom
add-x402-e2e-post-commit-lifecycle
May 21, 2026
Merged

feat(x402-e2e): post-commit lifecycle scenarios + PR6 cleanups#76
levalleux-ludo merged 33 commits into
mainfrom
add-x402-e2e-post-commit-lifecycle

Conversation

@levalleux-ludo
Copy link
Copy Markdown
Member

@levalleux-ludo levalleux-ludo commented May 19, 2026

Summary

Picks up the e2e suite where #74 left off (A1 only). Adds the @p0/@p1 buyer-driven post-commit lifecycle scenarios and applies the two robustness cleanups carried over from PR6.

New scenarios in typescript/packages/x402-e2e/test/scenarios/:

  • commit.test.ts — promotes A2 (atomic commit-and-redeem via Policy.redeemMode: \"commit-and-redeem\") from it.todo to runnable.
  • New post-commit.test.ts:
    • B1 redeem after deferred commit → REDEEMED
    • B2 completeExchange after redeem → COMPLETED
    • B3 raiseDispute after redeem → DISPUTED + RESOLVING
    • B4 mutual resolveDispute (50/50 dual-sig) → RESOLVED
    • B6 retractDispute by buyer → RETRACTED
    • B7 cancelVoucher before redeem → CANCELLED (driven via the facilitator since server-express doesn't mount a /cancel route)

Each post-commit test starts from a fresh commit driven through the same 402 retry A1 uses, then drives the transition under test.

Harness extensions:

  • SellerActor.signResolutionProposal — produces the counterpartySig the buyer needs for mutual resolveDispute. Routes through coreSdk.signDisputeResolutionProposal so the EIP-712 typed-data stays in lock-step with the deployed protocol.
  • New post-commit-http.tsperformBuyerPostCommitAction wraps buyer.client.signAction(...) + POST to /x402B/{redeem,complete,dispute/{raise,resolve,retract,escalate}}.
  • New facilitator-perform-action.tsperformCancelVoucher reaches the facilitator's POST /perform-action directly.
  • BuyerActor now accepts an optional policy override (needed by A2).

PR6 cleanups (the two robustness items the A1 PR called out as follow-ups):

  • exchange-reader.ts now accepts a viem PublicClient; on a null getExchangeById it fetches the chain head and calls coreSdk.waitForGraphNodeIndexing(blockNumber) before forwarding null — the canonical "indexer caught up" wait, replacing reliance on the outer poll loop alone.
  • New examples/resource-server/src/protocol-config.tsfetchProtocolConfig reads ConfigHandlerFacet.getMaxTotalOfferFeePercentage() + getMinDisputePeriod(). offer.ts tightens feeLimit = price * maxFeeBps / 10000 and floors disputePeriodDurationInMS against the on-chain minimum. Backwards-compatible: protocolConfig is an optional arg, so the example's existing unit tests still pass without changes.

Out of scope (deferred to follow-up PRs)

  • A3–A5 (per-strategy erc3009 / permit / permit2 token-auth)
  • B5 escalateDispute (DR-deposit handling)
  • B8 revokeVoucher (SellerActor meta-tx signing)
  • B9 decideDispute (ResolverActor wallet signing)
  • C/D/E scenarios (validation negatives, nextActions assertions, multi-party)

Test plan

  • pnpm build — workspace builds clean
  • pnpm test — offline unit tests pass; scenario tests skip cleanly without E2E_DOCKER=1
  • pnpm lint + pnpm format:check — clean
  • E2E_DOCKER=1 pnpm --filter @bosonprotocol/x402-e2e test — all enabled scenarios green end-to-end against the local Boson stack

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Per-request session IDs with a shared session header export.
    • Action-aware meta-transaction typed-data and signer recovery.
    • Protocol-driven offer parameters with on-chain config fetch and session-scoped payment-requirements cache.
    • New e2e harnesses: seeded seed-wallet slots, funded-buyer helper, buyer/facilitator post-commit helpers, facilitator voucher actions, seller resolution signing, and safe-stringify utility.
    • Client token-auth deadline utility; improved Docker multi-stage build caching.
  • Bug Fixes

    • Relayer nonce management to prevent concurrent nonce races.
  • Tests

    • Expanded e2e scenarios, additional accounts, concurrency/load tests, and optional sequential debug mode.

Review Change Stack

Adds the B1-B4, B6, B7 post-commit lifecycle scenarios (redeem,
complete, raiseDispute, mutual resolveDispute, retractDispute,
cancelVoucher) and A2 (atomic commit-and-redeem) to the e2e suite.

Harness extensions:
 - SellerActor.signResolutionProposal — produces the counterpartySig
   the buyer needs for mutual resolveDispute, routed through
   coreSdk.signDisputeResolutionProposal with a forwarding adapter.
 - performBuyerPostCommitAction — wraps signAction + POST to the
   /x402B/{redeem,complete,dispute/*} routes mounted by server-express.
 - performCancelVoucher — reaches the facilitator's /perform-action
   directly, since server-express does not mount /cancel.
 - BuyerActor accepts an optional policy override (needed by A2's
   redeemMode: "commit-and-redeem").

PR6 cleanups:
 - Subgraph reader now takes a viem PublicClient and waits on
   coreSdk.waitForGraphNodeIndexing(blockNumber) when getExchangeById
   returns null, instead of relying solely on the outer poll loop.
 - Resource-server offer.ts tightens feeLimit against
   ConfigHandlerFacet.getMaxTotalOfferFeePercentage() and floors
   disputePeriodDurationInMS against getMinDisputePeriod(). Fetched
   once at boot via fetchProtocolConfig and threaded as an optional
   protocolConfig arg, keeping the example's unit tests stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9c348ced-ac95-464f-9b44-22a6e404883f

📥 Commits

Reviewing files that changed from the base of the PR and between f0d7554 and 7b92b76.

📒 Files selected for processing (1)
  • typescript/packages/x402-e2e/README.md

📝 Walkthrough

Walkthrough

Adds action-aware meta-tx typed-data/recovery, fetches on-chain protocol config and threads it into offer construction, replaces global cached offers with session-scoped caching keyed by session header, stamps client session IDs, uses viem nonceManager for the relayer, centralizes token-auth deadline logic, and expands E2E seed-wallets, harnesses, readiness gating, and tests.

Changes

Action-aware EIP-712 meta-transaction infrastructure

Layer / File(s) Summary
Action-specific EIP-712 typed-data builders
typescript/packages/core/src/eip712/meta-transaction.ts
Adds ActionMetaTransactionTypedData and builders (metaTransactionExchangeTypedData, metaTransactionDisputeResolutionTypedData, metaTransactionFundTypedData) plus helpers that call core-sdk and optionally override functionName.
Action-aware signer recovery
typescript/packages/facilitator/src/verify/meta-tx-signature.ts
Adds recoverActionMetaTxSigner, refactors recoverMetaTxSigner to use checkV and recoverFromTypedData, and implements ABI decoders for action calldata.
Test fixture alignment
typescript/packages/facilitator/test/perform-action.test.ts
Updates test fixture to dispatch typed-data construction per action (buildActionTypedData) and import action-family builders.
Perform-action integration
typescript/packages/facilitator/src/perform-action/index.ts
Calls recoverActionMetaTxSigner (instead of generic recovery) and passes input.action for verification.

Resource server protocol configuration

Layer / File(s) Summary
On-chain protocol configuration
examples/resource-server/src/protocol-config.ts, examples/resource-server/src/index.ts
Adds ProtocolConfig types and fetchProtocolConfig() that reads getMaxTotalOfferFeePercentage and getMinDisputePeriod on-chain and converts to usable shapes; re-exports from the example index.
Offer construction from protocol
examples/resource-server/src/offer.ts
buildUnsignedOffer accepts optional protocolConfig and sessionId, derives disputePeriodDurationInMS and feeLimit from protocol values when provided, widens validity windows, and salts metadata by session.
Session-scoped offer caching
examples/resource-server/src/app.ts
Replaces global cached requirements with a per-session in-memory Map keyed by SESSION_ID_HEADER, storing in-flight Promise entries with TTL and bounded size; normalizes session ids and threads protocolConfig into offer construction.

Session tracking & payment flow control

Layer / File(s) Summary
Client-side session ID support
typescript/packages/client-fetch/src/wrap.ts, typescript/packages/client-fetch/src/index.ts, typescript/packages/client-fetch/package.json
Adds SESSION_ID_HEADER and newSessionId() (uses globalThis.crypto.randomUUID()), stamps the header on initial and retry requests so retries carry the same session id, re-exports the header, and adds @bosonprotocol/x402-core dependency.
Server middleware flow detection
typescript/packages/server-express/src/middleware.ts
Adds detectFlowFromHeader to decode X-PAYMENT header and route between commit and commit-and-redeem; honors strict opts.flow when provided and defaults to commit on header decode failure.
Relayer nonce management
examples/facilitator-http/src/config.ts
Creates relayer account with privateKeyToAccount(..., { nonceManager }) to serialize executeMetaTransaction submissions and avoid nonce races.

Client token-auth deadline

Layer / File(s) Summary
Deadline helper and call-sites
typescript/packages/client/src/token-auth/deadline.ts, erc3009.ts, permit.ts, permit2.ts
Adds computeTokenAuthDeadline with a safety margin and updates ERC-3009, permit, and permit2 signing code to use it, clamping tiny timeouts to avoid retroactive expired deadlines.

Core header and package exports

Layer / File(s) Summary
Core SESSION_ID_HEADER and root export
typescript/packages/core/src/headers.ts, typescript/packages/core/src/index.ts
Adds SESSION_ID_HEADER constant and re-exports it from the package root.
wrapFetchWithPayment & package deps
typescript/packages/client-fetch/src/wrap.ts, typescript/packages/client-fetch/package.json, typescript/packages/client-fetch/src/index.ts
Generates per-invocation session IDs, stamps SESSION_ID_HEADER before cloning/retry, re-exports the header, and adds a workspace dependency on @bosonprotocol/x402-core.

E2E test infrastructure

Layer / File(s) Summary
Seed wallet slots and extra accounts
typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts, typescript/packages/x402-e2e/src/config/accounts.ts
Introduces per-file seed-wallet slots mapped to viem LocalAccount, SeedWalletName type, SELLERS_ENV_KEY, getSellerInfo, and adds ACCOUNT_10ACCOUNT_15.
Global setup with per-slot sellers & mining
typescript/packages/x402-e2e/test/setup/globalSetup.ts
Refactors globalSetup to seed one seller per slot, writes per-slot { id,address } JSON to env, enables interval mining, and supports E2E_DOCKER_KEEP_STACK to preserve or teardown the Docker stack.
Scenario context & resource-server boot
typescript/packages/x402-e2e/test/scenarios/_setup.ts, typescript/packages/x402-e2e/src/bin/resource-server.ts
Require slot-driven scenario context, inject publicClient into SubgraphExchangeReader for indexer-wait, fetch protocolConfig, and refactor resource-server entry to main() with escrow/config readiness polling before server start.
Buyer/seller harnesses & post-commit helpers
typescript/packages/x402-e2e/src/harness/*, typescript/packages/x402-e2e/test/scenarios/_buyer-setup.ts
Add createFundedBuyer, BuyerActor policy override, SubgraphExchangeReader publicClient option, buyer post-commit HTTP harness, facilitator-perform-action harness, and SellerActor.signResolutionProposal (forwarding adapter).
Scenarios, tests, Vitest config, and docs
typescript/packages/x402-e2e/test/scenarios/*, vitest.config.ts, README.md, CLAUDE.md
Implement commit A2 and post-commit B1–B4/B6–B7 tests, add concurrent load test (flagged), add assertion constants and helper tests, add createFundedBuyer usage across tests, and document E2E_SEQUENTIAL=1 and rebuild guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"I stamp each session with a hop and hum,
I sign the typed-data when day is done,
I cache the offers, mind the nonce in line,
I fetch configs, keep deadlines fine,
I seed the tests — the garden’s snug and won."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-x402-e2e-post-commit-lifecycle

@levalleux-ludo levalleux-ludo marked this pull request as ready for review May 20, 2026 17:46
Copilot AI review requested due to automatic review settings May 20, 2026 17:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the @bosonprotocol/x402-e2e suite with runnable commit/post-commit lifecycle scenarios and strengthens determinism/robustness in the local-stack harness by isolating chain-touching tests and tightening offer/config handling across the example resource server and facilitator.

Changes:

  • Adds runnable e2e scenarios for atomic commit-and-redeem (A2) and buyer-driven post-commit lifecycle transitions (B1–B4, B6, B7).
  • Introduces per-test-file seed-wallet slots + multi-seller seeding to make cross-file parallelism safer (plus an E2E_SEQUENTIAL=1 escape hatch).
  • Improves robustness of on-chain/subgraph interactions and meta-tx signature recovery (subgraph indexing waits, typed-data variant dispatch, relayer nonce management, and example-server protocol-config + offer caching updates).

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
typescript/packages/x402-e2e/vitest.config.ts Adds E2E_SEQUENTIAL option to disable cross-file parallelism for debugging.
typescript/packages/x402-e2e/test/setup/globalSetup.ts Seeds one seller per seed-wallet slot and resets the stack defensively before boot.
typescript/packages/x402-e2e/test/scenarios/post-commit.test.ts New runnable post-commit lifecycle scenarios (B1–B4, B6, B7).
typescript/packages/x402-e2e/test/scenarios/commit.test.ts Promotes A2 to runnable (atomic commit-and-redeem) and switches to per-file seed wallet + funded random buyer.
typescript/packages/x402-e2e/test/scenarios/_skeletons.test.ts Removes moved post-commit todos and leaves remaining follow-ups parked.
typescript/packages/x402-e2e/test/scenarios/_setup.ts Requires per-file slot + buyer account; fetches on-chain protocol config for in-process resource server.
typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts Introduces named per-file seed-wallet slots and seller-info lookup via env JSON map.
typescript/packages/x402-e2e/test/scenarios/_buyer-setup.ts Adds createFundedBuyer helper for per-describe isolated buyer EOAs.
typescript/packages/x402-e2e/test/harness/actors.test.ts Adds unit coverage for SellerActor.signResolutionProposal.
typescript/packages/x402-e2e/src/harness/seller-actor.ts Adds signResolutionProposal via core-sdk typed-data signing path.
typescript/packages/x402-e2e/src/harness/post-commit-http.ts Adds helper to sign+POST buyer post-commit actions to resource server routes.
typescript/packages/x402-e2e/src/harness/index.ts Re-exports new post-commit and facilitator-perform helpers.
typescript/packages/x402-e2e/src/harness/facilitator-perform-action.ts Adds facilitator-direct cancelVoucher helper (and placeholder revoke).
typescript/packages/x402-e2e/src/harness/exchange-reader.ts Adds optional PublicClient and waits for subgraph indexing vs chain head before reads.
typescript/packages/x402-e2e/src/harness/buyer-actor.ts Adds optional policy override to drive atomic commit-and-redeem.
typescript/packages/x402-e2e/src/config/accounts.ts Adds additional test accounts (ACCOUNT_10–ACCOUNT_15).
typescript/packages/x402-e2e/src/bin/resource-server.ts Hardens boot: waits for Diamond code + ConfigHandlerFacet init; passes protocolConfig into app.
typescript/packages/x402-e2e/README.md Documents seed-wallet slots + sequential fallback and updates account range.
typescript/packages/server-express/src/middleware.ts Auto-detects commit flow from X-PAYMENT action when flow isn’t pinned.
typescript/packages/facilitator/test/perform-action.test.ts Updates test signing fixture to mirror typed-data variants per action family.
typescript/packages/facilitator/src/verify/meta-tx-signature.ts Adds action-aware typed-data reconstruction and signature recovery for post-commit actions.
typescript/packages/facilitator/src/perform-action/index.ts Switches perform-action signature recovery to the new action-aware recovery function.
typescript/packages/core/src/eip712/meta-transaction.ts Adds typed-data builders for action-specific meta-tx primary types (exchange/dispute/fund).
typescript/packages/client-fetch/src/wrap.ts Adds per-flow session header to scope resource-server FullOffer caching across 402+retry.
typescript/packages/client-fetch/src/index.ts Exports SESSION_ID_HEADER.
examples/resource-server/src/protocol-config.ts Adds fetchProtocolConfig to read on-chain fee/dispute-period bounds.
examples/resource-server/src/offer.ts Uses optional protocolConfig to tighten feeLimit and floor dispute period.
examples/resource-server/src/index.ts Re-exports fetchProtocolConfig (+ type) for integrators.
examples/resource-server/src/app.ts Switches requirements cache from time-based singleton to per-session keyed cache.
examples/facilitator-http/src/config.ts Wraps relayer account with viem nonceManager to serialize concurrent submissions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread examples/resource-server/src/app.ts Outdated
Comment thread examples/resource-server/src/protocol-config.ts
Comment thread typescript/packages/x402-e2e/vitest.config.ts Outdated
Comment thread typescript/packages/x402-e2e/README.md Outdated
Comment thread typescript/packages/x402-e2e/test/scenarios/_buyer-setup.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/resource-server/src/app.ts`:
- Around line 106-112: SESSION_ID_HEADER ("x-x402-boson-session-id") is
duplicated elsewhere and sessionCache (Map with SESSION_CACHE_TTL_MS and
FALLBACK_KEY) can grow unbounded; extract the header constant into a shared
module (e.g., x402-core) and import it into this file instead of redefining
SESSION_ID_HEADER, and either implement periodic cleanup for sessionCache (a
timer that scans and deletes expired entries) or swap the Map for an LRU cache
implementation so expired entries are evicted automatically; update references
to SESSION_ID_HEADER, SESSION_CACHE_TTL_MS, sessionCache, and FALLBACK_KEY
accordingly.

In `@typescript/packages/facilitator/src/verify/meta-tx-signature.ts`:
- Around line 243-270: decodeExchangeIdArg currently decodes the calldata but
doesn't verify decoded.functionName, allowing mismatched calldata (e.g.,
withdrawFunds) that still has a uint256 first arg; update decodeExchangeIdArg to
check decoded.functionName is one of the expected exchange-keyed function names
(same whitelist used by decodeResolveDisputeArgs / decodeWithdrawFundsArgs), and
return a DecodeFailure (code "BAD_META_TX_SIGNATURE") if it is not, before
validating the first arg type and returning { ok: true, value }. Ensure you
reference decoded.functionName and ACTION_ARGS_ABI within decodeExchangeIdArg
when adding this validation.

In `@typescript/packages/x402-e2e/src/harness/facilitator-perform-action.ts`:
- Around line 24-25: The type FacilitatorOnlyActionId currently advertises
"boson-revokeVoucher" as supported but the exported facilitator handler (the
exported function in the file that always throws around lines 95-101) is not
implemented; remove "boson-revokeVoucher" from FacilitatorOnlyActionId (and any
related export lists) or delete its entry from the exported actions surface, and
either remove the exported throwing handler or implement it before re-adding the
action; ensure only implemented actions are listed so callers cannot select the
unwired "boson-revokeVoucher".

In `@typescript/packages/x402-e2e/src/harness/post-commit-http.ts`:
- Around line 192-198: The safeStringify function is duplicated; extract the
function into a shared harness module (e.g., http-error-utils) and export it,
then replace the local safeStringify implementations in both modules with an
import of the shared function; update references to safeStringify in the modules
that currently define it (the post-commit-http and facilitator-perform-action
modules) to import from the new shared harness module and remove the duplicated
local definitions.

In `@typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts`:
- Around line 93-108: The JSON.parse call in getSellerInfo can throw a
SyntaxError with little context; wrap the JSON.parse(raw) into a try/catch
inside getSellerInfo (when reading process.env[SELLERS_ENV_KEY]) and on catch
throw a new Error that includes SELLERS_ENV_KEY, the raw value length/preview if
helpful, and the original error.message (or error.stack) so diagnostics clearly
indicate malformed JSON and where it came from; keep parsing into SellerInfoMap
and then proceed with the existing lookup of all[slot].

In `@typescript/packages/x402-e2e/test/scenarios/commit.test.ts`:
- Around line 119-131: The test uses duplicated literals (the txHash regex and
the price "1000000") in multiple scenarios; create a shared module (e.g.,
_assertion-constants.ts) that exports TX_HASH_REGEX and EXPECTED_PRICE, update
this test to import TX_HASH_REGEX and EXPECTED_PRICE and replace the inline
/^0x[0-9a-fA-F]+$/ and "1000000" occurrences in the assertions (the
readXPaymentResponse check and the ctx.asserter.expect call for exchangeId /
price), and update other scenario tests to import the same constants so the
regex and price are centralized.

In `@typescript/packages/x402-e2e/test/scenarios/post-commit.test.ts`:
- Around line 21-23: Update the high-level test description text that currently
reads "local stack (Diamond, subgraph, facilitator)" to use the user-facing term
"escrow" instead of "Diamond" (e.g., "local stack (escrow, subgraph,
facilitator)") so the scenario description uses escrow terminology; leave
internal/deep-link references alone if they specifically target Boson
facets/contracts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 99f71f13-9af1-4a1c-87e9-573273b0f19c

📥 Commits

Reviewing files that changed from the base of the PR and between 08e05f5 and 1ad29f0.

📒 Files selected for processing (30)
  • examples/facilitator-http/src/config.ts
  • examples/resource-server/src/app.ts
  • examples/resource-server/src/index.ts
  • examples/resource-server/src/offer.ts
  • examples/resource-server/src/protocol-config.ts
  • typescript/packages/client-fetch/src/index.ts
  • typescript/packages/client-fetch/src/wrap.ts
  • typescript/packages/core/src/eip712/meta-transaction.ts
  • typescript/packages/facilitator/src/perform-action/index.ts
  • typescript/packages/facilitator/src/verify/meta-tx-signature.ts
  • typescript/packages/facilitator/test/perform-action.test.ts
  • typescript/packages/server-express/src/middleware.ts
  • typescript/packages/x402-e2e/README.md
  • typescript/packages/x402-e2e/src/bin/resource-server.ts
  • typescript/packages/x402-e2e/src/config/accounts.ts
  • typescript/packages/x402-e2e/src/harness/buyer-actor.ts
  • typescript/packages/x402-e2e/src/harness/exchange-reader.ts
  • typescript/packages/x402-e2e/src/harness/facilitator-perform-action.ts
  • typescript/packages/x402-e2e/src/harness/index.ts
  • typescript/packages/x402-e2e/src/harness/post-commit-http.ts
  • typescript/packages/x402-e2e/src/harness/seller-actor.ts
  • typescript/packages/x402-e2e/test/harness/actors.test.ts
  • typescript/packages/x402-e2e/test/scenarios/_buyer-setup.ts
  • typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts
  • typescript/packages/x402-e2e/test/scenarios/_setup.ts
  • typescript/packages/x402-e2e/test/scenarios/_skeletons.test.ts
  • typescript/packages/x402-e2e/test/scenarios/commit.test.ts
  • typescript/packages/x402-e2e/test/scenarios/post-commit.test.ts
  • typescript/packages/x402-e2e/test/setup/globalSetup.ts
  • typescript/packages/x402-e2e/vitest.config.ts

Comment thread examples/resource-server/src/app.ts Outdated
Comment thread typescript/packages/facilitator/src/verify/meta-tx-signature.ts
Comment thread typescript/packages/x402-e2e/src/harness/post-commit-http.ts Outdated
Comment thread typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts
Comment thread typescript/packages/x402-e2e/test/scenarios/commit.test.ts
Comment thread typescript/packages/x402-e2e/test/scenarios/post-commit.test.ts
levalleux-ludo and others added 4 commits May 21, 2026 09:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@typescript/packages/client/src/token-auth/deadline.ts`:
- Around line 19-24: computeTokenAuthDeadline currently always subtracts
TOKEN_AUTH_DELINE_SAFETY_MARGIN_SECONDS which can make the deadline be in the
past when maxTimeoutSeconds is smaller than the margin; modify
computeTokenAuthDeadline to clamp the safety margin to at most maxTimeoutSeconds
- 1 (and never below 0) before subtracting so very short timeouts still produce
a future expiry. Update the function computeTokenAuthDeadline to compute an
effectiveMargin = Math.min(TOKEN_AUTH_DEADLINE_SAFETY_MARGIN_SECONDS,
Math.max(0, maxTimeoutSeconds - 1)) and subtract effectiveMargin instead of the
raw TOKEN_AUTH_DEADLINE_SAFETY_MARGIN_SECONDS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e2bea324-c168-478f-8462-36d0237bc7e9

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad29f0 and 916ecb4.

📒 Files selected for processing (7)
  • examples/resource-server/src/app.ts
  • typescript/packages/client/src/token-auth/deadline.ts
  • typescript/packages/client/src/token-auth/erc3009.ts
  • typescript/packages/client/src/token-auth/permit.ts
  • typescript/packages/client/src/token-auth/permit2.ts
  • typescript/packages/x402-e2e/README.md
  • typescript/packages/x402-e2e/vitest.config.ts

Comment thread typescript/packages/client/src/token-auth/deadline.ts
levalleux-ludo and others added 7 commits May 21, 2026 09:41
The fixed 60s subtraction in `computeTokenAuthDeadline` could push the
returned deadline into the past when `maxTimeoutSeconds < 60`, causing
token-auth signatures to expire before they reach the facilitator.

Clamp the effective margin to `Math.min(60, max(0, timeoutSeconds - 1))`
so short timeouts still produce a strictly-future deadline.

Addresses PR #76 review comment r3279645435.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`getMinDisputePeriod()` returns a `uint256` in seconds; converting
straight through `Number(...) * 1000` could silently lose precision or
overflow past `MAX_SAFE_INTEGER / 1000`.

Range-check the `bigint` against `MAX_SAFE_INTEGER / 1000` before
converting, and throw a clear `RangeError` rather than emitting a wrong
`minDisputePeriodMs`. `maxFeeBps` stays a plain `Number` — it's a
`uint16` (max 65535), always safe.

Addresses PR #76 review comment r3276008292.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`WalletClient` doesn't require `account` / `chain` at the type level, so
the previous non-null assertions would crash with an opaque viem error
if a caller passed a bare client.

Replace the assertions with an explicit guard that throws a clear
harness-side message pointing at the right helper
(`buildWalletClient(SEED_WALLETS.<slot>.account)`).

Addresses PR #76 review comment r3276008400.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`SESSION_ID_HEADER` was declared in both `@bosonprotocol/x402-client-fetch`
(canonical mixed case) and the example resource server (lowercase), so
the wire contract for per-buyer-flow offer caching was double-sourced.

Move the canonical declaration into `@bosonprotocol/x402-core` (the
package both buyer-side and server-side packages already orbit around),
re-export it from `client-fetch` for backwards compatibility, and have
the example resource server import the same constant. Express's
`req.header()` is case-insensitive, so the canonical mixed-case value
works for the server-side lookup.

Addresses PR #76 review comment r3276010028 (header-duplication half).
The unbounded-cache half called out by the same comment was already
addressed earlier on this branch via the 256-entry LRU cap + per-call
expired-entry pruning.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`decodeExchangeIdArg` decoded calldata against the full `ACTION_ARGS_ABI`
but didn't check which function the calldata actually encoded. A
malformed `metaTx.functionSignature` encoding e.g. `withdrawFunds`
would decode successfully as long as the first argument happened to be
a `uint256` — leading to subtle mismatches when the reconstructed
typed-data didn't reflect the action the signer actually approved.

Whitelist the six exchange-keyed function names (`redeemVoucher`,
`cancelVoucher`, `completeExchange`, `raiseDispute`, `retractDispute`,
`escalateDispute`) and reject anything else with the existing
`BAD_META_TX_SIGNATURE` envelope. Mirrors the validation already done
by sibling `decodeResolveDisputeArgs` / `decodeWithdrawFundsArgs`.

Addresses PR #76 review comment r3276010031.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`safeStringify` was duplicated between `post-commit-http.ts` and
`facilitator-perform-action.ts`. Move it into a shared
`harness/http-error-utils.ts` and import from both call sites so the
two implementations can't drift.

Addresses PR #76 review comment r3276010036.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tants

The tx-hash regex (`/^0x[0-9a-fA-F]+$/`) and the default commit price
(`"1000000"`) were repeated across `commit.test.ts` and
`post-commit.test.ts` (and elsewhere). Pull them into
`test/scenarios/_assertion-constants.ts` and import from both scenario
files so they can't drift independently.

Harness-level callers (`asserters.test.ts`, `_setup.ts`) keep their
literals — the comment scope was the scenario files.

Addresses PR #76 review comment r3276010052.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 40 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread examples/resource-server/src/app.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@typescript/packages/x402-e2e/src/harness/http-error-utils.ts`:
- Around line 11-15: The safeStringify function can return undefined because
JSON.stringify can return undefined for top-level undefined; update
safeStringify so it always returns a string by capturing the result of
JSON.stringify(value) into a variable, checking for undefined (or falsy) and
returning a fallback string (e.g., "undefined" or String(value)) if
JSON.stringify returns undefined, while keeping the existing catch branch that
returns String(value); reference: safeStringify function in http-error-utils.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dfac60aa-6640-4dae-b944-5a5391471f87

📥 Commits

Reviewing files that changed from the base of the PR and between 197b834 and 3b8adfb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • examples/resource-server/src/app.ts
  • typescript/packages/client-fetch/package.json
  • typescript/packages/client-fetch/src/wrap.ts
  • typescript/packages/core/src/headers.ts
  • typescript/packages/core/src/index.ts
  • typescript/packages/facilitator/src/verify/meta-tx-signature.ts
  • typescript/packages/x402-e2e/src/harness/facilitator-perform-action.ts
  • typescript/packages/x402-e2e/src/harness/http-error-utils.ts
  • typescript/packages/x402-e2e/src/harness/post-commit-http.ts
  • typescript/packages/x402-e2e/test/scenarios/_assertion-constants.ts
  • typescript/packages/x402-e2e/test/scenarios/commit.test.ts
  • typescript/packages/x402-e2e/test/scenarios/post-commit.test.ts

Comment thread typescript/packages/x402-e2e/src/harness/http-error-utils.ts
levalleux-ludo and others added 5 commits May 21, 2026 10:21
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
`JSON.stringify(value)` returns `undefined` (not a string) for top-level
`undefined`, function, and symbol values without throwing, so the
success branch could silently violate the declared `: string` return
type and propagate `undefined` into error messages.

Capture the call's result and fall back to `String(value)` on `null` /
`undefined` so every code path returns a real string.

Addresses PR #76 review comment r3279980090.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restructure the three locally-built stack Dockerfiles (facilitator-http,
webhook-sink, resource-server) so `pnpm install` runs in a separate stage
keyed only on the lockfile + workspace manifests, with a shared BuildKit
pnpm-store cache mount for tarball reuse. Source-only edits now skip the
install layer entirely, and `--build` with no changes is a near-instant
all-cache-hit. Also document a "When to rebuild" table in the x402-e2e
README so the flag's effect on each service is explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without it, `pnpm install` inside the image ignores the repo-wide
settings (`auto-install-peers`, `strict-peer-dependencies`,
`shared-workspace-lockfile`) and can resolve a different dependency
graph than a local install. Including `.npmrc` in the same COPY as
the lockfile also makes the install layer correctly invalidate when
the config changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l buyers)

E0 — 20 fresh EOAs fire `fetch(/resource)` concurrently against an
in-process resource server, each going through atomic
commit-and-redeem. Surfaces concurrency regressions the existing
single-buyer scenarios can't catch (relayer nonceManager, offer
template signing, subgraph indexer under load).

Three upstream tweaks were needed to land the test green:

- example-resource-server: fold the per-request session id into
  `metadataUri` / `metadataHash` so concurrent buyers each get a
  distinct `predictedOfferId`. Without it, two requests within the
  same millisecond produce byte-identical `UnsignedFullOffer`
  structs and the second commit reverts `OfferSoldOut` on the
  single-quantity template.
- example-resource-server: widen offer + voucher validity windows
  from 1 h to 30 d (with 1 d backdate) so they tolerate Hardhat's
  ~20x chain-time acceleration under interval mining.
- x402-e2e/globalSetup: switch Hardhat to 50 ms interval mining
  right after `[stack] ready`. The default automine rejects
  concurrent meta-tx submissions from a single relayer with
  "transactions can't be queued when automining"; interval mining
  lets them queue and mine in batches.

Also adds an `E2E_DOCKER_KEEP_STACK=1` escape hatch on globalSetup
so individual scenario files can be re-run repeatedly against a
stack the maintainer brought up via `pnpm stack:up` — useful for
iterating without paying the full cold-start cost each run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@typescript/packages/x402-e2e/test/setup/globalSetup.ts`:
- Around line 136-138: The call to enableIntervalMining() is outside the guarded
setup block so if it throws the outer catch won't run and the non-KEEP_STACK
cleanup path can be skipped; move the console.log and await
enableIntervalMining() into the same try/guarded block where the rest of the
setup occurs (the block that checks KEEP_STACK and performs cleanup) so any
failure triggers the existing catch handling and cleanup logic; ensure you
reference and preserve the KEEP_STACK conditional and existing cleanup calls
while relocating enableIntervalMining() into the guarded setup in globalSetup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4bade2f9-2a9a-453f-81bf-1ffa703c3e77

📥 Commits

Reviewing files that changed from the base of the PR and between 9446e2a and c2ef3c4.

📒 Files selected for processing (6)
  • CLAUDE.md
  • examples/resource-server/src/app.ts
  • examples/resource-server/src/offer.ts
  • typescript/packages/x402-e2e/test/scenarios/_seed-wallets.ts
  • typescript/packages/x402-e2e/test/scenarios/concurrent.test.ts
  • typescript/packages/x402-e2e/test/setup/globalSetup.ts

Comment thread typescript/packages/x402-e2e/test/setup/globalSetup.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@typescript/packages/x402-e2e/README.md`:
- Around line 101-107: The table row that reads "Only docs, CI, or
`.dockerignore` | nothing" is incorrect because changing `.dockerignore` can
change Docker build context and often requires rebuilding images; update the
README table entry (the row containing `.dockerignore`) to state that
`.dockerignore` changes should trigger a `--build` and/or rebuilding of the
relevant local images (e.g., the three images referenced by
`x402b-facilitator-http`, `x402b-resource-server`, and `x402b-webhook-sink`)
instead of "nothing", and add a short note that changes to `.dockerignore`
commonly require rebuilding when Dockerfiles use `COPY . .`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 117de05d-937a-4e35-a943-41859c064a68

📥 Commits

Reviewing files that changed from the base of the PR and between 58bdffb and f0d7554.

📒 Files selected for processing (4)
  • examples/facilitator-http/Dockerfile
  • examples/webhook-sink/Dockerfile
  • typescript/packages/x402-e2e/README.md
  • typescript/packages/x402-e2e/src/bin/resource-server.Dockerfile

Comment thread typescript/packages/x402-e2e/README.md Outdated
`.dockerignore` shapes the build context, and all three local Dockerfiles
copy the full repo via `COPY . .`, so changes to it commonly require a
`--build` of `x402b-facilitator-http`, `x402b-resource-server`, and
`x402b-webhook-sink`. Split the row out of the "rebuild nothing" bucket
and add a note explaining the `COPY . .` interaction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@levalleux-ludo levalleux-ludo merged commit 68a3ef6 into main May 21, 2026
3 checks passed
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.

2 participants