Skip to content

Latest commit

 

History

History
388 lines (321 loc) · 24.1 KB

File metadata and controls

388 lines (321 loc) · 24.1 KB

Changelog

All notable changes to Wasit are recorded here. Versions follow Semantic Versioning: patch releases are fixes, minor releases add checks or features without breaking existing usage, major releases break something.

[0.6.0] — 2026-09-30

All three packages, versioned together as usual. The two x402 payment checks now prove what their names say: X402-06 verifies the settlement on-chain, and X402-07 forges only the signature. MPP-01 reads the newer form of the transfer event and stops blaming a target for a slow RPC. Node.js 22 is supported. No check was added or removed.

Results can change on upgrade. A target that serves without settling, reports a transaction that is not its settlement, or answers without a PAYMENT-RESPONSE header now fails X402-06. A target that decodes a payment without verifying its signature, or accepts a forged one with 201 or 204, now fails X402-07. In the other direction, an MPP payment to a muxed recipient no longer fails MPP-01, and an RPC that stops advancing now gives no verdict instead of a FAIL. Against Stellar's official reference paywall (stellar/x402-stellar), this build passes 7/7, with X402-06 verified on-chain.

Changed — X402-06 verifies settlement on-chain. It passed on any 2xx, which established that the target served the resource, not that the payment landed. It now reads the settlement from the PAYMENT-RESPONSE header and holds the reported transaction to the advertised terms on Stellar RPC, as MPP-01 does: one transfer from this run's payer to payTo, for amount of asset. A target that serves without settling, or reports a transaction that is not its settlement, now fails; against a server built to do exactly that, 0.5.0 passed and this build fails both variants. A settlement_pending response is reconciled on chain, as the spec directs. wasit test gains --rpc-url, and the MCP tool rpcUrl. Settlement verification moved to a shared module so both checks apply identical rules. If a run goes red on upgrade, the check got stronger, not the service worse.

Fixed — X402-07 corrupts only the signature. It overwrote the tail of the base64 envelope, which broke XDR decoding, so a target that decoded the envelope and never verified the signature still refused it and passed. It now flips one byte of the client's Soroban authorization-entry signature and leaves the rest of the transaction intact. Against a server that decodes but never verifies, 0.5.0 passed and this build fails; the x402.org facilitator now rejects at simulation (…_simulation_failed) rather than at decoding (…_malformed). Acceptance is now any 2xx, not only 200, so a target that answers 201 or 204 to a forged payment no longer passes. Two offline tests, each mutation-checked.

Fixed — MPP-01 reads CAP-67 transfer events. Since CAP-67, a SEP-41 transfer to a muxed address (M...) emits its data as a map { amount, to_muxed_id } instead of a bare i128, with the base address in the topic. MPP-01 read only the bare form, so a settlement to a muxed recipient would have been reported as no transfer at all. It now reads both, and matches an advertised muxed recipient on the base account and the id. Verified against a real testnet transfer to a muxed address; seven offline tests, each mutation-checked. Checking this end to end also showed that the official @stellar/mpp charge server cannot verify a payment to a muxed recipient either (its verification throws on the same map), so no MPP service built on it can accept one today.

Fixed — MPP-01 no longer blames the target for a slow RPC. It gave RPC twelve seconds to show the settled transaction, then reported it as never broadcast. On a lagging RPC that is a FAIL against a target that did nothing wrong. The wait is now measured in ledgers: the transaction is reported missing only once RPC has closed ten more ledgers without it, and an RPC that stops advancing gives no verdict (ERROR (harness)) instead of a FAIL. Four offline tests, each mutation-checked.

Fixed — MPP-01 no longer says it paid when the target refused. When the target answered with an error instead of the resource, the FAIL read "Paid the advertised … base units", although a target that refuses may never broadcast the transaction, and in the run that found this none was. The verdict is unchanged; the wording now claims only that a payment was submitted.

Changed — Node.js 22 is supported. All three packages now declare "node": ">=22" instead of >=24. Nothing required 24: the full test suite, the CLI, the MCP server and a run against the fixtures all pass on Node 22, and no runtime dependency requires more. Node 22 is still a maintained LTS, and CI environments such as the official MPP SDK's run on it. CI now tests the packages on both Node 22 and Node 24.

