@ninsei-labs/sdk 0.40.0 → 0.42.0

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,40 @@
1
+ ## 0.42.0 — 2026-10-12
2
+
3
+ EVERY ENTRY THIS PACKAGE PUBLISHES CARRIES ITS OWN DECLARATIONS, AND THE SWEEP PAGE'S TWO PRIMITIVES ARE ON
4
+ `./xmr`. A node that imported a subpath got no types at all - only the root had them - while a sweep page kept its
5
+ own copies of two rules the engine already carries. Both are closed here.
6
+
7
+ - `./xmr`, `./order`, `./evm`, `./core` and `./recovery` each carry a declaration file next to their barrel
8
+ (`src/public/<entry>.d.ts`) declaring exactly the symbols that barrel re-exports, with the signatures read off the
9
+ engine modules. `./recovery` opens the recovery file a sweep page reads back: `ENVELOPE_VERSION`, `FILE_MAGIC`,
10
+ `PAYLOAD_VERSION`, `createRecoveryFile`, `decryptFile`, `scanStartHeight` and the rest of its 11 symbols.
11
+ - `./xmr` gains the sweep side: `spendKeysFromRevealedHalf` (the spend and view keys a revealed half yields, with the
12
+ check that `(s_a + s_b)*G` equals the order's public spend key before anything is swept) and `scalarFromMoneroHex`
13
+ (a Monero-notation key read as a number, never through `BigInt("0x" + key)`, which reverses the byte order).
14
+ - A `types` check keeps the declarations and the runtime equal in both directions: every subpath with an import or
15
+ default target must declare `types` (a subpath left without them must be named, with its reason, in the check's own
16
+ table - `./adapters` is), the declared file must exist and read as declarations, and every symbol the barrel
17
+ re-exports must be declared, and nothing else. `tools/check-types-teeth.mjs` is its breaking run: four patches, each
18
+ RED with its own named reason, each file restored byte for byte.
19
+ - `SDK_VERSION` is baked into `src/version.mjs` from `package.json` by `npm run sync:version`: a page cannot fetch
20
+ `package.json`, so the version is a value in the code, and the `version` check refuses a mismatch and refuses a
21
+ runtime read of the file. `engines.node` moves to `>=22`.
22
+ - `RouteRefusalCode` declares all 33 codes the four route providers refuse with, and a `refusals` check refuses to let
23
+ the provider tables and the type drift apart in either direction.
24
+
25
+ Nothing that already existed changed behaviour: the synchronous swap path is untouched.
26
+
27
+ ## 0.41.0 — 2026-10-11
28
+
29
+ THE DEPOSITOR PATH IS GONE. The factory no longer offers `createOrderAndFundByDepositor`, so the package must
30
+ stop building a call to a selector that does not exist in the deployed factory.
31
+
32
+ - `src/swap-flow.mjs` no longer carries or relays `depositorSignature`: the funding path is the one where the
33
+ payer and the refund recipient are always the same address.
34
+ - The engine mirror loses `evm/depositor.js` (47 modules instead of 48) and the EIP-712 spec comment now names
35
+ the order-quote form.
36
+ - A consumer that meant to fund someone else's quote funds through `createOrderAndFund`.
37
+
1
38
  ## 0.40.0 — 2026-10-11
2
39
 
3
40
  THE ASYNCHRONOUS ROUTE PROVIDERS REACH `latest`. This release moves the tag from 0.39.3; the demo pins this exact
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.
@@ -46,7 +46,7 @@ export const ENDPOINTS = Object.freeze({
46
46
  // with the page's actual origin, so a hard-coded host breaks the session on any other host.
47
47
  const PAGE_ORIGIN = typeof location !== "undefined" ? location.origin : "";
48
48
 
49
- // THE EIP-712 SIGNING DOMAIN (name/version) for the depositor signature. It is NOT a brand literal in the signer: it
49
+ // THE EIP-712 SIGNING DOMAIN (name/version) for a signed order quote. It is NOT a brand literal in the signer: it
50
50
  // belongs to the registry contract (it returns DOMAIN_SEPARATOR()), so it is taken from the config here. The defaults
51
51
  // reproduce today's result byte for byte.
52
52
  export const SIGNING_DOMAIN = Object.freeze({ name: "NinseiEscrowFactory", version: "1" });
@@ -19,9 +19,6 @@ export const FACTORY_METHODS = {
19
19
  predict: "0x0824d205",
20
20
  // create the escrow AND deposit funds in ONE transaction (a value transfer with the call).
21
21
  createOrderAndFund: "0x4703a9d3",
22
- // create the escrow and deposit when the DEPOSITOR is recovered FROM THE SIGNATURE over the quote digest:
23
- // anyone may send the transaction, and `locker` is set to the signer.
24
- createOrderAndFundByDepositor: "0x2acd187b",
25
22
  };
26
23
 
27
24
  // The OrderCreated event topic. It grew the registry address, the provider and the signing key:
@@ -129,15 +126,6 @@ export function encodeCreateOrderAndFund(quote) {
129
126
  return FACTORY_METHODS.createOrderAndFund + quoteCallArgs(quote);
130
127
  }
131
128
 
132
- // CREATE AND DEPOSIT when the depositor is recovered FROM ITS SIGNATURE over the quote digest. Anyone may send
133
- // the transaction, so there are TWO signers: quote.signature (the provider, checked by the escrow) and
134
- // depositorSignature (the depositor, checked by the factory, which requires the recovered address to equal
135
- // quote.locker). Both over ONE digest.
136
- export function encodeCreateOrderAndFundByDepositor(quote, depositorSignature) {
137
- return FACTORY_METHODS.createOrderAndFundByDepositor + quoteWords(quote) +
138
- signatureWords(quote.signature) + signatureWords(depositorSignature) + lockerHalfWords(quote);
139
- }
140
-
141
129
  // predict() answer - one address in a 32-byte word.
142
130
  export function decodeAddress(returnedHex) {
143
131
  const hex = String(returnedHex || "").replace(/^0x/, "");
@@ -14,10 +14,7 @@
14
14
  // keccak256 is passed in as a dependency, as in the address module: the caller loads the key library, not this
15
15
  // module. Monero and Ethereum share Keccak-256, so the hash computed by the bundle matches what Solidity checks.
16
16
 
17
- import { encodeCreateOrderAndFund, encodeCreateOrderAndFundByDepositor, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
18
- // THE DEPOSITOR BY SIGNATURE: the signature shape comes from one place (evm/depositor.js), next to the protocol
19
- // shape. Checking the shape here would be a second record of the same rule.
20
- import { depositorSignatureShape } from "./depositor.js";
17
+ import { encodeCreateOrderAndFund, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
21
18
  import { encodeStatus, decodeStatus } from "./escrow.js";
22
19
  // THE FEE: rate, amount and recipient are PART OF THE SIGNED QUOTE. The factory no longer has them (the router was
23
20
  // removed), so the amount comes from the quote and is cross-checked by the formula mirror. Without a quote there
@@ -152,7 +149,7 @@ async function waitReceipt(receiptReader, hash, { tries = 12, delayMs = 1500 } =
152
149
 
153
150
  // The full funding path. Steps are returned outward so the interface can show each one: predict is a free call,
154
151
  // createOrder and lock are transactions in the user's wallet.
155
- export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote, depositorSignature = null, call, send, receipt, onStep }) {
152
+ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote, call, send, receipt, onStep }) {
156
153
  if (!factory) throw new Error("no escrow factory address set for this network");
157
154
  if (!locker) throw new Error("no EVM wallet connected: there is nobody to deposit");
158
155
  if (!claimer) throw new Error("the order recipient (claimer) is not set");
@@ -243,20 +240,10 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
243
240
  ? " (order " + amountWei + " + fee " + fee.feeWei + ")"
244
241
  : "";
245
242
  onStep?.("locking " + (amountLabel || "funds") + feeWords + " in one transaction - confirm in wallet");
246
- // WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH, AND IT IS NOT DECORATION. If a depositor signature is present, we
247
- // deposit via part A: createOrderAndFundByDepositor recovers the depositor FROM THE SIGNATURE over the quote
248
- // digest, so anyone may send the transaction (a relayer, our service) while the `locker` role (right to refund,
249
- // right to mark readiness) stays with the signer. Without a signature, the old createOrderAndFund: one wallet
250
- // deposits and sends, and the direct swap works as before. On neither path may the sender change the order terms
251
- // - both signatures cover THE SAME quote digest.
252
- if (depositorSignature !== null && depositorSignature !== undefined && depositorSignature !== "") {
253
- const shape = depositorSignatureShape(depositorSignature);
254
- if (!shape.ok) throw new Error("the depositor signature is unusable: " + shape.reason);
255
- }
256
- const relayedByDepositor = Boolean(depositorSignature);
257
- const createData = relayedByDepositor
258
- ? encodeCreateOrderAndFundByDepositor(quoteEnc, depositorSignature)
259
- : encodeCreateOrderAndFund(quoteEnc);
243
+ // WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH. There is ONE path: createOrderAndFund, where the wallet that
244
+ // sends the transaction becomes the locker. The sender may not change the order terms - the quote signature
245
+ // covers THE SAME digest the escrow checks.
246
+ const createData = encodeCreateOrderAndFund(quoteEnc);
260
247
  const createHash = await send({
261
248
  to: factory,
262
249
  data: createData,
@@ -300,8 +287,5 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
300
287
  // THE FEE GOES OUTWARD: it went into the transaction and the deal record must keep it - otherwise the record
301
288
  // cannot tell how much the person deposited, and it would diverge from what the chain shows.
302
289
  return { feeWei: fee.feeWei, feeTotalWei: totalValueWei, feeBps: fee.bps, feeWasLegacyFactory: Boolean(fee.legacy),
303
- // WHICH FACTORY PATH RAN: visible outside, not only in the wallet's calldata. By it the checks and the
304
- // deal record know whether the ETH returns to the signer and not to the sender.
305
- depositorPath: relayedByDepositor,
306
290
  escrow: escrowToUse, predictedEscrow: escrow, escrowMismatch: Boolean(actualEscrow && actualEscrow !== String(escrow).toLowerCase()), salt, birthBlock, terms, createHash, lockHash };
307
291
  }
@@ -32,9 +32,8 @@ import { fundOrder } from "./funding.js";
32
32
  export * from "./factory.js";
33
33
  export * from "./escrow.js";
34
34
  export * from "./funding.js";
35
- // The depositor signature form (evm/depositor.js) and the permit verdict (evm/permit.js) go through the SAME
36
- // engine seam: the page and the SDK share one protocol code, not two lookalikes.
37
- export * from "./depositor.js";
35
+ // The permit verdict (evm/permit.js) goes through the SAME engine seam: the page and the SDK share one
36
+ // protocol code, not two lookalikes.
38
37
  export * from "./permit.js";
39
38
  // THE ASYNCHRONOUS ROUTE, EXECUTED - through the same engine seam as the decision that chooses it. The page reaches
40
39
  // it through the bridge; the SDK carries the same module in its engine mirror (tools/build-sdk-engine.mjs).
@@ -87,13 +86,10 @@ export async function simulateEscrowFunding(tx) {
87
86
  // could not pass even the first step. Neither syntax nor checks saw this, because the live path is never run.
88
87
  // FOUR FIELDS AND TWO DEADLINES EXPLICITLY. The deadlines come ready (readyBy, t1), not computed from now:
89
88
  // they go into the context the proof binds to, so they must be fixed before it.
90
- export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, depositorSignature, onStep }) {
89
+ export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, onStep }) {
91
90
  return fundOrder({
92
91
  factory,
93
92
  quote, // signed quote: carries the fee, registry address and provider
94
- // THE DEPOSITOR SIGNATURE OVER THE QUOTE DIGEST. Present: funding goes via
95
- // createOrderAndFundByDepositor and anyone may send the transaction. Absent: the old path.
96
- depositorSignature,
97
93
  locker,
98
94
  claimer,
99
95
  commitHalfLocker,
@@ -22,8 +22,8 @@
22
22
  // cannot cover the gas - the swap settles first and the escrow is funded after it. See usdcWithoutEthVerdict below
23
23
  // and www/js/evm/asyncRoute.js - and it is a NAMED refusal only when no provider serves the network.
24
24
  //
25
- // THE DEPOSITOR SIGNATURE IS A DIFFERENT ALLOWANCE. It lets a relayer SEND the order-creation transaction, but
26
- // not pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
25
+ // A RELAYED ORDER NEEDS A SEPARATE TOKEN ALLOWANCE. Letting a relayer SEND the order-creation transaction does
26
+ // not let it pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
27
27
 
28
28
  // THE BALANCE-DRIVEN PATH CHOICE. Whether a provider serves a network ASYNCHRONOUSLY (an intent auction whose
29
29
  // settlement arrives later - see www/js/evm/asyncRoute.js) is what decides the path for a person with no own
@@ -179,7 +179,7 @@ export function usdcWithoutEthVerdict({
179
179
  if (!isAddr(orchestrator)) {
180
180
  return {
181
181
  ok: false, blocked: true, kind: "no-orchestrator", path: null,
182
- reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none. The depositor signature without it lets a relayer SEND the order creation, but not pull the token",
182
+ reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none",
183
183
  };
184
184
  }
185
185
  if (permitSupported === false) {
@@ -551,12 +551,10 @@ export async function signMessage(message) {
551
551
  }
552
552
 
553
553
  // SIGNING TYPED DATA with the wallet (EIP-712, eth_signTypedData_v4). This is also NOT a transaction: the
554
- // signature moves no money and costs no gas, so a person WITHOUT their own ETH can give it. With it the
555
- // depositor lets the factory record his address in the order, and anyone may send the transaction for him
556
- // (the createOrderAndFundByDepositor path).
554
+ // signature moves no money and costs no gas, so a wallet without its own ETH can give it.
557
555
  //
558
- // WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here (www/js/evm/depositor.js),
559
- // not a string. Assembling the shape here would be a second record of the protocol next to the real one.
556
+ // WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here as an object, not a string.
557
+ // Assembling the shape here would be a second record of the protocol next to the real one.
560
558
  export async function signTypedData(typedData) {
561
559
  if (!provider || state.status !== "connected") {
562
560
  throw new Error("wallet not connected: typed-data signing is impossible");
@@ -68,7 +68,7 @@ import { height as nodeHeightValue } from "../monero/node.js";
68
68
 
69
69
  // THE CORE ADDRESS AS A STRING - THE BUILT BUNDLE UNDER www/: the page gets the core as ONE module, not a pile
70
70
  // of sdk/ sources; ?v=<hash> keeps the cache fresh.
71
- const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=3ce64c53";
71
+ const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=69d79c13";
72
72
 
73
73
  // A NAMED SEAM FOR CHECKS - THE SAME TECHNIQUE AS sdkBookUse BELOW. The asynchronous path is driven against a
74
74
  // stand-in order book (the demo network has no provider), and the ordinary escrow funding that follows a settled
package/index.d.ts CHANGED
@@ -494,6 +494,58 @@ 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.
504
+ *
505
+ * These are OUR codes, and they are what a person's message should be phrased from. A provider's OWN reason
506
+ * stays opaque on purpose (`QuoteRefusal.why`): that is data the provider sent, not a name of ours, so it is
507
+ * deliberately NOT part of this union.
508
+ */
509
+ export type RouteRefusalCode =
510
+ // cow
511
+ | "cow-bad-order"
512
+ | "cow-bad-response"
513
+ | "cow-not-settled"
514
+ | "cow-order-unknown"
515
+ | "cow-refused"
516
+ | "cow-sign-failed"
517
+ | "cow-unknown-network"
518
+ | "cow-unreachable"
519
+ // kyber
520
+ | "kyber-bad-order"
521
+ | "kyber-bad-response"
522
+ | "kyber-not-settled"
523
+ | "kyber-order-unknown"
524
+ | "kyber-refused"
525
+ | "kyber-sign-failed"
526
+ | "kyber-unknown-network"
527
+ | "kyber-unreachable"
528
+ // uniswapx
529
+ | "uniswapx-bad-order"
530
+ | "uniswapx-bad-response"
531
+ | "uniswapx-no-submitter"
532
+ | "uniswapx-not-settled"
533
+ | "uniswapx-order-unknown"
534
+ | "uniswapx-refused"
535
+ | "uniswapx-sign-failed"
536
+ | "uniswapx-unknown-network"
537
+ | "uniswapx-unreachable"
538
+ // uniswap-relay
539
+ | "uniswap-relay-bad-order"
540
+ | "uniswap-relay-bad-response"
541
+ | "uniswap-relay-no-discovery"
542
+ | "uniswap-relay-not-settled"
543
+ | "uniswap-relay-sign-failed"
544
+ | "uniswap-relay-unknown-network"
545
+ | "uniswap-relay-unreachable"
546
+ // shared
547
+ | "broken";
548
+
497
549
  export interface QuoteRefusal {
498
550
  readonly providerId: string;
499
551
  readonly displayName: string | null;
@@ -1373,6 +1425,7 @@ export declare class SdkError extends Error {
1373
1425
  }
1374
1426
 
1375
1427
  /** The step codes the SDK works with (`state.step`): the same list as the `StepCode` type. */
1428
+ export declare const SDK_VERSION: string;
1376
1429
  export declare const STEP_CODES: readonly StepCode[];
1377
1430
  /** The check codes a preflight verdict names (`checks[].code`): the same list as the `CheckCode` type. */
1378
1431
  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.40.0",
3
+ "version": "0.42.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -18,7 +18,9 @@
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:sweep": "node tools/sweep-refusal.test.mjs", "sync:version": "node tools/sync-version.mjs"
22
24
  },
23
25
  "types": "./index.d.ts",
24
26
  "exports": {
@@ -29,6 +31,26 @@
29
31
  },
30
32
  "./adapters": {
31
33
  "default": "./src/adapters/index.mjs"
34
+ },
35
+ "./xmr": {
36
+ "types": "./src/public/xmr.d.ts",
37
+ "default": "./src/public/xmr.mjs"
38
+ },
39
+ "./order": {
40
+ "types": "./src/public/order.d.ts",
41
+ "default": "./src/public/order.mjs"
42
+ },
43
+ "./evm": {
44
+ "types": "./src/public/evm.d.ts",
45
+ "default": "./src/public/evm.mjs"
46
+ },
47
+ "./core": {
48
+ "types": "./src/public/core.d.ts",
49
+ "default": "./src/public/core.mjs"
50
+ },
51
+ "./recovery": {
52
+ "types": "./src/public/recovery.d.ts",
53
+ "default": "./src/public/recovery.mjs"
32
54
  }
33
55
  },
34
56
  "files": [
@@ -40,7 +62,7 @@
40
62
  "LICENSE"
41
63
  ],
42
64
  "engines": {
43
- "node": ">=20"
65
+ "node": ">=22"
44
66
  },
45
67
  "keywords": [
46
68
  "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
@@ -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";