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: 9 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The dependency graph cannot be resolved under npm's default strict peer rules:
# eslint-plugin-jsx-a11y@6.10.2 (its latest) declares `eslint: ^3 … ^9`, while this
# package pins eslint ^10. The committed package-lock.json was already generated
# with legacy peer resolution — without this file `npm ci` fails ERESOLVE on a
# clean checkout, which is why CI has been red independently of any source change.
#
# Making the mode explicit keeps local installs, `npm ci`, and the lockfile in
# agreement. Remove it once eslint-plugin-jsx-a11y ships an eslint@10 peer range.
legacy-peer-deps=true
39 changes: 21 additions & 18 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

`@web3settle/merchant-sdk` is a **React + framework-agnostic TypeScript SDK** a
merchant embeds in their storefront to accept **non-custodial crypto payments**
across **EVM, Solana, and TRON**. It is published to a package registry and
across **EVM (non-Polygon) and TRON**. It is published to a package registry and
**runs in the end customer's browser**. Its core job: take a USD amount, obtain a
verified payment-config and quote from the Web3Settle gateway, build the chain-
specific transaction, and drive the user's wallet to sign and broadcast a
Expand All @@ -22,14 +22,18 @@ central to its architecture as the features it offers.

## Entry points / subpath structure

> **Chain cut (2026-09-24).** The `./solana` subpath was removed and its sources
> quarantined under [`archive/solana/`](./archive/solana/README.md) (decision
> D2); Polygon (chainId 137) was dropped permanently (decision D1). Everything
> below describes the post-cut package.

The package ships **multiple entry points** (`exports` in `package.json`), so a
consumer pulls in only the chain and rendering style they use. Solana and TRON
peer deps are **optional** — an EVM-only consumer never bundles them.
consumer pulls in only the chain and rendering style they use. The TRON peer dep
is **optional** — an EVM-only consumer never bundles it.

| Subpath | Source entry | Purpose |
|---|---|---|
| `.` (root) | [`src/index.ts`](./src/index.ts) | EVM React components + hooks, core (api-client, contract, price-feed, telemetry, confirmation policy, types, config), permit utils, i18n |
| `./solana` | [`src/solana/index.ts`](./src/solana/index.ts) | Solana provider, hooks, components, pipeline, PDA + instruction builders (optional `@solana/*` peers) |
| `./tron` | [`src/tron/index.ts`](./src/tron/index.ts) | TRON provider, hooks, components, pipeline; detects `window.tronWeb` at runtime (TronLink) |
| `./headless` | [`src/headless/index.ts`](./src/headless/index.ts) | Framework-agnostic controllers (`createPayButtonController`, …) — no React render; for Vue/Svelte/vanilla |
| `./wc` | [`src/wc/index.ts`](./src/wc/index.ts) | Native `<web3settle-pay-button>` Web Component (no Lit), reuses the headless controller |
Expand All @@ -38,8 +42,8 @@ peer deps are **optional** — an EVM-only consumer never bundles them.
Layering (no cycles; UI layers depend inward on core):