Fixed — wasit wallet status --role <role> exits 2 when it could not check that role. It exited 0 on an unreadable key, so wasit wallet status --role x402 && deploy went ahead on a key that was never checked, although docs/guides/cli.md already said a malformed key exits 2. With --role it now exits 2 whenever the role could not be checked: key not set, unreadable, or its balance lookup failed. An unfunded account is an answer and exits 0. Without --role, one unchecked role stays a row and the command exits 0. Three offline tests, each mutation-checked.

Added, repository only — scripts/run-all.sh. Starts the fixtures if they are not already up, runs the x402, MPP charge and MPP channel suites, prints one summary and stops the fixtures it started. Free checks by default; --full adds the checks that move testnet funds, and --npm runs the published CLI. Not part of any npm package.

[0.5.0] — 2026-09-28

All three packages, versioned together as usual. Two reporting fixes found by running Wasit against code it did not write, and one addition that brings the MCP server level with the CLI. No check was added or removed.

Results can change on upgrade, in two situations only: a target that issues an x402 v1 challenge (see below), and an MPP channel target that refuses a replay with a status other than 402 and 2xx, whose FAIL no longer calls it a double-spend. Against a conformant v2 target nothing changes: Wasit's own x402 fixture passes 7/7 with this build, as it did with 0.4.0.

Fixed — MPP-12 and MPP-14 no longer call a refused replay a double-spend. Both reported any non-402 response as "accepted twice ... This is a double-spend." The official SDK's channel server on main refuses replays with HTTP 500, so the check claimed a double-spend while the server had in fact refused. The verdict stays FAIL, since the required status is 402, but the double-spend wording now appears only for a 2xx. Found running against the SDK's own example servers; see docs/evidence/2026-09-24-official-sdk-reference-run.md.

Added — wasit_x402_test accepts method, body and headers. The CLI's --method, --body and --header have existed since 0.1.0, but the MCP tool could only send a GET, so an agent could not test a paid POST endpoint at all. The same request shape is applied to every probe, including X402-06 and X402-07. A body sent with GET or HEAD is refused as a configuration error. Header values travel through the agent's transcript, which the tool description and docs/guides/mcp.md say plainly.

Fixed — an x402 v1 challenge is read, and an unpayable challenge no longer fails the payment checks. Run with the operator's written authorization against a service that speaks x402 v1, 0.4.0 reported X402-02 FAIL, skipped X402-03–05, and reported X402-06 and X402-07 as FAIL although no payment was ever built or sent, so X402-07 read as a corrupted signature that was not rejected. docs/CHECKS.md already said a challenge that cannot be read has no verdict. Now X402-02 still fails, because the exact scheme on Stellar is defined for v2 only, but says a v1 challenge was found in the body; X402-03–05 inspect that body; and X402-06/X402-07 are skipped with the reason. The same skip applies to any challenge the payment client cannot read. Seven offline tests, each mutation-checked.

[0.4.0] — 2026-09-17

All three packages, versioned together as usual. Two correctness fixes, both in how a run reports what it actually established. Neither adds a check; both change what existing checks report in situations where the old answer was wrong.

Fixed — a charge-mode payment client no longer breaks every later channel-mode check. Mppx.create replaces globalThis.fetch with a wrapper bound to one payment method unless polyfill: false is passed. Once MPP-01 created a charge client, every later channel-mode 402 in the same process was refused by that wrapper with "No method found for challenges: stellar.channel. Available: stellar.charge", reported as a defect in a target that was in fact conformant. The CLI hid this because each invocation is a fresh process; the MCP server did not, because one process answers many tool calls in a row. Only the first payment-mode check in a process produced a trustworthy verdict. fetchTarget now resolves the underlying fetch through mppx's own Symbol.for("mppx.fetch.wrapper") tag, so a check observes the target rather than whatever a previous check installed on the global. packages/core's charge client also passes polyfill: false, which is necessary but not sufficient on its own: one stale build or one new caller brings the leak back. Regression test: test/unit/fetch-target-isolation.test.ts.

