From d4e886c06e80894f9bfbe47da4dd74b4bccf2aa9 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 29 Aug 2026 13:12:47 +0200 Subject: [PATCH 1/4] fix: clean dist before build and retry throttled reader responses Three release-blocking issues found while reviewing v7 end to end. `yarn build` wrote into whatever `dist/` was already there, so files deleted from `src` survived in the published package: 7.0.0 packed with `dist/transactions/helper.js` (deleted in #123) and `dist/version.js` (deleted in #124), both reachable through the `./dist/*` exports pattern. `prepublishOnly` runs `build`, so the fix is a clean step in `build` itself. A reader node under load answers 429 or 503. `handleFetch` only resolves a body on HTTP 200, so those answers arrived at callers as `undefined` and surfaced as `Error: Contract not found` -- observed against the app's `/api/domains/recent` and `/api/domains/stats` routes while running its end-to-end suite. Retry them. 404 still falls through to `undefined`, which is how a missing AVL value is reported. `privateKeyToAddress` and the signing backends had no exports entry, leaving `@metanames/sdk/dist/transactions` as the only way in. Add `./transactions`. --- NOTES-app-integration.md | 232 ++++++++++++++++++++++++ PLAN-drop-unmaintained-partisia-deps.md | 195 ++++++++++++++++++++ package.json | 11 +- src/repositories/helpers/client.ts | 20 +- 4 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 NOTES-app-integration.md create mode 100644 PLAN-drop-unmaintained-partisia-deps.md diff --git a/NOTES-app-integration.md b/NOTES-app-integration.md new file mode 100644 index 0000000..50001eb --- /dev/null +++ b/NOTES-app-integration.md @@ -0,0 +1,232 @@ +# Note: how the React app consumes the SDK + +Side note to the five optimisation PRs (#115–#119). Everything here is measured, +not estimated; the commands are included so the numbers can be re-derived. + +## 1. Where the SDK stands after the five PRs + +Measured by merging all five branches into a local `integration-check` branch, +then bundling the published entry point: + +``` +npx esbuild dist/esm/index.js --bundle --minify --format=esm \ + --platform=node --splitting --outdir=/tmp/split +``` + +| | `main` | after #115–#119 | +| --------------------------------- | ---------------------------------------- | ---------------------- | +| Read-path entry chunk | 1,642,792 B | **343,073 B (−79.1%)** | +| All chunks (consumer using everything) | 1,642,792 B | 1,192,906 B (−27.4%) | +| Production packages installed | 121 | **49** | +| Production advisories | 99 (25 critical, 25 high, 36 moderate, 13 low) | **1 low** | + +`yarn audit --groups dependencies`, both sides installed fresh from their own +lockfiles on the same day. + +Tests: 21 suites / 269 tests pass on the merged branch. + +What is left in the 343 KB entry chunk: + +``` +200,245 @partisiablockchain/abi-client + 97,288 @partisiablockchain/blockchain-api-transaction-client + 42,217 (SDK's own code) + 2,809 @partisiablockchain/sections +``` + +The remaining opportunity is `@partisiablockchain/abi-client`, which is now 58% +of the read path. Not touched in these PRs. + +## 2. What the app does today + +### 2.1 The whole SDK is eager in the client bundle + +`components/providers.tsx` is a `"use client"` component that statically imports +`metaNamesSdkFactory` from `lib/sdk.ts`, which statically imports `MetaNamesSdk`. +The instance is only ever created inside a `useEffect`, but the import is +top-level, so the SDK lands in the bundle for **every page**. + +Evidence from the existing production build in `.next/static/chunks`: + +``` +$ grep -l "Signing strategy not found" .next/static/chunks/* # SDK ContractRepository +$ grep -l "Domain name is too long" .next/static/chunks/* # SDK DomainValidator +``` + +Both match the *same* 306,958-byte client chunk. The signing path — which only +runs after a wallet connects — ships to every visitor, including ones who never +connect a wallet. + +**Fix:** move the import inside the effect. + +```diff +-import { metaNamesSdkFactory } from "@/lib/sdk"; +- + useEffect(() => { + if (!initialized.current && !metaNamesSdk) { + initialized.current = true; +- setMetaNamesSdk(metaNamesSdkFactory()); ++ void import("@/lib/sdk").then(({ metaNamesSdkFactory }) => ++ setMetaNamesSdk(metaNamesSdkFactory()), ++ ); + } + }, [metaNamesSdk, setMetaNamesSdk]); +``` + +This works today. #119 then splits what remains by signing strategy, and #118 is +what lets the bundler split at all. + +**Better fix, if it fits the roadmap:** the app already proxies reads through API +routes (`/api/account/balance`, `/api/domains`, `/api/register/[name]/fees/[coin]`) +and keeps a server-side singleton in `lib/actions/sdk.ts`. If the remaining +client-side reads moved behind routes, the SDK would leave the client bundle +entirely and only signing would need it — which is already lazy in `lib/wallet.ts`. + +### 2.2 `lib/domain-validator.ts` ships a second copy of the IDNA table + +The app imports `tr46` directly (`package.json:35`) to re-implement validation +the SDK already does. + +One client chunk is 203,107 bytes, of which **137,453 characters (68%) are +digits, commas and brackets** — the raw UTS-46 mapping table. It begins with a +lucide spinner icon and then `/^xn--/`: + +``` +$ head -c 300 .next/static/chunks/0d6-e_486gaul.js +(globalThis.TURBOPACK||…).push([…,"loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56"…}]]…let A=/^xn--/ +``` + +Six chunks reference `useSTD3ASCIIRules`, totalling 573,294 bytes. + +This is also a correctness problem, not only a size one. The comment in +`lib/domain-validator.ts:37-42` documents a bug caused by exactly this +duplication: the app capped each label at 32 characters while the SDK caps the +whole name including `.mpc`, so names passed app validation and then threw +inside the SDK, surfacing as a 500 from `/check`. + +**Fix:** delete the tr46 path and call the SDK's `DomainValidator`, then drop +`tr46` from the app's dependencies. After #117 the SDK's validator carries an +11.7 KB generated table instead of a 225 KB one, and needs no tr46 at all — so +this removes ~200 KB from the client bundle *and* removes the rules that already +drifted once. + +The app's friendlier error messages can stay: keep them as the copy layer and +let the SDK decide pass/fail — the same split `lib/records.ts` already uses for +record validation. + +### 2.3 Value imports are already minimal + +Only three modules import SDK *values*; everything else is `import type` and +costs nothing: + +| file | import | +| ------------------- | --------------------------------- | +| `lib/sdk.ts` | `MetaNamesSdk`, `Enviroment` | +| `lib/constants.ts` | `RecordClassEnum` | +| `lib/records.ts` | `getRecordValidator` | + +`RecordClassEnum` is a TypeScript enum, so it is a real runtime object and cannot +be `import type`. Today that one import pulls the entire barrel into any chunk +that touches `lib/constants.ts` — which is most of the UI. #118 fixes this: with +ESM and `sideEffects: false`, a consumer importing only `RecordClassEnum` bundles +393 bytes instead of 1,642,801. + +No app change needed for that one; it lands with the SDK upgrade. + +### 2.4 Deep `dist/` imports + +Four deep paths are in use: + +``` +@metanames/sdk/dist/models/domain +@metanames/sdk/dist/providers/config +@metanames/sdk/dist/interface +@metanames/sdk/dist/transactions/ledger +``` + +#118 adds an `exports` map, which would normally make these unreachable. They +are explicitly kept working via a `./dist/*` pattern, so **nothing breaks on +upgrade**. Cleaner equivalents are exported alongside and are worth migrating to +at leisure: + +```diff +-import type { BYOCSymbol } from "@metanames/sdk/dist/providers/config"; ++import type { BYOCSymbol } from "@metanames/sdk/providers/config"; +``` + +### 2.5 Wallet loading is already right + +`lib/wallet.ts` dynamic-imports `partisia-blockchain-applications-sdk`, +`@ledgerhq/hw-transport-webusb`, `partisia-blockchain-applications-crypto` and +the SDK's Ledger client at each connect call site. That is exactly the shape +#119 gives the SDK internals, and it is why the app already declares all three +signing packages as direct dependencies — so the v7 move to optional peer +dependencies needs **no change** on the app side. + +## 3. Suggested order of work + +1. Merge #115 (security) — no API change, unblocks the rest. +2. Merge #116, #117 — no API change; −540 KB between them. +3. Merge #118 (ESM). App keeps working unchanged thanks to the `./dist/*` + pattern; this is what makes everything downstream splittable. +4. Merge #119 (v7, breaking). App already has the three peers installed. +5. App: bump to `^7.0.0`, then §2.1 (lazy import in `providers.tsx`) and §2.2 + (delete the duplicated validator, drop `tr46`). These two are the whole + app-side win. +6. Optional: §2.4 import-path migration, §2.1's server-side variant. + +## 4. Not addressed + +- `@partisiablockchain/abi-client` at 200 KB is now the largest single item on + the read path. Worth a look on its own. +- `partisia-blockchain-applications-rpc` pulls axios + mime-db (~180 KB). It is + on the read path and stays a required dependency. +- The live-testnet test suites are order-dependent and flake under contention — + a full run failed 13 record-update tests, a re-run passed all 269, and each + suite passes in isolation. Same on unmodified `main`. Worth separating from + the unit suite so CI signal means something. + +## 5. Re-measured 2026-08-29, after #120, #122, #123 + +The app is still on `@metanames/sdk@^6.3.1`, so none of #115–#123 has reached it. + +App-shaped entry (`MetaNamesSdk`, `Enviroment`, `RecordClassEnum`, +`getRecordValidator`), minified, code-split, eager entry chunk only: + +| | 6.3.1 (published) | sdk `main` + #123 | +| ------------------------ | ----------------- | ----------------- | +| app-shaped import | 1,684,409 B | 245,837 B | +| `RecordClassEnum` only | 1,684,355 B | 413 B | + +6.3.1 emits one chunk: no ESM, so nothing splits and nothing shakes. + +In the app's existing `.next` build (4,207,292 B of client chunks): + +- one 306,958 B chunk holds the whole SDK — validators, signing, everything — + and is pulled in by `components/providers.tsx`'s top-level import of + `lib/sdk.ts`, so it ships to every visitor. §2.1 is still unfixed. +- six chunks reference `useSTD3ASCIIRules`, 573,294 B between them; the largest + is 212,393 B of raw UTS-46 table. §2.2 is still unfixed. The SDK carries an + 11.7 KB generated table instead. +- no bip39 wordlist in the client bundle; that was only ever on the SDK's node + path, and #122 removed it there too. + +Two things the SDK now does for the app (both in #123): + +- `assert` was the last node builtin; the package bundles for + `--platform=browser` with no polyfills. +- `privateKeyToAddress` replaces the app's dev-only use of + `partisia-blockchain-applications-crypto` in `lib/wallet.ts:63`, letting that + dependency leave the app's `package.json`. It stays in the graph transitively + through `partisia-blockchain-applications-sdk`, but leaves the bundle. + +App work, in order of payoff: + +1. Bump `@metanames/sdk`. Nothing breaks: the `./dist/*` export pattern keeps + the four deep imports working. +2. Delete `lib/domain-validator.ts`'s tr46 path, call the SDK's + `DomainValidator`, drop `tr46`. Largest single client-side win, and it ends + the validation drift documented at `lib/domain-validator.ts:37-42`. +3. Move the `lib/sdk` import inside the effect in `components/providers.tsx`. +4. Swap `lib/wallet.ts:63` to `privateKeyToAddress`, drop + `partisia-blockchain-applications-crypto`. diff --git a/PLAN-drop-unmaintained-partisia-deps.md b/PLAN-drop-unmaintained-partisia-deps.md new file mode 100644 index 0000000..5b776fc --- /dev/null +++ b/PLAN-drop-unmaintained-partisia-deps.md @@ -0,0 +1,195 @@ +# Plan: drop the three unmaintained `partisia-blockchain-applications-*` packages + +Your read is correct. Two package families are in play and they are not from the +same source: + +| family | publisher | status | +| --- | --- | --- | +| `@partisiablockchain/*`, `@secata-public/*` | Partisia / Secata (official) | **maintained** — abi-client 6.198.0 published 2026-07-02 | +| `partisia-blockchain-applications-*` | third party | **abandoned** | + +Last publish of the abandoned three: + +``` +partisia-blockchain-applications-rpc 1.0.13 2024-03-18 (~2.5 years) +partisia-blockchain-applications-sdk 0.1.4 2024-06-13 (~2 years) +partisia-blockchain-applications-crypto 1.0.34 2024-10-04 (~2 years) +``` + +The important part: **the official replacement is already installed.** +`@partisiablockchain/blockchain-api-transaction-client` arrives as a dependency +of `@partisiablockchain/abi-client`, is already 97,288 B of the bundle, and +covers everything the abandoned rpc and crypto packages are used for. + +--- + +## 1. Where the advisories actually come from + +45 distinct advisories in the partisia subtrees on `main`: + +| source | count | worst | +| --- | --- | --- | +| `partisia-blockchain-applications-rpc` | **33** | critical (`form-data`); the other 32 are `axios` + `follow-redirects` | +| `partisia-blockchain-applications-crypto` | **11** | critical (`elliptic`, `pbkdf2` ×2, `sha.js`, `cipher-base`) | +| `@partisiablockchain/abi-client` (official) | 1 | moderate (`bn.js` infinite loop) | + +Reproduce: + +``` +yarn audit --groups dependencies --json | grep -c partisia +``` + +**PR #115 already brings production advisories down to 4 low** via a lockfile +refresh plus four `resolutions`. So this plan is *not* about the current +advisory count — that is already handled. It is about the structural problem +underneath it: + +- A `resolutions` entry works only while a patched, API-compatible version of + the transitive dependency exists. The next advisory in `bip32@=2.0.6` (pinned + with `=`, so it cannot float) or in `axios` under a breaking major has no + resolution available, and there is no upstream to publish a fix. +- `partisia-blockchain-applications-crypto` pins `bip32: '=2.0.6'` and + `bip39: '=3.1.0'` exactly. Those are seed/mnemonic paths this SDK never calls. +- It also pulls **`zxcvbn` (3.4 MB installed)**, a password-strength dictionary, + into a blockchain SDK. + +## 2. Are they necessary? No — except one + +### `partisia-blockchain-applications-rpc` → **removable** + +Used for exactly two things: + +| current | official replacement | +| --- | --- | +| `PartisiaAccount(rpc).getContract()` (`src/repositories/contract-repository.ts:24`) | `ChainControllerApi.getContract()` | +| account nonce | `ChainControllerApi.getAccount()` | +| `PartisiaRpc({baseURL}).putTransaction` (`src/transactions/index.ts:35,84,112,139`) | `ChainControllerApi.putTransaction()` | +| transaction lookup | `ShardControllerApi.getTransaction()` | + +The official controllers are `fetch`-based (generated OpenAPI runtime), so this +also deletes axios and mime-db — ~180 KB of the read path and 33 of the 45 +advisories. + +Half this migration is already done: `src/repositories/helpers/avl-client.ts` +talks to the same REST reader API directly with `fetch` via +`src/repositories/helpers/client.ts`. Only `getContract` and the transaction +put/lookup still route through axios. + +### `partisia-blockchain-applications-crypto` → **removable** + +| current import | official replacement | +| --- | --- | +| `serializedTransaction` (`src/transactions/helper.ts:2`) | `BlockchainTransactionClient.sign()` | +| `deriveDigest`, `getTrxHash`, `getTransactionPayloadData` | handled inside `BlockchainTransactionClient` | +| `signTransaction`, `privateKeyToAccountAddress` | `SenderAuthenticationKeyPair.fromString(pk)` | +| — | `CryptoUtils.privateKeyToKeypair` / `keyPairToAccountAddress` / `signatureToBuffer` / `hashBuffers` | + +### `partisia-blockchain-applications-sdk` → **keep** + +This one is not crypto or RPC — it is the browser wallet connector (`PartisiaSdk`, +the postMessage bridge to the Partisia wallet extension). There is no official +npm replacement; the only official wallet package is `@partisiablockchain/snap` +(0.3.0, 2024-11-06), which covers the MetaMask snap path the app already calls +directly, not the Partisia extension. + +Caveat: it depends on `partisia-blockchain-applications-crypto@^1.0.23`, so that +subtree stays reachable transitively. Two things limit the damage — it is +dynamically imported at connect time only (off the read path entirely after +#119), and the app's own `lib/wallet.ts` already imports it lazily. Keep the +`elliptic` resolution from #115 in place for it. + +## 3. The design that falls out + +`SenderAuthentication` is a two-method interface: + +```ts +interface SenderAuthentication { + getAddress(): BlockchainAddress; + sign(transactionPayload: Buffer, chainId: string): Promise; +} +``` + +All four of this SDK's signing strategies collapse into four small +implementations of it, and `BlockchainTransactionClient.signAndSend()` replaces +the hand-rolled serialize/digest/sign/concat/put pipeline that +`src/transactions/index.ts` repeats four times: + +| strategy | implementation | +| --- | --- | +| `privateKey` | `SenderAuthenticationKeyPair.fromString(privateKey)` — already provided | +| `Ledger` | wrap the existing `PartisiaLedgerClient` (`src/transactions/ledger.ts`) | +| `MetaMask` | wrap the existing snap `wallet_invokeSnap` call | +| `partisiaSdk` | wrap `PartisiaSdk.signMessage` | + +This deletes most of `src/transactions/index.ts` and all of +`src/transactions/helper.ts`. + +## 4. Order of work + +Do this **after** #115–#119 land. It is a v8 change; #119 already moved these +packages to optional peers, and this removes two of the three outright. + +Status: step 1 = PR #120 (merged), step 2 = PR #122 (merged), step 3 = PR #123 (open). + +1. **Reader first, lowest risk.** Replace `PartisiaAccount.getContract` in + `src/repositories/contract-repository.ts` with `ChainControllerApi`. Nothing + about signing changes. Removing the axios subtree here is 33 of 45 + advisories and ~180 KB. +2. **`SenderAuthentication` adapters.** Add the four wrappers behind the + existing `setSigningStrategy` API so the public surface does not move. +3. **Swap the transaction pipeline** to `BlockchainTransactionClient`, delete + `src/transactions/helper.ts`, drop `partisia-blockchain-applications-crypto` + from peers. Done in #123: the chain id now comes from `GET /chain` so + `isMainnet` is gone, and the transaction half of `ShardedClient` went with + the helper. +4. **Leave `partisia-blockchain-applications-sdk`** as an optional peer for the + wallet connector. Revisit if Partisia publishes an official connector. + +Each step is independently shippable and independently testable against +testnet. + +### Verify at each step + +``` +yarn test:run +yarn audit --groups dependencies +npx esbuild dist/esm/index.js --bundle --minify --format=esm \ + --platform=node --splitting --outdir=/tmp/split +``` + +## 5. App side + +`../app` — checked, current state: + +| package | app usage | action | +| --- | --- | --- | +| `partisia-blockchain-applications-rpc` | **not imported at all** | nothing to do | +| `partisia-blockchain-applications-crypto` | one call site: `lib/wallet.ts:63-66`, `privateKeyToAccountAddress` inside `connectDevPrivateKey`, which throws in production | **removable now**, see below | +| `partisia-blockchain-applications-sdk` | `lib/wallet.ts:31` (`PartisiaSdk`, dynamic) + `PermissionTypes` type import | keep | + +The app can drop `partisia-blockchain-applications-crypto` from its own +`package.json` today, independent of everything above. It is used only in a +dev-only branch and only for deriving an address from a private key, which the +already-installed official client does: + +```diff +- const mod = await import("partisia-blockchain-applications-crypto"); +- const partisiaCrypto = mod.default?.partisiaCrypto ?? mod.partisiaCrypto; +- const address = partisiaCrypto.wallet.privateKeyToAccountAddress(privateKey); ++ const { CryptoUtils } = await import( ++ "@partisiablockchain/blockchain-api-transaction-client" ++ ); ++ const address = CryptoUtils.keyPairToAccountAddress( ++ CryptoUtils.privateKeyToKeypair(privateKey), ++ ); +``` + +Note this changes which package must be declared: add +`@partisiablockchain/blockchain-api-transaction-client` as a direct app +dependency rather than relying on it being hoisted from the SDK. Verify the two +functions produce the same address for a known key before merging — same +elliptic curve and hash, but confirm rather than assume. + +`partisia-blockchain-applications-sdk` stays either way, and keeps the crypto +subtree present transitively. That is acceptable: it is behind a dynamic import +that only runs when someone connects the Partisia wallet. diff --git a/package.json b/package.json index 05f6aa5..3bcc115 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,12 @@ "require": "./dist/interface.js", "default": "./dist/interface.js" }, + "./transactions": { + "types": "./dist/transactions/index.d.ts", + "import": "./dist/esm/transactions/index.js", + "require": "./dist/transactions/index.js", + "default": "./dist/transactions/index.js" + }, "./transactions/ledger": { "types": "./dist/transactions/ledger.d.ts", "import": "./dist/esm/transactions/ledger.js", @@ -61,14 +67,15 @@ "dist" ], "scripts": { - "build": "yarn build:cjs && yarn build:esm", + "build": "yarn clean && yarn build:cjs && yarn build:esm", "docs": "typedoc --out docs src", "format": "eslint --fix src", "test": "jest -i", "prepublishOnly": "yarn build", "audit-ci": "bash scripts/audit-ci.sh", "build:cjs": "tsc", - "build:esm": "tsc -p tsconfig.esm.json && node scripts/finalize-esm.js" + "build:esm": "tsc -p tsconfig.esm.json && node scripts/finalize-esm.js", + "clean": "rm -rf dist" }, "dependencies": { "@ledgerhq/hw-transport": "^6.34.0", diff --git a/src/repositories/helpers/client.ts b/src/repositories/helpers/client.ts index 8cd32d4..8b71184 100644 --- a/src/repositories/helpers/client.ts +++ b/src/repositories/helpers/client.ts @@ -23,11 +23,27 @@ function buildOptions(method: RequestType, headers: Record, sign } export function getRequest(url: string, timeoutMs = DEFAULT_TIMEOUT_MS): Promise { - return handleFetch(promiseRetry(() => fetchWithTimeout(url, "GET", jsonHeaders, undefined, timeoutMs))) + return handleFetch(promiseRetry(() => request(url, "GET", jsonHeaders, undefined, timeoutMs))) } export function postRequest(url: string, body: unknown, timeoutMs = DEFAULT_TIMEOUT_MS): Promise { - return handleFetch(promiseRetry(() => fetchWithTimeout(url, "POST", jsonBodyHeaders, body, timeoutMs))) + return handleFetch(promiseRetry(() => request(url, "POST", jsonBodyHeaders, body, timeoutMs))) +} + +/** + * A reader node under load answers 429 or 503. That answer is not the + * contract's state, but returning it as `undefined` made every caller report a + * missing contract: `getAll()` against a busy node surfaced as "Contract not + * found". Those statuses are retried instead. 404 and the other client errors + * still fall through to `undefined`, which is how a missing AVL value is + * reported. + */ +function request(url: string, method: RequestType, headers: Record, body: unknown, timeoutMs: number): Promise { + return fetchWithTimeout(url, method, headers, body, timeoutMs).then((response) => { + if (response.status === 429 || response.status >= 500) throw new Error(`${method} ${url} failed with HTTP ${response.status}`) + + return response + }) } async function fetchWithTimeout(url: string, method: RequestType, headers: Record, body: unknown, timeoutMs: number): Promise { From e0356091f30cdc47c3b58a6ae06d97629195cef0 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 29 Aug 2026 13:41:52 +0200 Subject: [PATCH 2/4] fix: keep directory subpath imports resolvable 6.3.1 shipped no exports map, so `@metanames/sdk/dist/models` and its siblings resolved through plain file lookup. The exports map added in v7 turns those into `./dist/models.js`, which does not exist, and Node does not fall back to a directory index. Consumers on those specifiers would break on upgrade for no reason: each directory has an `index.js`, so the entries point at it. Claude-Session: https://claude.ai/code/session_01GceWCwGXu66D1xEBDxZWBb --- package.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/package.json b/package.json index 3bcc115..be0ff1e 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,42 @@ "require": "./dist/transactions/ledger.js", "default": "./dist/transactions/ledger.js" }, + "./dist/models": { + "types": "./dist/models/index.d.ts", + "import": "./dist/esm/models/index.js", + "require": "./dist/models/index.js", + "default": "./dist/models/index.js" + }, + "./dist/providers": { + "types": "./dist/providers/index.d.ts", + "import": "./dist/esm/providers/index.js", + "require": "./dist/providers/index.js", + "default": "./dist/providers/index.js" + }, + "./dist/repositories": { + "types": "./dist/repositories/index.d.ts", + "import": "./dist/esm/repositories/index.js", + "require": "./dist/repositories/index.js", + "default": "./dist/repositories/index.js" + }, + "./dist/transactions": { + "types": "./dist/transactions/index.d.ts", + "import": "./dist/esm/transactions/index.js", + "require": "./dist/transactions/index.js", + "default": "./dist/transactions/index.js" + }, + "./dist/validators": { + "types": "./dist/validators/index.d.ts", + "import": "./dist/esm/validators/index.js", + "require": "./dist/validators/index.js", + "default": "./dist/validators/index.js" + }, + "./dist/validators/idna": { + "types": "./dist/validators/idna/index.d.ts", + "import": "./dist/esm/validators/idna/index.js", + "require": "./dist/validators/idna/index.js", + "default": "./dist/validators/idna/index.js" + }, "./dist/*.js": { "types": "./dist/*.d.ts", "import": "./dist/esm/*.js", From 1d3d0c191a23dd1cc27028c1591c409a3c1c7e7b Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 29 Aug 2026 14:28:07 +0200 Subject: [PATCH 3/4] chore: drop the scratch notes from the branch Working notes committed by accident in the previous commit. #125 squash merges, so main never carries them. Claude-Session: https://claude.ai/code/session_01GceWCwGXu66D1xEBDxZWBb --- NOTES-app-integration.md | 232 ------------------------ PLAN-drop-unmaintained-partisia-deps.md | 195 -------------------- 2 files changed, 427 deletions(-) delete mode 100644 NOTES-app-integration.md delete mode 100644 PLAN-drop-unmaintained-partisia-deps.md diff --git a/NOTES-app-integration.md b/NOTES-app-integration.md deleted file mode 100644 index 50001eb..0000000 --- a/NOTES-app-integration.md +++ /dev/null @@ -1,232 +0,0 @@ -# Note: how the React app consumes the SDK - -Side note to the five optimisation PRs (#115–#119). Everything here is measured, -not estimated; the commands are included so the numbers can be re-derived. - -## 1. Where the SDK stands after the five PRs - -Measured by merging all five branches into a local `integration-check` branch, -then bundling the published entry point: - -``` -npx esbuild dist/esm/index.js --bundle --minify --format=esm \ - --platform=node --splitting --outdir=/tmp/split -``` - -| | `main` | after #115–#119 | -| --------------------------------- | ---------------------------------------- | ---------------------- | -| Read-path entry chunk | 1,642,792 B | **343,073 B (−79.1%)** | -| All chunks (consumer using everything) | 1,642,792 B | 1,192,906 B (−27.4%) | -| Production packages installed | 121 | **49** | -| Production advisories | 99 (25 critical, 25 high, 36 moderate, 13 low) | **1 low** | - -`yarn audit --groups dependencies`, both sides installed fresh from their own -lockfiles on the same day. - -Tests: 21 suites / 269 tests pass on the merged branch. - -What is left in the 343 KB entry chunk: - -``` -200,245 @partisiablockchain/abi-client - 97,288 @partisiablockchain/blockchain-api-transaction-client - 42,217 (SDK's own code) - 2,809 @partisiablockchain/sections -``` - -The remaining opportunity is `@partisiablockchain/abi-client`, which is now 58% -of the read path. Not touched in these PRs. - -## 2. What the app does today - -### 2.1 The whole SDK is eager in the client bundle - -`components/providers.tsx` is a `"use client"` component that statically imports -`metaNamesSdkFactory` from `lib/sdk.ts`, which statically imports `MetaNamesSdk`. -The instance is only ever created inside a `useEffect`, but the import is -top-level, so the SDK lands in the bundle for **every page**. - -Evidence from the existing production build in `.next/static/chunks`: - -``` -$ grep -l "Signing strategy not found" .next/static/chunks/* # SDK ContractRepository -$ grep -l "Domain name is too long" .next/static/chunks/* # SDK DomainValidator -``` - -Both match the *same* 306,958-byte client chunk. The signing path — which only -runs after a wallet connects — ships to every visitor, including ones who never -connect a wallet. - -**Fix:** move the import inside the effect. - -```diff --import { metaNamesSdkFactory } from "@/lib/sdk"; -- - useEffect(() => { - if (!initialized.current && !metaNamesSdk) { - initialized.current = true; -- setMetaNamesSdk(metaNamesSdkFactory()); -+ void import("@/lib/sdk").then(({ metaNamesSdkFactory }) => -+ setMetaNamesSdk(metaNamesSdkFactory()), -+ ); - } - }, [metaNamesSdk, setMetaNamesSdk]); -``` - -This works today. #119 then splits what remains by signing strategy, and #118 is -what lets the bundler split at all. - -**Better fix, if it fits the roadmap:** the app already proxies reads through API -routes (`/api/account/balance`, `/api/domains`, `/api/register/[name]/fees/[coin]`) -and keeps a server-side singleton in `lib/actions/sdk.ts`. If the remaining -client-side reads moved behind routes, the SDK would leave the client bundle -entirely and only signing would need it — which is already lazy in `lib/wallet.ts`. - -### 2.2 `lib/domain-validator.ts` ships a second copy of the IDNA table - -The app imports `tr46` directly (`package.json:35`) to re-implement validation -the SDK already does. - -One client chunk is 203,107 bytes, of which **137,453 characters (68%) are -digits, commas and brackets** — the raw UTS-46 mapping table. It begins with a -lucide spinner icon and then `/^xn--/`: - -``` -$ head -c 300 .next/static/chunks/0d6-e_486gaul.js -(globalThis.TURBOPACK||…).push([…,"loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56"…}]]…let A=/^xn--/ -``` - -Six chunks reference `useSTD3ASCIIRules`, totalling 573,294 bytes. - -This is also a correctness problem, not only a size one. The comment in -`lib/domain-validator.ts:37-42` documents a bug caused by exactly this -duplication: the app capped each label at 32 characters while the SDK caps the -whole name including `.mpc`, so names passed app validation and then threw -inside the SDK, surfacing as a 500 from `/check`. - -**Fix:** delete the tr46 path and call the SDK's `DomainValidator`, then drop -`tr46` from the app's dependencies. After #117 the SDK's validator carries an -11.7 KB generated table instead of a 225 KB one, and needs no tr46 at all — so -this removes ~200 KB from the client bundle *and* removes the rules that already -drifted once. - -The app's friendlier error messages can stay: keep them as the copy layer and -let the SDK decide pass/fail — the same split `lib/records.ts` already uses for -record validation. - -### 2.3 Value imports are already minimal - -Only three modules import SDK *values*; everything else is `import type` and -costs nothing: - -| file | import | -| ------------------- | --------------------------------- | -| `lib/sdk.ts` | `MetaNamesSdk`, `Enviroment` | -| `lib/constants.ts` | `RecordClassEnum` | -| `lib/records.ts` | `getRecordValidator` | - -`RecordClassEnum` is a TypeScript enum, so it is a real runtime object and cannot -be `import type`. Today that one import pulls the entire barrel into any chunk -that touches `lib/constants.ts` — which is most of the UI. #118 fixes this: with -ESM and `sideEffects: false`, a consumer importing only `RecordClassEnum` bundles -393 bytes instead of 1,642,801. - -No app change needed for that one; it lands with the SDK upgrade. - -### 2.4 Deep `dist/` imports - -Four deep paths are in use: - -``` -@metanames/sdk/dist/models/domain -@metanames/sdk/dist/providers/config -@metanames/sdk/dist/interface -@metanames/sdk/dist/transactions/ledger -``` - -#118 adds an `exports` map, which would normally make these unreachable. They -are explicitly kept working via a `./dist/*` pattern, so **nothing breaks on -upgrade**. Cleaner equivalents are exported alongside and are worth migrating to -at leisure: - -```diff --import type { BYOCSymbol } from "@metanames/sdk/dist/providers/config"; -+import type { BYOCSymbol } from "@metanames/sdk/providers/config"; -``` - -### 2.5 Wallet loading is already right - -`lib/wallet.ts` dynamic-imports `partisia-blockchain-applications-sdk`, -`@ledgerhq/hw-transport-webusb`, `partisia-blockchain-applications-crypto` and -the SDK's Ledger client at each connect call site. That is exactly the shape -#119 gives the SDK internals, and it is why the app already declares all three -signing packages as direct dependencies — so the v7 move to optional peer -dependencies needs **no change** on the app side. - -## 3. Suggested order of work - -1. Merge #115 (security) — no API change, unblocks the rest. -2. Merge #116, #117 — no API change; −540 KB between them. -3. Merge #118 (ESM). App keeps working unchanged thanks to the `./dist/*` - pattern; this is what makes everything downstream splittable. -4. Merge #119 (v7, breaking). App already has the three peers installed. -5. App: bump to `^7.0.0`, then §2.1 (lazy import in `providers.tsx`) and §2.2 - (delete the duplicated validator, drop `tr46`). These two are the whole - app-side win. -6. Optional: §2.4 import-path migration, §2.1's server-side variant. - -## 4. Not addressed - -- `@partisiablockchain/abi-client` at 200 KB is now the largest single item on - the read path. Worth a look on its own. -- `partisia-blockchain-applications-rpc` pulls axios + mime-db (~180 KB). It is - on the read path and stays a required dependency. -- The live-testnet test suites are order-dependent and flake under contention — - a full run failed 13 record-update tests, a re-run passed all 269, and each - suite passes in isolation. Same on unmodified `main`. Worth separating from - the unit suite so CI signal means something. - -## 5. Re-measured 2026-08-29, after #120, #122, #123 - -The app is still on `@metanames/sdk@^6.3.1`, so none of #115–#123 has reached it. - -App-shaped entry (`MetaNamesSdk`, `Enviroment`, `RecordClassEnum`, -`getRecordValidator`), minified, code-split, eager entry chunk only: - -| | 6.3.1 (published) | sdk `main` + #123 | -| ------------------------ | ----------------- | ----------------- | -| app-shaped import | 1,684,409 B | 245,837 B | -| `RecordClassEnum` only | 1,684,355 B | 413 B | - -6.3.1 emits one chunk: no ESM, so nothing splits and nothing shakes. - -In the app's existing `.next` build (4,207,292 B of client chunks): - -- one 306,958 B chunk holds the whole SDK — validators, signing, everything — - and is pulled in by `components/providers.tsx`'s top-level import of - `lib/sdk.ts`, so it ships to every visitor. §2.1 is still unfixed. -- six chunks reference `useSTD3ASCIIRules`, 573,294 B between them; the largest - is 212,393 B of raw UTS-46 table. §2.2 is still unfixed. The SDK carries an - 11.7 KB generated table instead. -- no bip39 wordlist in the client bundle; that was only ever on the SDK's node - path, and #122 removed it there too. - -Two things the SDK now does for the app (both in #123): - -- `assert` was the last node builtin; the package bundles for - `--platform=browser` with no polyfills. -- `privateKeyToAddress` replaces the app's dev-only use of - `partisia-blockchain-applications-crypto` in `lib/wallet.ts:63`, letting that - dependency leave the app's `package.json`. It stays in the graph transitively - through `partisia-blockchain-applications-sdk`, but leaves the bundle. - -App work, in order of payoff: - -1. Bump `@metanames/sdk`. Nothing breaks: the `./dist/*` export pattern keeps - the four deep imports working. -2. Delete `lib/domain-validator.ts`'s tr46 path, call the SDK's - `DomainValidator`, drop `tr46`. Largest single client-side win, and it ends - the validation drift documented at `lib/domain-validator.ts:37-42`. -3. Move the `lib/sdk` import inside the effect in `components/providers.tsx`. -4. Swap `lib/wallet.ts:63` to `privateKeyToAddress`, drop - `partisia-blockchain-applications-crypto`. diff --git a/PLAN-drop-unmaintained-partisia-deps.md b/PLAN-drop-unmaintained-partisia-deps.md deleted file mode 100644 index 5b776fc..0000000 --- a/PLAN-drop-unmaintained-partisia-deps.md +++ /dev/null @@ -1,195 +0,0 @@ -# Plan: drop the three unmaintained `partisia-blockchain-applications-*` packages - -Your read is correct. Two package families are in play and they are not from the -same source: - -| family | publisher | status | -| --- | --- | --- | -| `@partisiablockchain/*`, `@secata-public/*` | Partisia / Secata (official) | **maintained** — abi-client 6.198.0 published 2026-07-02 | -| `partisia-blockchain-applications-*` | third party | **abandoned** | - -Last publish of the abandoned three: - -``` -partisia-blockchain-applications-rpc 1.0.13 2024-03-18 (~2.5 years) -partisia-blockchain-applications-sdk 0.1.4 2024-06-13 (~2 years) -partisia-blockchain-applications-crypto 1.0.34 2024-10-04 (~2 years) -``` - -The important part: **the official replacement is already installed.** -`@partisiablockchain/blockchain-api-transaction-client` arrives as a dependency -of `@partisiablockchain/abi-client`, is already 97,288 B of the bundle, and -covers everything the abandoned rpc and crypto packages are used for. - ---- - -## 1. Where the advisories actually come from - -45 distinct advisories in the partisia subtrees on `main`: - -| source | count | worst | -| --- | --- | --- | -| `partisia-blockchain-applications-rpc` | **33** | critical (`form-data`); the other 32 are `axios` + `follow-redirects` | -| `partisia-blockchain-applications-crypto` | **11** | critical (`elliptic`, `pbkdf2` ×2, `sha.js`, `cipher-base`) | -| `@partisiablockchain/abi-client` (official) | 1 | moderate (`bn.js` infinite loop) | - -Reproduce: - -``` -yarn audit --groups dependencies --json | grep -c partisia -``` - -**PR #115 already brings production advisories down to 4 low** via a lockfile -refresh plus four `resolutions`. So this plan is *not* about the current -advisory count — that is already handled. It is about the structural problem -underneath it: - -- A `resolutions` entry works only while a patched, API-compatible version of - the transitive dependency exists. The next advisory in `bip32@=2.0.6` (pinned - with `=`, so it cannot float) or in `axios` under a breaking major has no - resolution available, and there is no upstream to publish a fix. -- `partisia-blockchain-applications-crypto` pins `bip32: '=2.0.6'` and - `bip39: '=3.1.0'` exactly. Those are seed/mnemonic paths this SDK never calls. -- It also pulls **`zxcvbn` (3.4 MB installed)**, a password-strength dictionary, - into a blockchain SDK. - -## 2. Are they necessary? No — except one - -### `partisia-blockchain-applications-rpc` → **removable** - -Used for exactly two things: - -| current | official replacement | -| --- | --- | -| `PartisiaAccount(rpc).getContract()` (`src/repositories/contract-repository.ts:24`) | `ChainControllerApi.getContract()` | -| account nonce | `ChainControllerApi.getAccount()` | -| `PartisiaRpc({baseURL}).putTransaction` (`src/transactions/index.ts:35,84,112,139`) | `ChainControllerApi.putTransaction()` | -| transaction lookup | `ShardControllerApi.getTransaction()` | - -The official controllers are `fetch`-based (generated OpenAPI runtime), so this -also deletes axios and mime-db — ~180 KB of the read path and 33 of the 45 -advisories. - -Half this migration is already done: `src/repositories/helpers/avl-client.ts` -talks to the same REST reader API directly with `fetch` via -`src/repositories/helpers/client.ts`. Only `getContract` and the transaction -put/lookup still route through axios. - -### `partisia-blockchain-applications-crypto` → **removable** - -| current import | official replacement | -| --- | --- | -| `serializedTransaction` (`src/transactions/helper.ts:2`) | `BlockchainTransactionClient.sign()` | -| `deriveDigest`, `getTrxHash`, `getTransactionPayloadData` | handled inside `BlockchainTransactionClient` | -| `signTransaction`, `privateKeyToAccountAddress` | `SenderAuthenticationKeyPair.fromString(pk)` | -| — | `CryptoUtils.privateKeyToKeypair` / `keyPairToAccountAddress` / `signatureToBuffer` / `hashBuffers` | - -### `partisia-blockchain-applications-sdk` → **keep** - -This one is not crypto or RPC — it is the browser wallet connector (`PartisiaSdk`, -the postMessage bridge to the Partisia wallet extension). There is no official -npm replacement; the only official wallet package is `@partisiablockchain/snap` -(0.3.0, 2024-11-06), which covers the MetaMask snap path the app already calls -directly, not the Partisia extension. - -Caveat: it depends on `partisia-blockchain-applications-crypto@^1.0.23`, so that -subtree stays reachable transitively. Two things limit the damage — it is -dynamically imported at connect time only (off the read path entirely after -#119), and the app's own `lib/wallet.ts` already imports it lazily. Keep the -`elliptic` resolution from #115 in place for it. - -## 3. The design that falls out - -`SenderAuthentication` is a two-method interface: - -```ts -interface SenderAuthentication { - getAddress(): BlockchainAddress; - sign(transactionPayload: Buffer, chainId: string): Promise; -} -``` - -All four of this SDK's signing strategies collapse into four small -implementations of it, and `BlockchainTransactionClient.signAndSend()` replaces -the hand-rolled serialize/digest/sign/concat/put pipeline that -`src/transactions/index.ts` repeats four times: - -| strategy | implementation | -| --- | --- | -| `privateKey` | `SenderAuthenticationKeyPair.fromString(privateKey)` — already provided | -| `Ledger` | wrap the existing `PartisiaLedgerClient` (`src/transactions/ledger.ts`) | -| `MetaMask` | wrap the existing snap `wallet_invokeSnap` call | -| `partisiaSdk` | wrap `PartisiaSdk.signMessage` | - -This deletes most of `src/transactions/index.ts` and all of -`src/transactions/helper.ts`. - -## 4. Order of work - -Do this **after** #115–#119 land. It is a v8 change; #119 already moved these -packages to optional peers, and this removes two of the three outright. - -Status: step 1 = PR #120 (merged), step 2 = PR #122 (merged), step 3 = PR #123 (open). - -1. **Reader first, lowest risk.** Replace `PartisiaAccount.getContract` in - `src/repositories/contract-repository.ts` with `ChainControllerApi`. Nothing - about signing changes. Removing the axios subtree here is 33 of 45 - advisories and ~180 KB. -2. **`SenderAuthentication` adapters.** Add the four wrappers behind the - existing `setSigningStrategy` API so the public surface does not move. -3. **Swap the transaction pipeline** to `BlockchainTransactionClient`, delete - `src/transactions/helper.ts`, drop `partisia-blockchain-applications-crypto` - from peers. Done in #123: the chain id now comes from `GET /chain` so - `isMainnet` is gone, and the transaction half of `ShardedClient` went with - the helper. -4. **Leave `partisia-blockchain-applications-sdk`** as an optional peer for the - wallet connector. Revisit if Partisia publishes an official connector. - -Each step is independently shippable and independently testable against -testnet. - -### Verify at each step - -``` -yarn test:run -yarn audit --groups dependencies -npx esbuild dist/esm/index.js --bundle --minify --format=esm \ - --platform=node --splitting --outdir=/tmp/split -``` - -## 5. App side - -`../app` — checked, current state: - -| package | app usage | action | -| --- | --- | --- | -| `partisia-blockchain-applications-rpc` | **not imported at all** | nothing to do | -| `partisia-blockchain-applications-crypto` | one call site: `lib/wallet.ts:63-66`, `privateKeyToAccountAddress` inside `connectDevPrivateKey`, which throws in production | **removable now**, see below | -| `partisia-blockchain-applications-sdk` | `lib/wallet.ts:31` (`PartisiaSdk`, dynamic) + `PermissionTypes` type import | keep | - -The app can drop `partisia-blockchain-applications-crypto` from its own -`package.json` today, independent of everything above. It is used only in a -dev-only branch and only for deriving an address from a private key, which the -already-installed official client does: - -```diff -- const mod = await import("partisia-blockchain-applications-crypto"); -- const partisiaCrypto = mod.default?.partisiaCrypto ?? mod.partisiaCrypto; -- const address = partisiaCrypto.wallet.privateKeyToAccountAddress(privateKey); -+ const { CryptoUtils } = await import( -+ "@partisiablockchain/blockchain-api-transaction-client" -+ ); -+ const address = CryptoUtils.keyPairToAccountAddress( -+ CryptoUtils.privateKeyToKeypair(privateKey), -+ ); -``` - -Note this changes which package must be declared: add -`@partisiablockchain/blockchain-api-transaction-client` as a direct app -dependency rather than relying on it being hoisted from the SDK. Verify the two -functions produce the same address for a known key before merging — same -elliptic curve and hash, but confirm rather than assume. - -`partisia-blockchain-applications-sdk` stays either way, and keeps the crypto -subtree present transitively. That is acceptable: it is behind a dynamic import -that only runs when someone connects the Partisia wallet. From 81f1f6196051670a7b43e18a8e067521051cf319 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 29 Aug 2026 14:34:24 +0200 Subject: [PATCH 4/4] fix: drop the dist subpaths from the exports map `./dist/*` was carried over so consumers importing the build output kept working. That advertises the build layout as API: the paths hard-code the CJS tree, and nothing outside it can move without breaking someone. v7 is the major to stop. The named subpaths cover every use. Claude-Session: https://claude.ai/code/session_01GceWCwGXu66D1xEBDxZWBb --- package.json | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/package.json b/package.json index be0ff1e..4bbb9e6 100644 --- a/package.json +++ b/package.json @@ -43,54 +43,6 @@ "require": "./dist/transactions/ledger.js", "default": "./dist/transactions/ledger.js" }, - "./dist/models": { - "types": "./dist/models/index.d.ts", - "import": "./dist/esm/models/index.js", - "require": "./dist/models/index.js", - "default": "./dist/models/index.js" - }, - "./dist/providers": { - "types": "./dist/providers/index.d.ts", - "import": "./dist/esm/providers/index.js", - "require": "./dist/providers/index.js", - "default": "./dist/providers/index.js" - }, - "./dist/repositories": { - "types": "./dist/repositories/index.d.ts", - "import": "./dist/esm/repositories/index.js", - "require": "./dist/repositories/index.js", - "default": "./dist/repositories/index.js" - }, - "./dist/transactions": { - "types": "./dist/transactions/index.d.ts", - "import": "./dist/esm/transactions/index.js", - "require": "./dist/transactions/index.js", - "default": "./dist/transactions/index.js" - }, - "./dist/validators": { - "types": "./dist/validators/index.d.ts", - "import": "./dist/esm/validators/index.js", - "require": "./dist/validators/index.js", - "default": "./dist/validators/index.js" - }, - "./dist/validators/idna": { - "types": "./dist/validators/idna/index.d.ts", - "import": "./dist/esm/validators/idna/index.js", - "require": "./dist/validators/idna/index.js", - "default": "./dist/validators/idna/index.js" - }, - "./dist/*.js": { - "types": "./dist/*.d.ts", - "import": "./dist/esm/*.js", - "require": "./dist/*.js", - "default": "./dist/*.js" - }, - "./dist/*": { - "types": "./dist/*.d.ts", - "import": "./dist/esm/*.js", - "require": "./dist/*.js", - "default": "./dist/*.js" - }, "./package.json": "./package.json" }, "repository": "https://github.com/MetaNames/sdk.git",