Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .npmrc

This file was deleted.

131 changes: 131 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,137 @@ All notable changes to `@web3settle/merchant-sdk` will be documented in this fil
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] — Reference-aware pay-ins, real permit allowlist (unpublished, 2026-09-30)

**Breaking** (0.x minor). This change publishes nothing; merging it to `develop` lets CI publish its usual `0.6.0-dev.<run>` prerelease to GitHub Packages (HP-3).
Supersedes merchant-sdk#47, whose toolchain repair is carried unchanged below.

### Added — pay-ins attributed to a payment request (MerchantPayIn V3.2.3, HP-16)

- `PAYMENT_CONTRACT_ABI` gains `payInNativeWithReference(bytes32)`,
`payInTokenWithReference(address,uint256,bytes32)` and the `PaymentReference(bytes32 indexed)`
event; selectors pinned in tests against the compiled V3.2.3 artifact.
- `executePayInNativeWithReference` / `executePayInTokenWithReference`, and the pure
`buildPayInCall({ token, amount, paymentReference? })` every path shares.
- `usePayment().startPayment(…, { paymentReference })` sends the `…WithReference` call.
- `Web3SettleConfig.createPayment(ctx)` — the modal asks the merchant backend for the reference at
Pay time, handing it the exact chain / token / amount (and `userId`, which the modal used to drop).
`onSuccess(session).id` is now the `requestId`, not a zero UUID.
- Headless `createPayButtonController`: `start(amount, { paymentReference })`; the runner receives
it. `<web3settle-pay-button payment-reference="0x…">` validates it and emits it on `payment-started`.
- `isPaymentReference`, `assertPaymentReference`, `resolveAttribution`,
`MissingPaymentReferenceError`, `InvalidPaymentReferenceError`.

### Changed — attribution is the default and is enforced

Every pay path resolves attribution before the wallet is prompted. Without a valid non-zero bytes32
reference it refuses (`MissingPaymentReferenceError`). The plain `payInNative()` / `payInToken()`
remain behind the explicit `mode: 'unattributed'` (`payInMode` on the config) opt-in: such deposits
settle but reach the webhook with no `paymentRequestId` / `metadata`.

### Removed

- `Web3SettleApiClient.createTopUpSession` / `getSessionStatus`, `CreateSessionResponseSchema`,
`CreateSessionResponse`, `SESSION_POLL_INTERVAL_MS`, `MAX_POLL_ATTEMPTS`. They targeted
`/api/storefronts/{id}/sessions`, which no gateway serves (404), and creating a payment needs the
storefront API key, which must not be in the browser. The server-side create → reference flow
replaces them (README, *How a payment reaches your order*).

### Fixed — permit allowlist holds the real digests (HP-5)

`KNOWN_PERMIT_TOKENS` held one placeholder digest that matched no token. It now holds USDC
Ethereum, DAI Ethereum and USDC Base, each re-derived from the chain (`docs/PERMIT_ALLOWLIST.md`,
`scripts/verify-permit-domains.mjs`). ADR-0004 is unchanged: unknown domains still fall back /
throw. The four `signPermit` tests that failed on the placeholder now target the USDC domain and
exercise their own guards; the unknown-domain refusal has its own test.

DAI's permit is not EIP-2612 (on-chain `PERMIT_TYPEHASH` =
`Permit(holder,spender,nonce,expiry,allowed)`), so `detectPermitSupport` now reports a token with a
non-EIP-2612 `PERMIT_TYPEHASH` as unsupported (`reason: 'non-eip2612-permit'`) and `permit: 'auto'`
uses `approve()` for it, instead of signing a payload the token cannot redeem.

### Not in this release

- TRON (`./tron`) pay-ins are not reference-aware yet (contract + gateway are).
- `WEB3SETTLE_PAYMENT_CONFIG_PUBKEY_PRIMARY` is still the all-zero placeholder, so the signed
payment-config never verifies against a real gateway and the modal / pay button cannot load a
chain list. Use `usePayment` with your own `ChainConfig` until the real key ships.

## [0.6.0 toolchain, from #47] — Restore lint + build under ESLint 10 / Tailwind v4 (2026-09-24)