Changed — a failed setup is reported as no verdict, not as a defect. MPP-11, MPP-12 and MPP-14 each need one correctly advancing commitment accepted before they can probe anything. That submission used to be attempted once, and a refusal was reported as FAIL. Two very different worlds produce that refusal identically: a target that wrongly rejects valid vouchers, and a channel whose cumulative moved between the challenge being issued and the credential being submitted, because something else is paying through it. The submission is now retried up to three times, each against a freshly issued challenge, so ordinary contention clears on its own. When every attempt is refused the result is ERROR with the new error kind setup, carrying all three refusals verbatim, and the run exits 2 (no-verdict) instead of 1 (non-conformant).

Added — CheckSetupError and the setup error kind, exported from @wasit-dev/core. errorKind in --json and in the MCP server's structuredContent can now carry "setup" alongside unreachable, configuration and harness. A consumer that switches exhaustively on that field needs a branch for it.

Added — scripts/fixtures.sh, which starts, stops and reports on the four local fixture servers so a full local run needs one terminal instead of four. Also adds packages/core/test/fixtures/mpp-channel-refusing-server.ts, a target that issues valid challenges and refuses every credential, which reproduces a setup failure on demand rather than by racing two runs.

[0.3.0] — 2026-09-05

All three packages. Adds an interactive dashboard and testnet wallet tooling to the CLI, a wallet layer to core, and a runtime version lookup in the MCP server. All three are versioned together so they always resolve the same @wasit-dev/core, rather than the MCP server quietly running a version behind the CLI.

Package Version
@wasit-dev/core 0.2.0 → 0.3.0
@wasit-dev/cli 0.2.0 → 0.3.0
@wasit-dev/server 0.2.0 → 0.3.0

Added — an interactive dashboard. Running wasit with no arguments in a terminal opens a menu: the three check runners, a catalogue browser, and a testnet wallet screen, driven by arrow keys. A run shows a live elapsed timer and per-check progress, ends with total and average timing, and can be saved to wasit-<protocol>-<timestamp>.json with s — the same shape as --json. A misconfigured run gets its own error screen rather than a red line under an otherwise-empty checklist. Piped or in CI, wasit still prints help, so nothing that scripts it today changes behaviour.

Added — wasit wallet. status, create and fund for the testnet payer keys the other subcommands read from .env. There is deliberately no --network flag: Friendbot, the printed USDC issuer and the whole idea of a disposable generated key only make sense on testnet. XLM funding via Friendbot is fully automatic. USDC is not, and the tool says so rather than pretending otherwise: the trustline is created automatically, but a balance needs one manual visit to Circle's faucet or a configured WASIT_USDC_DISTRIBUTOR_SECRET, because no scriptable testnet USDC faucet exists for Stellar.

Changed — core classifies its own wallet failures. Every wallet function in core now throws the taxonomy in errors.ts (ConfigurationError, TargetUnreachableError) instead of leaking raw Stellar SDK errors, with Horizon's result codes (op_underfunded, op_no_trust) folded into the message. Callers render error.message and never inspect an SDK error type, which is what stopped the CLI and the dashboard reporting the same failure two different ways. Horizon and Friendbot requests are now bounded by a timeout; previously a stalled request had no way back.

Fixed — a malformed key in .env no longer kills the process. A truncated paste, a G... public key in a secret's slot, or a hex commitment seed in a Stellar-secret slot reached Keypair.fromSecret unguarded. In the dashboard that escaped as an unhandled rejection and terminated the process from under Ink's renderer, leaving the wallet screen frozen on its loading spinner; from wasit wallet it printed an SDK stack trace instead of the CLI's own error contract. All three now report the offending variable by name and exit 2, and the message never echoes the rejected value.

Fixed — wasit wallet status --role mpp-channel is rejected up front. COMMITMENT_SECRET_HEX is a raw hex seed with no on-chain account, which the help text and docs already said; the command accepted the role anyway and then crashed deriving an address for it.

Fixed — .env written by the dashboard is owner-only and atomic. The confirm-to-save flow wrote with Node's default permissions, which under a typical umask produces a world-readable 0644 file holding Stellar secrets. It is now written 0600 via a temp file and a rename, so an interrupted write cannot truncate the file, and an existing world-readable .env is tightened on the next write.