```
React components (.) Solana (./solana) TRON (./tron)
\ | /
React components (.) TRON (./tron)
\ /
Web Component (./wc) → headless controllers (./headless)
\ | /
core/ (api-client, contract, pipeline interface,
Expand Down Expand Up @@ -92,20 +96,19 @@ payments rather than accepting unverified ones.
## Multi-chain adapters

A single `PaymentPipeline` interface ([`src/core/pipeline.ts`](./src/core/pipeline.ts))
abstracts the three chain stacks so merchant UIs never branch on `chainId`:
abstracts both chain stacks so merchant UIs never branch on `chainId`:

| Family | Stack | Approval step | Amount unit | Tx-hash shape |
|---|---|---|---|---|
| EVM | wagmi + viem | yes (non-native, when allowance < amount) | wei | hex |
| Solana | `@solana/web3.js`; hand-rolled instructions + PDA derivation ([`src/solana/pda.ts`](./src/solana/pda.ts)) | **no** (transfer signed inline) | lamports / SPL raw | base58 |
| TRON | runtime `window.tronWeb` | yes (non-native) | sun | base58 |

Cross-chain **finality** is unified by `ConfirmationPolicy`
([`src/core/ConfirmationPolicy.ts`](./src/core/ConfirmationPolicy.ts)): EVM block
confirmations, Solana commitment levels, TRON confirmed-tx — one "is this safe
yet?" API. On Solana, per-merchant state is a set of **PDAs** (`merchant_config`,
`sol_vault`, `token_totals`) whose seeds mirror the Anchor program 1:1 — the
analogue of the EVM/TRON per-merchant contract address.
confirmations and TRON confirmed-tx behind one "is this safe yet?" API. Both
families count block depth, so the interface is now purely numeric — the
commitment-level vocabulary went away with Solana. Adding a new **EVM** chain is
a registry entry, not an interface change.

## Build & publish

Expand All @@ -120,11 +123,11 @@ analogue of the EVM/TRON per-merchant contract address.
mode** ([`vite.config.ts`](./vite.config.ts)) with `@vitejs/plugin-react`,
rollup multi-entry `lib`, and **`vite-plugin-dts`** (`rollupTypes`) for bundled
`.d.ts`. Emits dual **ESM (`.js`) + CJS (`.cjs`)** plus a single `styles.css`.
React / wagmi / viem / `@solana/*` / `tronweb` are kept **external** (consumers
supply them).
React / wagmi / viem / `tronweb` are kept **external** (consumers supply
them).
- **Test:** **Vitest** (`npm test`, jsdom) — see `src/__tests__/` (api-client,
payment-config-verifier, permit + permit-allowlist, telemetry, per-chain gas /
pipeline / PDA, confirmation policy).
pipeline, confirmation policy incl. the chain-cut drop guards).
- **Lint/type:** ESLint (flat config) + `tsc --noEmit` strict.
- **Publish integrity** — `prepublishOnly` runs `npm ci --ignore-scripts` +
typecheck + lint + test + build; `publishConfig` sets **`provenance: true`**.
Expand Down Expand Up @@ -156,16 +159,16 @@ analogue of the EVM/TRON per-merchant contract address.
│ │ allow-list (ADR-0003) │ │
│ │ │ verified contractAddress + amount │ │
│ │ ▼ │ │
│ │ PaymentPipeline (EVM | Solana | TRON adapter) │ │
│ │ PaymentPipeline (EVM | TRON adapter) │ │
│ │ • permit? → KNOWN_PERMIT_TOKENS gate + deadline/owner/chain │ │
│ │ checks (ADR-0004), else approve() │ │
│ │ • build calldata / instruction / trigger │ │
│ │ │ │ │
│ │ ▼ request signature │ │
│ │ USER WALLET (MetaMask / Phantom / TronLink) ── signs ───────────┼─┼──► BLOCKCHAIN
│ │ USER WALLET (MetaMask / TronLink) ── signs ─────────────────────┼─┼──► BLOCKCHAIN
│ │ │ │ │ funds land DIRECTLY in the
│ │ ▼ broadcast tx │ │ merchant's MerchantPayIn
│ │ ConfirmationPolicy: poll to required finality │ │ contract/PDA (non-custodial)
│ │ ConfirmationPolicy: poll to required finality │ │ contract (non-custodial)
│ │ │ │ │
│ │ telemetry breadcrumb (opt-in, redacted, salted digest, │ │
│ │ NO network from SDK) ──► merchant's own analytics (ADR-0005) │ │
Expand Down
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,81 @@ 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).

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

Product decisions **D1** (drop Polygon permanently) and **D2** (archive Solana
out of mainline). Active chain families: **EVM (non-Polygon) + TRON**.

> **Version not bumped on purpose.** This is a breaking API change and needs a
> `0.6.0` release decision from the founder — bumping here would make CI publish
> a prerelease carrying breaking changes off `develop`. See the workspace-root
> `humanpending.md`.

### Removed — BREAKING

- **`@web3settle/merchant-sdk/solana` subpath.** Gone from `exports`, from the
Vite entry map, and from the peer/dev dependency sets (`@solana/web3.js`,
`@solana/wallet-adapter-base`, `@solana/wallet-adapter-react`). Sources moved
to `archive/solana/` — outside `tsconfig` `include`, the Vitest `include`, and
the ESLint `files` glob, so nothing typechecks, lints, tests or bundles them.
- **Solana-shaped holes in the shared surface**, rather than leaving them
returning `null` for every chain: `SolanaCommitmentLevel`,
`ConfirmationPolicy.commitmentLevel()`, `createHighValueConfirmationPolicy()`,
and `SolanaGasBreakdown`.
- **`'solana'` from the union types** `ChainFamily`, `PaymentFamily` and
`TelemetryChain`.
- **The `solana.*` i18n namespace** from `en` and `pt-BR`.
- **Polygon (chainId 137)** from `DEFAULT_CHAINS`, `CHAIN_ICONS`,
`COINGECKO_CHAIN_IDS`, `KNOWN_CONTRACT_ADDRESSES`,
`DEFAULT_CONFIRMATION_THRESHOLDS`, `CHAIN_FAMILY_REGISTRY`,
`DEFAULT_SECONDS_TO_FINALITY`, the `price-feed` `matic-network` fallback, the
wagmi chain list in `Web3SettleProvider`, and the EVM chain-id allowlists in
`TopUpModal` / `evm/confirmationPolicy`.

### Added

- **Drop guards** in `src/__tests__/confirmationPolicy.test.ts`: chainId 137 and
the Solana sentinels 900–902 must be absent from every registry, must resolve
as *unknown* chains (12 confirmations, 0 s ETA) rather than as their old
selves, and no registry entry may claim a family outside `evm | tron`. These
fail if a dropped chain is re-added without a founder decision.
- `archive/solana/README.md` — quarantine banner and re-wiring checklist.

### Fixed — pre-existing, not caused by the cut

These three were blocking `tsc --noEmit`, which is the check that proves the cut
did not break the type surface. Fixed minimally so the cut is verifiable; each is
independent of the chain work:

- `tsconfig.json` — dropped the deprecated `baseUrl` (and the `@/*` `paths` alias
it enabled, which nothing imported). TypeScript 6 makes `baseUrl` a hard error,
so `npm run typecheck` aborted before reaching any source file.
- `src/hooks/useWallet.ts` — wagmi v3's `useBalance` no longer returns
`formatted`; format `value`/`decimals` with viem's `formatUnits` so the SDK's
public `balance: string | null` contract is unchanged.
- `src/css.d.ts` (new) — TypeScript 6 needs a declaration for the side-effect
`import './styles.css'` in `src/styles.ts`.
- `.npmrc` (new) — pins `legacy-peer-deps=true`. `eslint-plugin-jsx-a11y@6.10.2`
(its newest) declares `eslint: ^3 … ^9` while this package pins eslint ^10, so
a clean `npm ci` fails ERESOLVE. The committed lockfile was already built that
way; making it explicit keeps local installs, CI and the lockfile in agreement.

### Still broken — pre-existing, out of scope for this cut

Verified as failing identically on the pre-cut tree:

- `npm run lint` — `eslint-plugin-react` is incompatible with ESLint 10
(`contextOrFilename.getFilename is not a function`). Needs eslint pinned to ^9
or plugin releases that support 10.
- `npm run build` (the `vite build` half) — Tailwind v4 moved its PostCSS plugin
to `@tailwindcss/postcss`; after that is fixed the build next fails on a
missing `esbuild` (the root `overrides.esbuild` pin vs Vite 8 / rolldown).
- 4 tests in `src/__tests__/permit.test.ts` — `signPermit` gained the
`KNOWN_PERMIT_TOKENS` gate (ADR-0004) but those cases still use a token domain
that is not allow-listed, and the shipped allowlist holds one placeholder
digest. Fixing it means deciding the real allowlist contents, not editing a
test. 165 of 169 tests pass.

## [0.5.0] - 2026-05-09

### Added
Expand Down
Loading
Loading