@ninsei-labs/sdk 0.39.0 → 0.39.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,118 @@
1
+ ## Unreleased — 2026-10-08
2
+
3
+ EVERY ORDER'S ESCROW IS A CLONE, AND THE CLIENT NOW CHECKS THE IMPLEMENTATION IT WILL EXECUTE (issue #114).
4
+ Since the contract change an order's escrow is an EIP-1167 minimal proxy of ONE shared implementation, and the
5
+ factory carries the pair (`implementation()`, `implementationCodeHash()`). The factory checks that pair before every
6
+ creation; the client now checks the SAME pair on chain, armed by the interface's own policy - `knownImplementationCodes`,
7
+ an array of known implementation build hashes, sitting next to `knownFactoryCodes`. The verdict reads the pair off the
8
+ factory, reads the CODE at the implementation's address and applies the same check the factory does - and re-hashes the
9
+ live code, so it does not trust the factory's own answer alone. A `delegatecall` to an address without code SUCCEEDS,
10
+ so a clone without an implementation would take the ETH and its `markReady`/`claim`/`refund` would appear to go through
11
+ while doing nothing: that case is refused by name (`implementation-has-no-code`).
12
+
13
+ - **`knownImplementationCodes`** (`src/quotes.mjs`, `src/index.mjs`, `index.d.ts`) - the interface's
14
+ escrow-implementation policy, next to `knownFactoryCodes`. An empty/absent list means "no policy": the pair is not
15
+ read and behaviour is as before (needed while a network still runs a factory of the OLDER build, which has no such
16
+ getters and answers a revert - the deployed one does). A non-empty list refuses an implementation whose recorded
17
+ hash is not a known build with `quote-factory-code-unknown`; a pair that cannot be read at all is refused with
18
+ `quote-factory-unchecked`. Proven by four cases beside the existing factory-code ones: a known build is accepted,
19
+ and a foreign one, a code whose hash does not match the recorded one, and an implementation without code are each
20
+ refused by a named reason.
21
+ - **`verifyFactoryImplementationOnChain`** (`engine/evm/factory.js`) - one place that reads the pair and applies
22
+ `verifyFactoryImplementation`, with the chain injected (`read` = `eth_call`, `code` = `eth_getCode`). The module
23
+ stays pure (no imports, no network) and is the same helper the demo's node and watchtower call with their own reads.
24
+ - **The package source is re-synced from the source-of-truth tree**: `src/**`, `engine/**` and `index.d.ts` are
25
+ byte-for-byte the demo repository's `sdk/`. The demo's interface config pins the known implementation hash - a
26
+ protocol constant, because the implementation has no immutable fields, so its runtime code (and its hash) is the
27
+ same on every network.
28
+ - **Claim and order-creation gas limits are the MEASURED ones** (`engine/evm/claimGas.js`, `engine/evm/funding.js`).
29
+ `CLAIM_GAS_LIMIT` is raised from 120 000 to 200 000: an honest `claim` whose fee is the FIRST credit to the
30
+ recipient's cashier costs 120 721 gas on a live chain (124 072 in `forge --gas-report`), so the former limit cut a
31
+ valid claim. `GAS_CREATE_ORDER_AND_FUND` is lowered from 1 500 000 to 700 000, because an order's escrow is now an
32
+ EIP-1167 clone - the measured full call is 315 530 gas (1 190 683 for the former full contract). The dead
33
+ `GAS_CREATE_ORDER` constant (there is no separate creation path) is removed. The node's own claim in the
34
+ source-of-truth tree signs with the SAME shared limit instead of its own literal.
35
+ - Checks: `node tools/check.mjs` - all green.
36
+
37
+ ## 0.39.2 — 2026-10-08
38
+
39
+ `fakechain` IS A KNOWN MONERO NETWORK. The withdrawal page takes the Monero network FROM THE NODE, and monerod in
40
+ regtest mode reports its network as `fakechain` - the official name of that mode. On the CI contour (a regtest node)
41
+ the core therefore refused its OWN recovery file (`bad-input`, field `xmrNetwork`) and the withdrawal never started.
42
+ The name is now accepted by every check of the network value, and everything else is still refused by name.
43
+
44
+ - **`fakechain` joins the accepted networks** (`src/config.mjs`, `index.d.ts`): beside the engine registry's own
45
+ names and the standard mainnet/stagenet/testnet it is a KNOWN name of the regtest mode. Anything else is still a
46
+ `bad-input` refusal that names the known list - the value is checked, not "any word accepted".
47
+ - **The recovery-file reader accepts it as well** (`engine/recovery/recoveryFile.js`): a file whose wallet network is
48
+ `fakechain` reads back; an unknown name is still rejected. Proven by a writer -> reader round trip, with the
49
+ unknown-name case beside it.
50
+ - **The engine's network registry declares it** (`engine/core/config.js`): the Monero wallet adapter builds its list
51
+ of known networks from `XMR_NETWORKS`, so `/adapters` knows the name too.
52
+ - **The package source is re-synced from the source-of-truth tree**: `src/**`, `engine/**` and `index.d.ts` are now
53
+ byte-for-byte the demo repository's `sdk/`. Besides the change above this also carries two changes that were
54
+ already in that tree: the escrow-implementation address/codehash read and verdict (`engine/evm/factory.js`,
55
+ `verifyFactoryImplementation`) and the smallest-trade rule that reads the maker's SENT minimum rather than the
56
+ effective set's first level (`src/levelsWalkSpec.mjs`, the `minAtomic` argument).
57
+ - **A withdrawal no longer loses its refusal code on a chain the wallet library cannot scan** (`src/sweep.mjs`):
58
+ the FREE REMAINDER is read and judged BEFORE any input selection, and a failed `sync()` no longer pre-empts that
59
+ verdict. On the CI contour (a regtest node) monero-ts `sync()` throws a bare `failed to get hashes`; that error
60
+ used to escape ahead of the funds check, so an EMPTY wallet was refused with `code: null` + raw text instead of
61
+ the named `insufficient-funds` - and the page had no phrase to translate, because the core had named nothing.
62
+ - **Every refusal keeps a code from the package's dictionary** (`src/sweep.mjs`, `walletStep`): a wallet step that
63
+ fails with anything other than an `SdkError` is re-thrown as one, on `wallet-rejected`, with the library's own
64
+ message kept as `params.why`. `code: null` + raw text is not a refusal an interface can show or a check assert.
65
+ - Proven by a unit run WITHOUT a browser or a contour (the source-of-truth tree's `tools/check-sweep-empty-wallet.mjs`,
66
+ part of the fast gates): a zero remainder refuses with `insufficient-funds` and human words while input selection
67
+ is never touched; a non-zero remainder reaches signing; a low-level library failure comes out WITH a code. Two
68
+ breaking runs redden - cutting the remainder check (the refusal stops being named) and cutting the wrapper (the
69
+ run then reproduces the exact CI symptom: `code: null` + `failed to get hashes`).
70
+ - Checks: `node tools/check.mjs` - all green.
71
+
72
+ HEAD
73
+ ## Unreleased — 2026-10-07
74
+
75
+ PRERELEASES GET THEIR OWN npm TAGS, AND `latest` STOPS MOVING BY ACCIDENT. The release workflow published whatever
76
+ version `package.json` carried under whatever tag npm chose on its own; now the tag is DERIVED from the version and
77
+ handed to `npm publish` by name. This entry rides the next version - nothing in `src/`, `engine/` or `index.d.ts`
78
+ changes here.
79
+
80
+ - **The tag comes from the version** (`tools/npm-tag.mjs`): `X.Y.Z` -> `latest`, `X.Y.Z-develop.N` -> `develop`,
81
+ `X.Y.Z-preview.N` -> `preview`, read from the FIRST identifier of the prerelease part. Anything else is a named
82
+ refusal, not a guess: a version that is not `X.Y.Z[...]`, a prerelease channel that is not declared, a tag that
83
+ reads as a version.
84
+ - **A prerelease can never move `latest`.** The publish step works the tag out before it sends anything, and the
85
+ derivation refuses, naming the case, when a prerelease would take `latest` - even under a manual override. The
86
+ reverse is refused too: a plain version does not publish into a channel tag with no decision behind it.
87
+ - **The tag is passed explicitly.** "Publishing a package sets the `latest` tag to the published version unless
88
+ the `--tag` option is used" (https://docs.npmjs.com/cli/v11/commands/npm-dist-tag), and the `tag` config defaults
89
+ to `latest` (https://docs.npmjs.com/cli/v11/using-npm/config#tag) - so the step now ends in
90
+ `npm publish --access public --provenance --tag <tag>` and never leans on that default.
91
+ - **A manual run may override the tag** with the new `tag` input; the step echoes the override and the tag the
92
+ derivation would have produced into the log before the publish, so the decision is readable, not silent.
93
+ - **The cycle is written down** (README, "Releases and prereleases"): a build for ourselves is a prerelease under
94
+ `develop`/`preview`, the demo pins the EXACT version, and a release is a separate decision - a plain version, the
95
+ only shape that moves `latest`.
96
+ - **Proven locally:** `node tools/npm-tag.test.mjs` walks the table - plain, both channels, build metadata, an
97
+ undeclared channel, garbage, every override - 28 cases, all green. A breaking run that cuts the guard reddens 5
98
+ of those 28, exactly the tooth's own cases, and `tools/npm-tag.mjs` was restored byte for byte. `node
99
+ tools/check.mjs` stays green, and CI gained the same case table as its own named step.
100
+
101
+ ## 0.39.1 — 2026-10-07
102
+
103
+ THE FIRST RELEASE CUT BY THE TAG-TRIGGERED WORKFLOW. The package's contents did not change: this entry and the
104
+ version field are the whole difference from 0.39.0, and the release exists to publish once through the
105
+ trusted-publisher setup on npm.
106
+
107
+ - **Why a release at all.** npm's trusted-publisher link for `@ninsei-labs/sdk` (GitHub Actions, repository `sdk`,
108
+ workflow `release.yml`) reads "Pending validation" and only becomes active after one publish goes through that
109
+ workflow. This is that publish.
110
+ - **Nothing in the package moved.** `src/**`, `engine/**` and `index.d.ts` are the same sources as 0.39.0; the only
111
+ edits are `package.json` (0.39.0 to 0.39.1) and this entry. The dependencies are unchanged, so `pnpm-lock.yaml` is
112
+ untouched.
113
+ - Checks: `node tools/check.mjs` - all 7 green (syntax, import, surface, mirror, language, pack, secrets).
114
+
115
+
1
116
  ## 0.39.0 — 2026-10-07
2
117
 
3
118
  THE PACKAGE IS LEVEL WITH THE MAIN REPOSITORY AGAIN. The `sdk/src` and `sdk/engine` trees were re-synced
package/README.md CHANGED
@@ -185,3 +185,24 @@ trigger where those exist; on another VM they simply do not appear, rather than
185
185
  touch them: `atomic/order-worker.js`, the Monero bundles.
186
186
  6. **Strict CSP.** The package is designed for a policy without `eval` and inline.
187
187
  7. **Zero dependencies.**
188
+
189
+ ## Releases and prereleases
190
+
191
+ Every published version rides on an npm dist-tag, and the tag is **derived from the version** - never left to
192
+ npm's default:
193
+
194
+ | Version | npm dist-tag | What it is |
195
+ |---|---|---|
196
+ | `X.Y.Z` | `latest` | a release - a separate decision, and the only thing that moves `latest` |
197
+ | `X.Y.Z-develop.N` | `develop` | a build for ourselves |
198
+ | `X.Y.Z-preview.N` | `preview` | a build to show |
199
+
200
+ The loop this serves: build a version for yourself (`0.40.0-develop.1`), publish it under `develop`, and have the
201
+ demo pin the **exact** version (`"@ninsei-labs/sdk": "0.40.0-develop.1"`) rather than a tag. `latest` does not
202
+ move, so `npm install @ninsei-labs/sdk` keeps serving the last real release. Shipping a release is then its own
203
+ decision: a plain `X.Y.Z`, the only shape that publishes under `latest`.
204
+
205
+ The tag is passed to `npm publish` explicitly (`--tag <tag>`), and `tools/npm-tag.mjs` refuses the two ways this
206
+ can go wrong **by name** - a prerelease that would take `latest`, and a plain version that would slide into a
207
+ channel someone else owns. A manual run of the release workflow may override the tag with an input; when it does,
208
+ the log says so. The derivation and its refusals are covered by `node tools/npm-tag.test.mjs`.