Fixed — Friendbot no longer claims a transfer that did not happen. A "this account already exists" response is treated as success, correctly, but every caller reported it as "Funded: 10,000 XLM." regardless.

Added — releases are verified from a clean install. npm run verify:clean-install packs the three packages, installs the tarballs into an empty project, and drives the result as a user would — the CLI's own binary and the MCP server over stdio, with no keys and no target. Everything else in CI runs against the working tree, where npm has deduplicated one dependency graph across all three workspaces; that is not the tree an installer gets, which is how 0.1.1 shipped without the checks subcommand its own docs described. Now part of CI. See #2.

Fixed — the MCP server reported the wrong version. It announced 0.1.0 in the initialize handshake through two releases, because the string was hardcoded. It now reads the package's own manifest at runtime, the same way wasit --version does, so a client cannot report a bug against a version that was never published.

Fixed — quitting the dashboard restores the terminal. q called process.exit directly, skipping Ink's unmount and leaving the cursor hidden. Ctrl+C still exits 130 immediately, by design.

[0.2.0] — 2026-09-04

All three packages. Adds a machine-readable surface to every check runner, and brings each package's declared dependencies in line with what its code actually imports.

Package Version
@wasit-dev/core 0.1.1 → 0.2.0
@wasit-dev/cli 0.1.1 → 0.2.0
@wasit-dev/server 0.1.2 → 0.2.0

Added — wasit checks. Lists every check the tool can run, grouped by protocol and annotated with the subcommand that runs it, plus flags for negative, destructive and funds-spending checks. --protocol narrows it to one suite. The catalogue behind it is exported from core as CHECK_CATALOGUE and stays a short-form companion to docs/CHECKS.md, which remains the source of truth for pass criteria and spec citations.

Added — --json on every check runner. test, mpp-charge, mpp-channel and checks all take it. A run emits outcome, per-status counts, and a results array carrying each check's id, name, status, detail, destructive flag and — when a check could not run — its errorKind. Human-readable output goes to stderr when --json is set, so stdout stays parseable.

Changed — MCP structuredContent is now the same reshape as --json. @wasit-dev/server builds it from core's toStructuredRun() verbatim instead of assembling its own object, so the CLI and an agent can no longer report the same run in two different shapes. Anything parsing the old structuredContent should re-read it: the field set is close, but it is no longer hand-built here.

Changed — dependencies now match imports. @wasit-dev/core dropped @x402/core, @x402/express, commander and dotenv from its runtime dependencies; none are imported by its source, and the first two are only used by the bundled fixtures, which are not published. express moved in as a devDependency — the x402 fixture imports it directly but it had never been declared anywhere, resolving only because npm happened to hoist it from a deeper transitive dependency. @wasit-dev/cli dropped @stellar/mpp, @stellar/stellar-sdk and mppx, which it never imports, and its commander and dotenv ranges now name the versions the code is actually built and verified against rather than two majors behind.

Known issue — a clean install still resolves two Stellar SDKs. @stellar/mpp@0.7.1 declares the peers @stellar/stellar-sdk@^15.1.0 and mppx@^0.6.29; Wasit uses ^16.1.0 and ^0.8.14, outside both ranges. A fresh npm install satisfies that peer by nesting an older SDK alongside the one Wasit uses, and that older SDK carries open axios advisories with no fix available upstream. A repository checkout dedupes to a single SDK instead, so npm audit reports differently depending on which tree you are in — meaning the configuration Wasit is developed against is not the one its users get. Being reported upstream; nothing in this release changes that chain.

2026-09-01 — READMEs

README only, no code changes. Each package now ships a README.md (previously absent, so the npm listing page for all three was empty). @wasit-dev/cli and @wasit-dev/server also pick up a fresh build — their published dist/ predated some already-merged source changes.

Package Version
@wasit-dev/core 0.1.0 → 0.1.1
@wasit-dev/cli 0.1.0 → 0.1.1
@wasit-dev/server 0.1.1 → 0.1.2

[0.1.1] — 2026-09-01

@wasit-dev/server only. core and cli are unchanged and stay at 0.1.0 — only the package that actually changed gets a version bump.

