@ninsei-labs/sdk 0.41.0 → 0.42.1

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,45 @@
1
+ ## 0.42.1 — 2026-10-12
2
+
3
+ THE BID SIDE OF THE QUOTE WALK ROUNDED THE WRONG WAY. The specification fixes a rounding DIRECTION per side, and
4
+ the walk rounded both sides alike: a bid price came out up to 3 wei high and disagreed with the reference at a
5
+ level boundary.
6
+
7
+ - Per-level rounding follows the side now: a level's cost rounds up on the ask side, where the user pays, and down
8
+ on the bid side, where the user is paid; the XMR amount a budget yields at a level rounds up on the bid side.
9
+ - The level-fits test is asymmetric: a level is taken whole when `remaining >= full` on the ask side and when
10
+ `remaining > full` on the bid side.
11
+ - This removes a systematic 1-to-3-wei over-charge on bid prices and the disagreement with the reference at level
12
+ boundaries.
13
+
14
+ Checks: `node tools/check.mjs` - all 19 green; `node tools/sync-version.mjs`, `node tools/npm-tag.test.mjs`, the two
15
+ teeth runs and `tools/sweep-refusal.test.mjs` are green too.
16
+
17
+ ## 0.42.0 — 2026-10-12
18
+
19
+ EVERY ENTRY THIS PACKAGE PUBLISHES CARRIES ITS OWN DECLARATIONS, AND THE SWEEP PAGE'S TWO PRIMITIVES ARE ON
20
+ `./xmr`. A node that imported a subpath got no types at all - only the root had them - while a sweep page kept its
21
+ own copies of two rules the engine already carries. Both are closed here.
22
+
23
+ - `./xmr`, `./order`, `./evm`, `./core` and `./recovery` each carry a declaration file next to their barrel
24
+ (`src/public/<entry>.d.ts`) declaring exactly the symbols that barrel re-exports, with the signatures read off the
25
+ engine modules. `./recovery` opens the recovery file a sweep page reads back: `ENVELOPE_VERSION`, `FILE_MAGIC`,
26
+ `PAYLOAD_VERSION`, `createRecoveryFile`, `decryptFile`, `scanStartHeight` and the rest of its 11 symbols.
27
+ - `./xmr` gains the sweep side: `spendKeysFromRevealedHalf` (the spend and view keys a revealed half yields, with the
28
+ check that `(s_a + s_b)*G` equals the order's public spend key before anything is swept) and `scalarFromMoneroHex`
29
+ (a Monero-notation key read as a number, never through `BigInt("0x" + key)`, which reverses the byte order).
30
+ - A `types` check keeps the declarations and the runtime equal in both directions: every subpath with an import or
31
+ default target must declare `types` (a subpath left without them must be named, with its reason, in the check's own
32
+ table - `./adapters` is), the declared file must exist and read as declarations, and every symbol the barrel
33
+ re-exports must be declared, and nothing else. `tools/check-types-teeth.mjs` is its breaking run: four patches, each
34
+ RED with its own named reason, each file restored byte for byte.
35
+ - `SDK_VERSION` is baked into `src/version.mjs` from `package.json` by `npm run sync:version`: a page cannot fetch
36
+ `package.json`, so the version is a value in the code, and the `version` check refuses a mismatch and refuses a
37
+ runtime read of the file. `engines.node` moves to `>=22`.
38
+ - `RouteRefusalCode` declares all 33 codes the four route providers refuse with, and a `refusals` check refuses to let
39
+ the provider tables and the type drift apart in either direction.
40
+
41
+ Nothing that already existed changed behaviour: the synchronous swap path is untouched.
42
+
1
43
  ## 0.41.0 — 2026-10-11
2
44
 
3
45
  THE DEPOSITOR PATH IS GONE. The factory no longer offers `createOrderAndFundByDepositor`, so the package must
package/README.md CHANGED
@@ -31,6 +31,13 @@ value that looks like data is more dangerous than a refusal.
31
31
  `index.d.ts` is the single source of truth about the API shape, and this is checked: `tools/check-sdk.mjs`
32
32
  compares the list of what the SDK returns with what is declared in the contract (both ways).
33
33
 
34
+ EVERY PUBLISHED SUBPATH CARRIES ITS OWN DECLARATIONS: `.` (`index.d.ts`), `./xmr`, `./order`, `./evm`,
35
+ `./core` and `./recovery` (`src/public/<entry>.d.ts`). Each of them declares exactly the symbols its barrel
36
+ re-exports, so a typed consumer sees the same surface the runtime has, and nothing else.
37
+ `tools/check.mjs --only=types` keeps the two sides equal (a subpath without a `types` target, a declarations
38
+ file that does not read as declarations, or a symbol declared on one side only is red), and
39
+ `tools/check-types-teeth.mjs` is its breaking run.
40
+
34
41
  ```js
35
42
  import { createNinsei } from "@ninsei-labs/sdk";
36
43
 
@@ -209,3 +216,7 @@ The tag is passed to `npm publish` explicitly (`--tag <tag>`), and `tools/npm-ta
209
216
  can go wrong **by name** - a prerelease that would take `latest`, and a plain version that would slide into a
210
217
  channel someone else owns. A manual run of the release workflow may override the tag with an input; when it does,
211
218
  the log says so. The derivation and its refusals are covered by `node tools/npm-tag.test.mjs`.
219
+
220
+ Before the tag, the version constant moves with the version: `npm run sync:version` writes `src/version.mjs` from
221
+ `package.json`. The number is BAKED into the code on purpose - a page cannot fetch `package.json`, and the file
222
+ need not be served beside a bundle - so `npm run check` refuses both a mismatch and a runtime read of it.
package/index.d.ts CHANGED
@@ -494,6 +494,81 @@ export interface QuoteOffer {
494
494
  readonly networks: { readonly asset: string | null; readonly currency: string | null; readonly stated: boolean };
495
495
  }
496
496
 
497
+ /**
498
+ * Every code a route provider of this package refuses with.
499
+ *
500
+ * The providers already name them in their own tables (`COW_REFUSAL_CODES` and its three siblings), and the
501
+ * battery proves each of those is reachable; what was missing was the TYPE, so an interface could enumerate
502
+ * them instead of meeting a string it cannot switch on. `broken` is shared by all four: it is the code for a
503
+ * case a provider could not classify. `RouteRefusalTables` below declares what those tables ARE - each of them a
504
+ * `readonly RouteRefusalCode[]` - so the union is the tables' TYPE and not a comment standing beside them.
505
+ *
506
+ * These are OUR codes, and they are what a person's message should be phrased from. A provider's OWN reason
507
+ * stays opaque on purpose (`QuoteRefusal.why`): that is data the provider sent, not a name of ours, so it is
508
+ * deliberately NOT part of this union.
509
+ */
510
+ export type RouteRefusalCode =
511
+ // cow
512
+ | "cow-bad-order"
513
+ | "cow-bad-response"
514
+ | "cow-not-settled"
515
+ | "cow-order-unknown"
516
+ | "cow-refused"
517
+ | "cow-sign-failed"
518
+ | "cow-unknown-network"
519
+ | "cow-unreachable"
520
+ // kyber
521
+ | "kyber-bad-order"
522
+ | "kyber-bad-response"
523
+ | "kyber-not-settled"
524
+ | "kyber-order-unknown"
525
+ | "kyber-refused"
526
+ | "kyber-sign-failed"
527
+ | "kyber-unknown-network"
528
+ | "kyber-unreachable"
529
+ // uniswapx
530
+ | "uniswapx-bad-order"
531
+ | "uniswapx-bad-response"
532
+ | "uniswapx-no-submitter"
533
+ | "uniswapx-not-settled"
534
+ | "uniswapx-order-unknown"
535
+ | "uniswapx-refused"
536
+ | "uniswapx-sign-failed"
537
+ | "uniswapx-unknown-network"
538
+ | "uniswapx-unreachable"
539
+ // uniswap-relay
540
+ | "uniswap-relay-bad-order"
541
+ | "uniswap-relay-bad-response"
542
+ | "uniswap-relay-no-discovery"
543
+ | "uniswap-relay-not-settled"
544
+ | "uniswap-relay-sign-failed"
545
+ | "uniswap-relay-unknown-network"
546
+ | "uniswap-relay-unreachable"
547
+ // shared
548
+ | "broken";
549
+
550
+ /**
551
+ * WHAT THE FOUR REFUSAL TABLES *ARE*. Each route provider names its own refusals in one table - `COW_REFUSAL_CODES`
552
+ * (src/legs/cow.mjs), `KYBER_REFUSAL_CODES` (src/legs/kyber.mjs), `UNISWAPX_REFUSAL_CODES` (src/legs/uniswapx.mjs) and
553
+ * `UNISWAPRELAY_REFUSAL_CODES` (src/legs/uniswaprelay.mjs) - and every one of those tables is a
554
+ * `readonly RouteRefusalCode[]`. The dependency runs ONE WAY on purpose: a table answers to the union (a code that is
555
+ * not declared above cannot enter a table), while the union answers to no single table, so the same vocabulary is
556
+ * met in four modules.
557
+ *
558
+ * THEY ARE NOT ON THIS ENTRY'S SURFACE, and this is deliberately NOT a value: the package re-exports no provider and
559
+ * no table (src/index.mjs), the tables travel in the shipped source (`files` in package.json) and they are the
560
+ * VOCABULARY, not an API. A `declare const` here that the runtime does not export would be a lie, and the `surface`
561
+ * check refuses exactly that - so the tables are declared as their TYPE. That is what keeps the union from being a
562
+ * comment beside the tables: this declaration says what they are, and the `refusals` check says the same thing from
563
+ * the other side - a table declared here as a plain `readonly string[]` is a named problem there.
564
+ */
565
+ export interface RouteRefusalTables {
566
+ readonly COW_REFUSAL_CODES: readonly RouteRefusalCode[];
567
+ readonly KYBER_REFUSAL_CODES: readonly RouteRefusalCode[];
568
+ readonly UNISWAPX_REFUSAL_CODES: readonly RouteRefusalCode[];
569
+ readonly UNISWAPRELAY_REFUSAL_CODES: readonly RouteRefusalCode[];
570
+ }
571
+
497
572
  export interface QuoteRefusal {
498
573
  readonly providerId: string;
499
574
  readonly displayName: string | null;
@@ -1373,6 +1448,7 @@ export declare class SdkError extends Error {
1373
1448
  }
1374
1449
 
1375
1450
  /** The step codes the SDK works with (`state.step`): the same list as the `StepCode` type. */
1451
+ export declare const SDK_VERSION: string;
1376
1452
  export declare const STEP_CODES: readonly StepCode[];
1377
1453
  /** The check codes a preflight verdict names (`checks[].code`): the same list as the `CheckCode` type. */
1378
1454
  export declare const CHECK_CODES: readonly CheckCode[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninsei-labs/sdk",
3
- "version": "0.41.0",
3
+ "version": "0.42.1",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -18,7 +18,11 @@
18
18
  "check:mirror": "node tools/check.mjs --only=mirror",
19
19
  "check:pack": "node tools/check.mjs --only=pack",
20
20
  "check:secrets": "node tools/check.mjs --only=secrets",
21
- "check:sweep": "node tools/sweep-refusal.test.mjs"
21
+ "check:types": "node tools/check.mjs --only=types",
22
+ "check:types-teeth": "node tools/check-types-teeth.mjs",
23
+ "check:refusals": "node tools/check.mjs --only=refusals",
24
+ "check:refusals-teeth": "node tools/check-refusals-teeth.mjs",
25
+ "check:sweep": "node tools/sweep-refusal.test.mjs", "sync:version": "node tools/sync-version.mjs"
22
26
  },
23
27
  "types": "./index.d.ts",
24
28
  "exports": {
@@ -29,6 +33,26 @@
29
33
  },
30
34
  "./adapters": {
31
35
  "default": "./src/adapters/index.mjs"
36
+ },
37
+ "./xmr": {
38
+ "types": "./src/public/xmr.d.ts",
39
+ "default": "./src/public/xmr.mjs"
40
+ },
41
+ "./order": {
42
+ "types": "./src/public/order.d.ts",
43
+ "default": "./src/public/order.mjs"
44
+ },
45
+ "./evm": {
46
+ "types": "./src/public/evm.d.ts",
47
+ "default": "./src/public/evm.mjs"
48
+ },
49
+ "./core": {
50
+ "types": "./src/public/core.d.ts",
51
+ "default": "./src/public/core.mjs"
52
+ },
53
+ "./recovery": {
54
+ "types": "./src/public/recovery.d.ts",
55
+ "default": "./src/public/recovery.mjs"
32
56
  }
33
57
  },
34
58
  "files": [
@@ -40,7 +64,7 @@
40
64
  "LICENSE"
41
65
  ],
42
66
  "engines": {
43
- "node": ">=20"
67
+ "node": ">=22"
44
68
  },
45
69
  "keywords": [
46
70
  "xmr",
package/src/index.mjs CHANGED
@@ -270,6 +270,7 @@ export function createNinsei(options) {
270
270
  }
271
271
 
272
272
  export { SdkError, ERROR_CODES, STEP_CODES, CHECK_CODES };
273
+ export { SDK_VERSION } from "./version.mjs";
273
274
 
274
275
  // THE LEVEL WALK IS ON THE PACKAGE SURFACE. A maker publishes a level set (chunks of volume with their own prices)
275
276
  // and both the node and the client compute a firm price by WALKING it - the client must reach the SAME price before
@@ -19,7 +19,9 @@
19
19
  // 3. the first level sets the minimum deal size (there is no separate min);
20
20
  // 4. an EMPTY set = the side is off, and that is NOT an error;
21
21
  // 5. the walk goes BOTH ways: "I give asset - how much XMR" and "I give XMR - how much asset";
22
- // 6. rounding to atomic units is in the maker's favour;
22
+ // 6. rounding to atomic units is in the maker's favour, EACH LEVEL ON ITS OWN: up on ask, where the user pays,
23
+ // down on bid, where the user is paid; the answer in XMR is rounded the maker's way too, down for a buyer
24
+ // (ask) and up for a seller (bid);
23
25
  // 7. one orientation: volume in XMR, price in the asset per 1 XMR;
24
26
  // 8. a fixed swap fee (fee in wei, separate for each side): the buyer pays the walk PLUS the fee, the seller
25
27
  // receives the walk MINUS the fee. It is NOT spread over the volume: otherwise the average price would fall
@@ -83,51 +85,65 @@ export function checkLevels(levels, orientation = "ask") {
83
85
  return { ok: true, enabled: true, count: levels.length, minAtomic: toBigInt(levels[0].amount), totalAtomic };
84
86
  }
85
87
 
86
- // THE COST OF A CHUNK OF VOLUME IN WEI, ROUNDED UP (rule 6, in the maker's favour).
87
- function levelCostWei(amountAtomic, priceWeiPerXmr) {
88
- const num = amountAtomic * priceWeiPerXmr;
89
- return (num + XMR_ATOMIC_PER_ONE - 1n) / XMR_ATOMIC_PER_ONE;
88
+ // ROUND A NON-NEGATIVE FRACTION TO A WHOLE UNIT: up, or down. Money is BigInt only (rule 10); the fraction is
89
+ // non-negative where this is reached (amounts and prices are positive, a budget is positive).
90
+ function divRound(num, den, up) {
91
+ const q = num / den;
92
+ return up && q * den < num ? q + 1n : q;
90
93
  }
91
94
 
92
- // HOW MANY XMR ATOMS A BUDGET GIVES WITHIN ONE LEVEL, ROUNDED DOWN (rule 6).
93
- function levelXmrForBudget(budgetWei, priceWeiPerXmr) {
94
- return (budgetWei * XMR_ATOMIC_PER_ONE) / priceWeiPerXmr;
95
+ // THE COST OF A CHUNK OF VOLUME IN THE ASSET, ONE LEVEL AT A TIME (rule 6). Rounding is always in the maker's
96
+ // favour: UP on ask, where the user pays, DOWN on bid, where the node pays the user. A walk rounds each level on
97
+ // its own and adds the levels up, as the node does: rounding the sum once would differ by up to a unit per level,
98
+ // and the node would sign the maker another amount than its journal books.
99
+ function levelCostWei(amountAtomic, priceWeiPerXmr, orientation) {
100
+ return divRound(amountAtomic * priceWeiPerXmr, XMR_ATOMIC_PER_ONE, orientation === "ask");
95
101
  }
96
102
 
97
- // THE FULL COST OF A SIDE (the sum of its levels' costs) and its volume - computed once.
98
- function sideSummary(levels) {
103
+ // HOW MANY XMR ATOMS A BUDGET GIVES WITHIN ONE LEVEL (rule 6). The answer in XMR is rounded the maker's way too:
104
+ // DOWN for a buyer (ask), UP for a seller (bid). At a coarse asset several atomic units pay the same, and a
105
+ // seller sends no more of them than the payout needs.
106
+ function levelXmrForBudget(budgetWei, priceWeiPerXmr, orientation) {
107
+ return divRound(budgetWei * XMR_ATOMIC_PER_ONE, priceWeiPerXmr, orientation === "bid");
108
+ }
109
+
110
+ // THE FULL COST OF A SIDE (the sum of its levels' costs, each rounded on its own) and its volume - computed once.
111
+ function sideSummary(levels, orientation) {
99
112
  let totalAtomic = 0n;
100
113
  let fullCostWei = 0n;
101
114
  for (const level of levels) {
102
115
  const amount = toBigInt(level.amount);
103
116
  const price = toBigInt(level.price);
104
117
  totalAtomic += amount;
105
- fullCostWei += levelCostWei(amount, price);
118
+ fullCostWei += levelCostWei(amount, price, orientation);
106
119
  }
107
120
  return { totalAtomic, fullCostWei };
108
121
  }
109
122
 
110
- // THE WALK WITHOUT THE FEE: a budget is given - how much XMR it yields (rounding down within a level, rule 6).
111
- function walkXmrForBudget(levels, budgetWei) {
123
+ // THE WALK WITHOUT THE FEE: a budget is given - how much XMR it yields (each level rounded on its own, rule 6).
124
+ function walkXmrForBudget(levels, budgetWei, orientation) {
112
125
  let remaining = budgetWei;
113
126
  let xmrAtomic = 0n;
114
127
  for (const level of levels) {
115
128
  const amount = toBigInt(level.amount);
116
129
  const price = toBigInt(level.price);
117
- const full = levelCostWei(amount, price);
118
- if (remaining >= full) {
130
+ const full = levelCostWei(amount, price, orientation);
131
+ // The maker's way once more: a buyer (ask) takes a level whole even when the budget only just reaches it; a
132
+ // seller (bid) sends no more than the payout needs, so an exact match is left to the partial step, rounded up.
133
+ const fits = orientation === "ask" ? remaining >= full : remaining > full;
134
+ if (fits) {
119
135
  xmrAtomic += amount;
120
136
  remaining -= full;
121
137
  continue;
122
138
  }
123
- xmrAtomic += levelXmrForBudget(remaining, price);
139
+ xmrAtomic += levelXmrForBudget(remaining, price, orientation);
124
140
  break;
125
141
  }
126
142
  return xmrAtomic;
127
143
  }
128
144
 
129
- // THE WALK WITHOUT THE FEE: an XMR volume is given - what it costs (each chunk's cost rounded up, rule 6).
130
- function walkCostForXmr(levels, xmrAtomic) {
145
+ // THE WALK WITHOUT THE FEE: an XMR volume is given - what it costs (each chunk's cost rounded on its own, rule 6).
146
+ function walkCostForXmr(levels, xmrAtomic, orientation) {
131
147
  let left = xmrAtomic;
132
148
  let costWei = 0n;
133
149
  for (const level of levels) {
@@ -135,7 +151,7 @@ function walkCostForXmr(levels, xmrAtomic) {
135
151
  const amount = toBigInt(level.amount);
136
152
  const price = toBigInt(level.price);
137
153
  const take = left < amount ? left : amount;
138
- costWei += levelCostWei(take, price);
154
+ costWei += levelCostWei(take, price, orientation);
139
155
  left -= take;
140
156
  }
141
157
  return costWei;
@@ -166,16 +182,16 @@ export function xmrForAsset({ levels, role, feeWei = 0n, assetWei, orientation =
166
182
  if (asset === null || fee === null || fee < 0n) return { ok: false, reason: "levels-not-integer", role };
167
183
  const budget = role === "buy" ? asset - fee : asset + fee;
168
184
  if (budget <= 0n) return { ok: false, reason: "levels-fee-beyond-budget", role };
169
- const { fullCostWei } = sideSummary(levels);
185
+ const { fullCostWei } = sideSummary(levels, orientation);
170
186
  if (budget > fullCostWei) return { ok: false, reason: "levels-exceed-side", role };
171
187
  // THE SMALLEST TRADE, from the set AS THE MAKER SENT IT (rule 3): callers pass it in minAtomic and the
172
188
  // node's deductions never change it. Absent, the set's own first level is used, as it always was.
173
189
  const min = minOf(levels, minAtomic);
174
190
  // Its cost ON THE EFFECTIVE SET: after deductions the smallest trade may reach into a later level, so the
175
191
  // cost is walked, not read off one price.
176
- const minCost = walkCostForXmr(levels, min);
192
+ const minCost = walkCostForXmr(levels, min, orientation);
177
193
  if (budget < minCost) return { ok: false, reason: "levels-below-minimum", role };
178
- const xmrAtomic = walkXmrForBudget(levels, budget);
194
+ const xmrAtomic = walkXmrForBudget(levels, budget, orientation);
179
195
  return { ok: true, role, xmrAtomic, assetWei: asset };
180
196
  }
181
197
 
@@ -193,7 +209,7 @@ export function assetForXmr({ levels, role, feeWei = 0n, xmrAtomic, orientation
193
209
  if (want > verdict.totalAtomic) return { ok: false, reason: "levels-exceed-side", role };
194
210
  // THE SMALLEST TRADE AS THE MAKER SENT IT, never the effective set's first level (rule 3): see minOf.
195
211
  if (want < minOf(levels, minAtomic)) return { ok: false, reason: "levels-below-minimum", role };
196
- const walkWei = walkCostForXmr(levels, want);
212
+ const walkWei = walkCostForXmr(levels, want, orientation);
197
213
  const assetWei = role === "buy" ? walkWei + fee : walkWei - fee;
198
214
  if (assetWei <= 0n) return { ok: false, reason: "levels-fee-beyond-budget", role };
199
215
  return { ok: true, role, xmrAtomic: want, assetWei };
@@ -0,0 +1,13 @@
1
+ // DECLARATIONS FOR THE `./core` ENTRY.
2
+ //
3
+ // src/public/core.mjs only NAMES what the engine already offers; these declarations NAME the same thing for a
4
+ // TypeScript consumer. Every signature is copied from the engine module the barrel re-exports
5
+ // (`../../engine/core/format.js`), and a symbol the barrel does not re-export is not declared here.
6
+
7
+ /**
8
+ * The number a person entered, in units, or null when the text carries no number.
9
+ *
10
+ * A comma without a dot is the decimal separator ("0,05" is 0.05) and a comma beside a dot separates thousands
11
+ * ("1,234.5" is 1234.5), so what the field shows and what the rest of the code computes cannot diverge.
12
+ */
13
+ export declare function parseAmount(raw: string | number | null | undefined): number | null;
@@ -0,0 +1,6 @@
1
+ // NEUTRAL HELPERS that the entries above and their consumers share: a decimal amount typed as text, in units.
2
+ //
3
+ // One function today. It is a separate entry because it belongs to neither the XMR side nor the EVM side, and a
4
+ // consumer forced to import it from either would learn a false lesson about where it lives.
5
+
6
+ export { parseAmount } from "../../engine/core/format.js";
@@ -0,0 +1,184 @@
1
+ // DECLARATIONS FOR THE `./evm` ENTRY: factory calldata and the arithmetic of a claim.
2
+ //
3
+ // src/public/evm.mjs only NAMES what the engine already offers; these declarations NAME the same thing for a
4
+ // TypeScript consumer. Every signature is copied from the modules the barrel re-exports
5
+ // (`../../engine/evm/factory.js`, `../../engine/evm/claimGas.js`), and a symbol the barrel does not re-export is
6
+ // not declared here.
7
+
8
+ /** The factory's calldata selectors, computed from the contract's own signatures. */
9
+ export declare const FACTORY_METHODS: {
10
+ readonly predict: string;
11
+ readonly createOrderAndFund: string;
12
+ };
13
+
14
+ /** The factory getters read to check the single shared escrow implementation every clone runs. */
15
+ export declare const FACTORY_READS: {
16
+ readonly implementation: string;
17
+ readonly implementationCodeHash: string;
18
+ };
19
+
20
+ /** The OrderCreated event topic: the same value the contract declares. */
21
+ export declare const ORDER_CREATED_TOPIC: string;
22
+
23
+ /** The signed quote fields IN CONTRACT ORDER. The order is part of the ABI: a rearrangement is another call. */
24
+ export declare const QUOTE_FIELDS: readonly string[];
25
+
26
+ /**
27
+ * Everything `predict` and `createOrderAndFund` encode: the signed quote, the provider's signature, and the three
28
+ * depositor fields that travel as separate arguments (they go into the escrow's init code).
29
+ */
30
+ export interface FactoryQuote {
31
+ readonly provider: string;
32
+ readonly quoteKey: string;
33
+ readonly registry: string;
34
+ readonly cashier: string;
35
+ readonly locker: string;
36
+ readonly claimer: string;
37
+ readonly amount: string | number | bigint;
38
+ readonly readyBy: string | number | bigint;
39
+ readonly t1: string | number | bigint;
40
+ readonly salt: string;
41
+ readonly commitHalfClaimer: string;
42
+ readonly edPointClaimer: string;
43
+ readonly chainId: string | number | bigint;
44
+ readonly feeBps: string | number | bigint;
45
+ readonly fee: string | number | bigint;
46
+ readonly feeRecipient: string;
47
+ readonly validUntil: string | number | bigint;
48
+ readonly nonce: string | number | bigint;
49
+ readonly xmrAmount: string | number | bigint;
50
+ /** r || s || v over the quote digest, 65 bytes, with or without the `0x` prefix. */
51
+ readonly signature: string;
52
+ readonly commitHalfLocker: string;
53
+ readonly edPointLocker: string;
54
+ readonly edViewPointLocker: string;
55
+ }
56
+
57
+ /**
58
+ * One calldata word: the value right-aligned to `bytes` (32 unless another width is asked for). Decimal digits are
59
+ * turned into hex as a NUMBER, so padding cannot change the value. Refuses anything that is neither.
60
+ */
61
+ export declare function word(value: string | number | bigint, bytes?: number): string;
62
+
63
+ /** The `predict` call: the argument set matches `encodeCreateOrderAndFund` byte for byte. */
64
+ export declare function encodePredict(quote: FactoryQuote): string;
65
+
66
+ /** The `createOrderAndFund` call: creation and deposit in one transaction. */
67
+ export declare function encodeCreateOrderAndFund(quote: FactoryQuote): string;
68
+
69
+ /** `predict()`'s answer - one address in a 32-byte word. */
70
+ export declare function decodeAddress(returnedHex: string): string;
71
+
72
+ /** `implementation()`'s answer. */
73
+ export declare function decodeImplementation(returnedHex: string): string;
74
+
75
+ /** `implementationCodeHash()`'s answer - one bytes32 word. */
76
+ export declare function decodeImplementationCodeHash(returnedHex: string): string;
77
+
78
+ /** The getter's calldata by name: it takes no arguments, so the selector is the whole call. */
79
+ export declare function encodeFactoryRead(name: "implementation" | "implementationCodeHash"): string;
80
+
81
+ /**
82
+ * The verdict on the escrow implementation, by the same three conditions the factory applies at order creation:
83
+ * the address is named and its code is non-empty, the recorded hash matches, and the hash is in the interface
84
+ * policy if one is set. Refusals are named, never silent.
85
+ */
86
+ export type FactoryImplementationVerdict =
87
+ | {
88
+ readonly ok: false;
89
+ readonly why: string;
90
+ readonly codeHash?: string;
91
+ readonly got?: string;
92
+ readonly want?: string;
93
+ }
94
+ | { readonly ok: true; readonly codeHash: string };
95
+
96
+ /**
97
+ * What the verdict is read from. `code` is the implementation's on-chain code; `hashCode` recomputes it when the
98
+ * carrier can hash bytes. An unreadable base is not zero: the reason names what was missing.
99
+ */
100
+ export interface FactoryImplementationRequest {
101
+ readonly implementation?: string;
102
+ readonly implementationCodeHash?: string;
103
+ readonly code?: string | null;
104
+ readonly hashCode?: ((code: string) => string) | null;
105
+ /** The interface's own list of known builds. An empty or absent list means "no policy". */
106
+ readonly policy?: readonly string[] | null;
107
+ }
108
+
109
+ /** The verdict on the implementation, from the fields alone. */
110
+ export declare function verifyFactoryImplementation(request?: FactoryImplementationRequest): FactoryImplementationVerdict;
111
+
112
+ /**
113
+ * The verdict read at the factory itself: `implementation()`/`implementationCodeHash()` and the code at the
114
+ * implementation's address, then the same three conditions. The chain is injected - `read` is
115
+ * `eth_call({to, data}) -> hex` and `code` is `eth_getCode(address) -> hex` - so the module performs no call of
116
+ * its own. An unreadable pair or code is a named refusal, because an old factory without these getters reverts.
117
+ */
118
+ export type FactoryImplementationOnChainVerdict =
119
+ | {
120
+ readonly ok: false;
121
+ readonly why: string;
122
+ readonly factory?: string;
123
+ readonly implementation?: string;
124
+ readonly codeHash?: string;
125
+ readonly got?: string;
126
+ readonly want?: string;
127
+ }
128
+ | { readonly ok: true; readonly codeHash: string; readonly implementation: string; readonly implementationCodeHash: string };
129
+
130
+ export interface FactoryImplementationOnChainRequest {
131
+ readonly factory?: string;
132
+ readonly read?: (request: { readonly to: string; readonly data: string }) => Promise<string>;
133
+ readonly code?: (address: string) => Promise<string>;
134
+ readonly policy?: readonly string[] | null;
135
+ readonly hashCode?: ((code: string) => string) | null;
136
+ }
137
+
138
+ /** The pair read at the factory, with the same verdict the factory applies. */
139
+ export declare function verifyFactoryImplementationOnChain(
140
+ request?: FactoryImplementationOnChainRequest,
141
+ ): Promise<FactoryImplementationOnChainVerdict>;
142
+
143
+ /**
144
+ * The claim gas limit, MEASURED on a live chain with a margin: an honest claim costs 120 721 gas. The same
145
+ * number on both sides of the reverse swap.
146
+ */
147
+ export declare const CLAIM_GAS_LIMIT: bigint;
148
+
149
+ /** The network's tip floor. Zero where the network needs no tip, which is policy and not a constant here. */
150
+ export declare const CLAIM_TIP_FLOOR_WEI: bigint;
151
+
152
+ /** Margin for base-fee growth between the ticket and the claim, in basis points, as a node setting. */
153
+ export declare const DEFAULT_GIFT_RESERVE_BPS: bigint;
154
+
155
+ /** The fee of a claim in EIP-1559 terms: `2 x baseFee + tip`, tip not below the floor. */
156
+ export interface ClaimMaxFeeRequest {
157
+ readonly baseFeeWei?: string | number | bigint | null;
158
+ readonly priorityFeeWei?: string | number | bigint | null;
159
+ readonly minTipWei?: string | number | bigint | null;
160
+ }
161
+
162
+ /** `maxFeePerGas`, or null when the base fee is unreadable or zero (no fee fields is not a zero fee). */
163
+ export declare function claimMaxFeePerGasWei(request?: ClaimMaxFeeRequest): bigint | null;
164
+
165
+ /** What the balance needs for the claim. */
166
+ export interface ClaimRequiredRequest {
167
+ readonly gasLimit?: string | number | bigint | null;
168
+ readonly maxFeePerGasWei?: string | number | bigint | null;
169
+ }
170
+
171
+ /** `gasLimit x maxFeePerGas`, or null when either factor is unmeasured. */
172
+ export declare function claimRequiredWei(request?: ClaimRequiredRequest): bigint | null;
173
+
174
+ /** The gift the node sends to a claimer who holds no native coin. */
175
+ export interface ClaimGasGiftRequest {
176
+ readonly baseFeeWei?: string | number | bigint | null;
177
+ readonly priorityFeeWei?: string | number | bigint | null;
178
+ readonly minTipWei?: string | number | bigint | null;
179
+ readonly gasLimit?: string | number | bigint | null;
180
+ readonly reserveBps?: string | number | bigint | null;
181
+ }
182
+
183
+ /** The claim cost the client requires, plus the reserve margin. The ONE computation of the gift size. */
184
+ export declare function claimGasGiftWei(request?: ClaimGasGiftRequest): bigint | null;
@@ -0,0 +1,29 @@
1
+ // THE EVM SIDE: factory calldata and the arithmetic of a claim.
2
+ //
3
+ // A node verifies a deployed factory against the implementation it was told about, and prices the gas gift for
4
+ // a claim. Both used to be reachable only through the page. Names only.
5
+
6
+ export {
7
+ FACTORY_METHODS,
8
+ FACTORY_READS,
9
+ ORDER_CREATED_TOPIC,
10
+ QUOTE_FIELDS,
11
+ decodeAddress,
12
+ decodeImplementation,
13
+ decodeImplementationCodeHash,
14
+ encodeCreateOrderAndFund,
15
+ encodeFactoryRead,
16
+ encodePredict,
17
+ verifyFactoryImplementation,
18
+ verifyFactoryImplementationOnChain,
19
+ word,
20
+ } from "../../engine/evm/factory.js";
21
+
22
+ export {
23
+ CLAIM_GAS_LIMIT,
24
+ CLAIM_TIP_FLOOR_WEI,
25
+ DEFAULT_GIFT_RESERVE_BPS,
26
+ claimGasGiftWei,
27
+ claimMaxFeePerGasWei,
28
+ claimRequiredWei,
29
+ } from "../../engine/evm/claimGas.js";