Toolchain only (carried from merchant-sdk#47, which 0.6.0 supersedes). Every
chain-cut guarantee is intact: `archive/solana/` is still unwired, the `exports`
map is still `. / ./tron / ./headless / ./wc / ./styles.css`, and the 137 / 900–902
drop guards still pass.

### Fixed — `npm run lint` crashed, now runs clean

`eslint-plugin-react` under ESLint 10 threw
`TypeError: contextOrFilename.getFilename is not a function` before linting a
single file. No published version of `eslint-plugin-react` (max 7.37.5, peers
`eslint ^3 … ^9.7`) or `eslint-plugin-jsx-a11y` (max 6.10.2, peers `… ^9`)
supports ESLint 10, so the honest minimal fix is to pin the linter, not the
plugins: **`eslint` and `@eslint/js` `^10` → `^9.39.5`**.

- **`.npmrc` is deleted.** The `legacy-peer-deps=true` workaround added during the
chain cut existed only because ESLint 10 broke the peer graph. With eslint 9 the
graph resolves strictly — `npm ci` now succeeds with no flags at all.
- ⚠️ eslint 9.39.5 installs with a *"no longer supported"* deprecation notice. That
is the trade: a supported-but-broken linter versus an EOL-but-working one. The
real exit is upstream — either those two plugins ship ESLint 10 support, or they
get dropped. Tracked in `humanpending.md`.

With linting actually executing, 23 previously-masked errors surfaced. 14 were
mechanical and are fixed here:

| Rule | Count | How |
|---|---|---|
| `@typescript-eslint/no-unnecessary-type-assertion` | 7 | `eslint --fix` — redundant `as` removed |
| `@typescript-eslint/prefer-for-of` | 3 | index loops → `for…of` (incl. `permitDomainKey`) |
| `@typescript-eslint/prefer-optional-chain` | 2 | in `payment-config-verifier.ts` |
| `@typescript-eslint/require-await` | 2 | test double now returns `Promise.resolve` / `Promise.reject` |
| unused `eslint-disable` directive | 1 | replaced with a directive that states *why* the cast exists |

The `prefer-optional-chain` and `prefer-for-of` edits touch the **signed-config
verifier** and the **permit-allowlist digest function**, so they were applied by
hand rather than by a fixer and proven behaviour-neutral: `permit-allowlist`,
`payment-config-verifier` and `api-client` suites pass **26/26**.

The remaining 9 are `react-hooks/set-state-in-effect`, new in
`eslint-plugin-react-hooks` v7. They flag a pre-existing pattern in the payment
modals where an effect mirrors a `status` prop onto a local `step` state machine.
That fix is a real refactor of live payment-flow state and does not belong in a
CI-repair change, so the **rule is set to `warn`** with the rationale inline in
`eslint.config.js`. Lint reports `0 errors, 10 warnings` and exits 0. No security
or correctness rule was relaxed.

### Fixed — `npm run build` failed in two places

1. **Tailwind v4** moved its PostCSS plugin to a separate package; passing
`tailwindcss` itself as a plugin throws. Added `@tailwindcss/postcss` and
pointed `postcss.config.js` at it.
2. **`esbuild` was never installed**, so Vite died with
`Cannot find package 'esbuild'`. Two causes, both fixed:
- `overrides.esbuild` pinned `^0.25.0` while Vite 8.3.1 peers
`^0.27.0 || ^0.28.0` — an unsatisfiable slot. Raised to **`^0.28.1`**, which
also clears GHSA-gv7w-rqvm-qjhr (**high**, esbuild `< 0.28.1`) and
GHSA-g7r4-m6w7-qqqr. The old `^0.25.0` pin was itself vulnerable to both.
- Vite declares `esbuild` as an **optional** peer, so it is never installed
transitively. Added it as an explicit devDependency.

### npm audit

**0 vulnerabilities**, down from 10 high / 16 moderate / 2 low. No `--force` was
used; the only version changes are the ones listed above.

### Still failing — deliberately untouched

The 4 `signPermit` cases in `src/__tests__/permit-allowlist.test.ts` still fail.
They hit ADR-0004's `KNOWN_PERMIT_TOKENS` gate with a token domain that is not
allow-listed, and the shipped allowlist holds one placeholder digest. Making them
pass means deciding the real allowlist contents — **the gate was not weakened and
the tests were not mocked out.** 165 passed / 4 failed, unchanged by this work.

## [Unreleased] — Chain cut: drop Polygon, archive Solana (2026-09-24)

Product decisions **D1** (drop Polygon permanently) and **D2** (archive Solana
Expand Down
Loading
Loading