Fixed: the wasit://checks MCP resource located docs/CHECKS.md by walking up from the server's own installed location, which found it in a local git checkout but not in an npx @wasit-dev/server install with no repo present. The resource was silently absent rather than erroring. docs/CHECKS.md is now copied into the package at publish time (a prepack script) and shipped under files, so it resolves the same way whether Wasit is run from a checkout or installed straight from npm. The four test tools were never affected by this — only the check catalogue resource was.

[0.1.0] — 2026-09-01

First public release. All three packages are now live on npm under the @wasit-dev organization.

Package Version npm What it is
@wasit-dev/core 0.1.0 https://www.npmjs.com/package/@wasit-dev/core The check-suite library. x402 and MPP conformance logic, on-chain settlement verification, no CLI or MCP dependency of its own.
@wasit-dev/cli 0.1.0 https://www.npmjs.com/package/@wasit-dev/cli The wasit command. Thin adapter over @wasit-dev/core for running checks from a terminal or CI.
@wasit-dev/server 0.1.0 https://www.npmjs.com/package/@wasit-dev/server The wasit-mcp command. Exposes the same checks as MCP tools so an agent (Claude or otherwise) can run them directly.

Anyone can now install and run Wasit without cloning the repository:

npm install -g @wasit-dev/cli
wasit test <target-url>

or wire the MCP server into an agent:

npx @wasit-dev/server

What Wasit checks in this release

x402. Seven checks (X402-01 through X402-07) covering the full flow: the 402 status itself, the payment header, its base64/JSON payload, the required fields inside that payload (version-aware, since v1 and v2 use different field names for price), the CAIP-2 network identifier, a real signed payment that must be accepted, and a deliberately corrupted signature that must be rejected. The two payment checks settle real testnet funds and are skipped automatically when no payer key is configured, or explicitly with --read-only.

MPP, charge mode. One check (MPP-01) that pays the target and then verifies the settlement independently on-chain, by reading the token contract's own CAP-46 transfer event via Stellar RPC rather than trusting the response the target returns. This catches a target whose response claims a payment succeeded when the on-chain event says otherwise.

MPP, channel mode. Five checks (MPP-10, MPP-11, MPP-12, MPP-14, and MPP-13). The first four are non-destructive: channel deployment and state, cumulative-commitment ordering, challenge replay rejection, and commitment replay rejection against a fresh challenge — the double-spend case the official client SDK cannot even express, since it always re-signs. MPP-13 verifies that closing a channel actually settles on-chain and permanently ends the channel, so it is gated behind an explicit opt-in (--allow-destructive on the CLI, WASIT_ALLOW_DESTRUCTIVE=1 on the MCP server) and refuses to run unless the operator names the exact channel being closed.

Reporting. Every run distinguishes four outcomes rather than a simple pass/fail: PASS, FAIL (the target answered and didn't conform), SKIP (a check that legitimately didn't apply, e.g. a destructive check that wasn't opted into), and ERROR (no verdict at all, because the target was unreachable or misconfigured — never treated as a finding about the target). CLI exit codes follow the same distinction: 0 clean, 1 at least one real conformance failure, 2 at least one check produced no verdict.

Interfaces. Both the CLI and the MCP server run the identical check code in @wasit-dev/core, so the two can never disagree about the same target. MCP exposes four tools: wasit_x402_test, wasit_mpp_charge_test, wasit_mpp_channel_test (non-destructive channel checks, with MPP-13 reported as SKIP), and wasit_mpp_channel_test_with_close (registered only when destructive mode is explicitly enabled at server startup).

Known gaps going into the next release

Two items from the original SOW scope are still open. A completion-summary/demo video has not been started. Third-party validation — at least one service outside this project actually running Wasit against itself with explicit authorization — is in progress via outreach to teams building on x402/MPP (RouteDock, stellarpay, stellar-pay), with no confirmed run back yet as of this release.

Two defects were found in the upstream @stellar/mpp SDK while building the channel-mode checks and were filed against stellar/stellar-mpp-sdk: issue #66 (channel-mode rejections collapse to one generic error instead of the SDK's own precise error taxonomy) and issue #67 (feePayer.envelopeSigner names the wrong concept and produces [object Object] on a mismatched key). Both are documented in docs/findings/upstream-sdk.md.