@ninsei-labs/sdk 0.41.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,29 @@
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
+
1
27
  ## 0.41.0 — 2026-10-11
2
28
 
3
29
  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,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.41.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";
@@ -0,0 +1,161 @@
1
+ // DECLARATIONS FOR THE `./order` ENTRY: what a signature commits to, and the quote types it commits under.
2
+ //
3
+ // src/public/order.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/atomic/orderContext.js`, `../../engine/atomic/orderHalfCommit.js`,
6
+ // `../quoteEip712Spec.mjs`), and a symbol the barrel does not re-export is not declared here.
7
+
8
+ /** The order conditions a context is built from. Numbers may be decimal text, hex text or a bigint: one order. */
9
+ export interface OrderContextFields {
10
+ /** The network: the same order on another network is another order. */
11
+ readonly chainId: string | number | bigint;
12
+ /** The factory that creates the escrow. */
13
+ readonly factory: string;
14
+ /** The address that deposits the settlement coin. */
15
+ readonly locker: string;
16
+ /** The address the contract pays. */
17
+ readonly claimer: string;
18
+ /** The amount in wei. */
19
+ readonly amount: string | number | bigint;
20
+ /** The readiness deadline. */
21
+ readonly readyBy: string | number | bigint;
22
+ /** The settlement deadline. */
23
+ readonly t1: string | number | bigint;
24
+ /** The order salt. */
25
+ readonly salt: string | number | bigint;
26
+ }
27
+
28
+ /**
29
+ * The prefix that separates the order context from any other signature or proof, and the version of its field
30
+ * set. It moves only together with that set, so a proof made under an older prefix never verifies under this one.
31
+ */
32
+ export declare const ORDER_CONTEXT_VERSION: string;
33
+
34
+ /**
35
+ * The canonical context string the DLEQ proof binds to, built from the order fields alone and identically in
36
+ * every implementation, so the proof can be checked from the on-chain order.
37
+ *
38
+ * Refuses (throws) an object without the fields: a partial set is not a context.
39
+ */
40
+ export declare function orderContextString(order: OrderContextFields): string;
41
+
42
+ /** The secp256k1 members the half commitment calls: the base point and its uncompressed encoding. */
43
+ export interface Secp256k1BasePoint {
44
+ readonly multiply: (scalar: bigint) => { readonly toBytes: (isCompressed: boolean) => Uint8Array };
45
+ }
46
+
47
+ /** The secp256k1 primitive, as the engine and the SDK's own `primitives` module hand it in. */
48
+ export interface Secp256k1Primitive {
49
+ readonly Point: { readonly BASE: Secp256k1BasePoint };
50
+ }
51
+
52
+ /** keccak256 over bytes - the same function Solidity's `keccak256` computes. */
53
+ export type Keccak256 = (bytes: Uint8Array) => Uint8Array;
54
+
55
+ /**
56
+ * The commitment to a half: `keccak256(x || y)` of the secp256k1 point `half*G`, taken UNCOMPRESSED (64 bytes,
57
+ * big-byte-first, like Ethereum key coordinates). The chain compares the presented half against the POINT behind
58
+ * this hash, not against a hash of the number, so this formula must match everywhere it is computed.
59
+ */
60
+ export declare function halfCommitmentHex(
61
+ half: bigint | string | number,
62
+ secp256k1: Secp256k1Primitive,
63
+ keccak256: Keccak256,
64
+ ): string;
65
+
66
+ /**
67
+ * The crypto the quote spec is bound to. The spec signs nothing itself: it is handed keccak256 and secp256k1 -
68
+ * the same two the SDK's `primitives` module exports.
69
+ */
70
+ export interface QuoteCrypto {
71
+ readonly keccak256: (bytes: Uint8Array) => Uint8Array;
72
+ readonly secp256k1: {
73
+ /** A signature over the digest, `format: "recovered"`, as 65 bytes [recovery, r, s]. */
74
+ readonly sign: (
75
+ message: Uint8Array,
76
+ privateKey: Uint8Array,
77
+ options?: { readonly format?: string; readonly prehash?: boolean },
78
+ ) => Uint8Array;
79
+ readonly Signature: {
80
+ readonly fromBytes: (bytes: Uint8Array) => {
81
+ readonly addRecoveryBit: (bit: number) => {
82
+ readonly recoverPublicKey: (message: Uint8Array) => { readonly toBytes: (isCompressed: boolean) => Uint8Array };
83
+ };
84
+ };
85
+ };
86
+ };
87
+ }
88
+
89
+ /**
90
+ * A signed quote: the fields the provider's signature covers, the factory the EIP-712 domain names, and the
91
+ * signature itself. Field names and values are the protocol's; a missing field is refused, never read as zero.
92
+ */
93
+ export interface OrderQuote {
94
+ readonly provider: string;
95
+ readonly quoteKey: string;
96
+ readonly registry: string;
97
+ readonly cashier: string;
98
+ readonly locker: string;
99
+ readonly claimer: string;
100
+ readonly amount: string | number | bigint;
101
+ readonly readyBy: string | number | bigint;
102
+ readonly t1: string | number | bigint;
103
+ readonly salt: string;
104
+ readonly commitHalfClaimer: string;
105
+ readonly edPointClaimer: string;
106
+ readonly chainId: string | number | bigint;
107
+ readonly feeBps: string | number | bigint;
108
+ readonly fee: string | number | bigint;
109
+ readonly feeRecipient: string;
110
+ readonly validUntil: string | number | bigint;
111
+ readonly nonce: string | number | bigint;
112
+ readonly xmrAmount: string | number | bigint;
113
+ /** The escrow factory the domain names as `verifyingContract`. */
114
+ readonly factory: string;
115
+ /** r || s || v over the digest, 65 bytes, with or without the `0x` prefix; v is normalised to 27/28. */
116
+ readonly signature: string;
117
+ }
118
+
119
+ /** The EIP-712 domain type string, assembled from its parts. */
120
+ export declare const EIP712_DOMAIN_TYPE: string;
121
+
122
+ /** The domain name and its version string: the domain's identity, not the message shape. */
123
+ export declare const ORDER_QUOTE_DOMAIN_NAME: string;
124
+ export declare const ORDER_QUOTE_DOMAIN_VERSION: string;
125
+
126
+ /** The request-shape generation label. It moves WITH the signed field set; the domain identity does not. */
127
+ export declare const ORDER_QUOTE_VERSION: number;
128
+
129
+ /** `[name, type]` pairs IN CONTRACT ORDER. The order is the protocol: a rearrangement moves every digest. */
130
+ export declare const ORDER_QUOTE_FIELDS: readonly (readonly [string, string])[];
131
+
132
+ /** The message type string, assembled from `ORDER_QUOTE_FIELDS`. */
133
+ export declare const ORDER_QUOTE_TYPE: string;
134
+
135
+ /**
136
+ * Names of the signed-set fields (`factory` and `signature` included) the quote does not carry. A non-empty
137
+ * answer means the quote is not verifiable at all.
138
+ */
139
+ export declare function quoteMissingFields(quote: Partial<OrderQuote> | null | undefined): string[];
140
+
141
+ /** The EIP-712 domain separator, with `chainId` and the factory taken live from the quote. */
142
+ export declare function quoteDomainSeparator(request: { readonly crypto: QuoteCrypto; readonly quote: OrderQuote }): Uint8Array;
143
+
144
+ /** The hash of the message struct. */
145
+ export declare function quoteStructHash(request: { readonly crypto: QuoteCrypto; readonly quote: OrderQuote }): Uint8Array;
146
+
147
+ /** The digest the provider's key signs: `0x1901` || domain separator || struct hash. */
148
+ export declare function quoteDigest(request: { readonly crypto: QuoteCrypto; readonly quote: OrderQuote }): Uint8Array;
149
+
150
+ /** The signature on the wire: r || s || v with v in {27, 28}, `0x`-prefixed. */
151
+ export declare function signQuoteDigest(request: {
152
+ readonly crypto: QuoteCrypto;
153
+ readonly digest: Uint8Array;
154
+ readonly privateKey: string;
155
+ }): string;
156
+
157
+ /**
158
+ * The address that signed the quote, recovered without knowing it beforehand, or null when the quote cannot be
159
+ * verified at all. Null is a refusal for the caller to name, never consent.
160
+ */
161
+ export declare function recoverQuoteKey(request: { readonly crypto: QuoteCrypto; readonly quote: OrderQuote }): string | null;
@@ -0,0 +1,22 @@
1
+ // THE ORDER SIDE: what a signature commits to, and the quote types it commits under.
2
+ //
3
+ // The canonical context string, the commitment on a half key, the EIP-712 quote types and digests. Same rule
4
+ // as ./xmr: names only, no second implementation, no browser.
5
+
6
+ export { ORDER_CONTEXT_VERSION, orderContextString } from "../../engine/atomic/orderContext.js";
7
+ export { halfCommitmentHex } from "../../engine/atomic/orderHalfCommit.js";
8
+
9
+ export {
10
+ EIP712_DOMAIN_TYPE,
11
+ ORDER_QUOTE_DOMAIN_NAME,
12
+ ORDER_QUOTE_DOMAIN_VERSION,
13
+ ORDER_QUOTE_FIELDS,
14
+ ORDER_QUOTE_TYPE,
15
+ ORDER_QUOTE_VERSION,
16
+ quoteDigest,
17
+ quoteDomainSeparator,
18
+ quoteMissingFields,
19
+ quoteStructHash,
20
+ recoverQuoteKey,
21
+ signQuoteDigest,
22
+ } from "../quoteEip712Spec.mjs";
@@ -0,0 +1,243 @@
1
+ // DECLARATIONS FOR THE `./recovery` ENTRY: the recovery file as it travels - the envelope a wallet writes and a
2
+ // sweep page reads back.
3
+ //
4
+ // src/public/recovery.mjs only NAMES what the engine already offers; these declarations NAME the same thing for a
5
+ // TypeScript consumer. Every signature is copied from the module the barrel re-exports
6
+ // (`engine/recovery/recoveryFile.js`, itself a generated byte-for-byte copy of the engine source), and a symbol
7
+ // the barrel does not re-export is not declared here.
8
+ //
9
+ // NO DOM AND NO ARGUMENTS: the module touches WebCrypto (`crypto.subtle`), `btoa`/`atob` and the text encoders,
10
+ // so the environment hands those in and nothing has to be passed as a parameter here.
11
+
12
+ /**
13
+ * The envelope version (encryption) - NOT the payload version inside it. 1 is a file written without AAD, 2 is a
14
+ * file whose header is authenticated. Both stay readable: people hold version-1 files, and a recovery file is the
15
+ * only way to claim their XMR.
16
+ */
17
+ export declare const ENVELOPE_VERSION: number;
18
+
19
+ /** The payload version (deal content), raised independently of the envelope: 1 keys as fields, 2 the mnemonic, 3 halves. */
20
+ export declare const PAYLOAD_VERSION: number;
21
+
22
+ /** The mark every Ninsei recovery file carries in its payload, so a foreign file is refused by name. */
23
+ export declare const FILE_MAGIC: string;
24
+
25
+ /** The deal fields of the envelope header that sit under authentication (`meta`). Each one is optional: a header without `meta` is still a header. */
26
+ export interface RecoveryEnvelopeMeta {
27
+ /** The deal id the file belongs to. */
28
+ readonly id?: string | null;
29
+ /** The side of the deal the file was issued for. */
30
+ readonly side?: string | null;
31
+ /** When the file was written, as the deal record carries it. */
32
+ readonly createdAt?: string | number | null;
33
+ /** The order terms hash from the contract: the file's tie to the chain. */
34
+ readonly termsHash?: string | null;
35
+ }
36
+
37
+ /** The envelope header BEFORE encryption: the same fields also form the AAD of the ciphertext, which is why the writer and the reader build them from one place. */
38
+ export interface RecoveryEnvelopeHeader {
39
+ readonly v: number;
40
+ /** The algorithm identifier: one string for the whole module. */
41
+ readonly alg: string;
42
+ /** The PBKDF2 iteration count, read FROM THE FILE on decryption so it can be raised without losing already issued files. */
43
+ readonly iter: number;
44
+ /** The PBKDF2 salt, base64. */
45
+ readonly salt: string;
46
+ /** The AES-GCM IV, base64. */
47
+ readonly iv: string;
48
+ }
49
+
50
+ /** The recovery file object as it is written (`ninsei-<id>.json.enc`) and read back. */
51
+ export interface RecoveryEnvelope extends RecoveryEnvelopeHeader {
52
+ /** The AES-GCM ciphertext of the payload, base64. */
53
+ readonly ciphertext: string;
54
+ /** The authenticated deal fields of the header. */
55
+ readonly meta?: RecoveryEnvelopeMeta | null;
56
+ }
57
+
58
+ /**
59
+ * The AAD string: the header fields, in one FIXED order, that AES-GCM authenticates beside the ciphertext.
60
+ *
61
+ * Exported on purpose - the string is part of the format, so a check builds it with this same code instead of its
62
+ * own copy. An absent `meta` contributes nulls; any edit to a header field changes the string and decryption is
63
+ * refused.
64
+ */
65
+ export declare function envelopeAad(envelope: RecoveryEnvelope): string;
66
+
67
+ /** The heights a recovery scan may start from. All three are optional; a source that did not answer is simply not a source. */
68
+ export interface RecoveryScanHeightRequest {
69
+ /** The escrow's birth height, from the creation receipt. */
70
+ readonly escrowBirthHeight?: string | number | null;
71
+ /** The height the application stored when it registered the watch - the source that survives a deal record without one. */
72
+ readonly serverRestoreHeight?: string | number | null;
73
+ /** The Monero node's current height. */
74
+ readonly nodeHeight?: string | number | null;
75
+ }
76
+
77
+ /**
78
+ * The height to start the recovery scan from: the smallest KNOWN height, one block lower and never below 1, or null
79
+ * when no source answered. A scan that starts below the money finds it; one that starts above never does.
80
+ */
81
+ export declare function scanStartHeight(request?: RecoveryScanHeightRequest): number | null;
82
+
83
+ /** One side of the payment the payload carries. */
84
+ export interface RecoveryPayment {
85
+ readonly token?: string | null;
86
+ readonly amount?: string | number | null;
87
+ }
88
+
89
+ /** What the deal receives: XMR, at the address the file was issued for. */
90
+ export interface RecoveryReceive {
91
+ readonly token?: string | null;
92
+ readonly amount?: string | number | null;
93
+ readonly address?: string | null;
94
+ }
95
+
96
+ /** The signed quote as the payload carries it: the signature, and the last eight characters of it as the nonce. */
97
+ export interface RecoveryQuote {
98
+ readonly signature?: string | null;
99
+ readonly nonce?: string | null;
100
+ }
101
+
102
+ /** The escrow of the deal, as the deal record carries it. Fields beyond these travel through unchanged. */
103
+ export interface RecoveryEscrow {
104
+ readonly address?: string | null;
105
+ readonly termsHash?: string | null;
106
+ readonly [key: string]: unknown;
107
+ }
108
+
109
+ /** A wallet built from HALVES (payload v3): no full spend key exists for anyone, so there is no mnemonic here. */
110
+ export interface RecoveryWalletHalves {
111
+ readonly source?: string | null;
112
+ readonly library?: string | null;
113
+ readonly network?: string | null;
114
+ readonly address?: string | null;
115
+ readonly subaddress?: string | null;
116
+ /** MY half of the spend key. */
117
+ readonly spendHalf?: string | null;
118
+ /** MY half of the view key. */
119
+ readonly viewHalf?: string | null;
120
+ /** The counterparty's public spend point. */
121
+ readonly otherSpendPoint?: string | null;
122
+ /** The counterparty's public view point. */
123
+ readonly otherViewPoint?: string | null;
124
+ /** The counterparty's view HALF - not a secret: the full view key sees funds and cannot spend them. */
125
+ readonly otherViewHalf?: string | null;
126
+ /** The escrow address the revealed half is read from. */
127
+ readonly escrowAddress?: string | null;
128
+ readonly restoreHeight?: number | null;
129
+ }
130
+
131
+ /** A wallet built from a MNEMONIC (payload v1/v2): the phrase is the single source of both keys. */
132
+ export interface RecoveryWalletMnemonic {
133
+ readonly source?: string | null;
134
+ readonly library?: string | null;
135
+ readonly network?: string | null;
136
+ readonly address?: string | null;
137
+ readonly subaddress?: string | null;
138
+ readonly mnemonic?: string | null;
139
+ readonly publicSpendKey?: string | null;
140
+ readonly publicViewKey?: string | null;
141
+ readonly restoreHeight?: number | null;
142
+ }
143
+
144
+ /** The Monero wallet data of the payload. Which shape it is follows the payload version. */
145
+ export type RecoveryWallet = RecoveryWalletHalves | RecoveryWalletMnemonic;
146
+
147
+ /**
148
+ * The payload inside the ciphertext: everything needed to complete or roll back the deal without the site.
149
+ *
150
+ * The fields are OPTIONAL on purpose - a file issued earlier carries fewer of them and must still read, and an
151
+ * unknown extra key is tolerated rather than refused. `magic`, `swapId`, `moneroWallet` and a supported `version`
152
+ * are what validation requires, and it names the one that is missing.
153
+ */
154
+ export interface RecoveryPayload {
155
+ readonly magic?: string;
156
+ readonly version?: number;
157
+ readonly swapId?: string;
158
+ readonly termsHash?: string | null;
159
+ readonly side?: string | null;
160
+ readonly network?: string | null;
161
+ readonly moneroNetwork?: string | null;
162
+ readonly createdAt?: string | number | null;
163
+ readonly pay?: RecoveryPayment | null;
164
+ readonly receive?: RecoveryReceive | null;
165
+ readonly rate?: string | number | null;
166
+ readonly maker?: string | null;
167
+ readonly quote?: RecoveryQuote | null;
168
+ readonly escrow?: RecoveryEscrow | null;
169
+ readonly moneroWallet?: RecoveryWallet | null;
170
+ /** The deal's timeline, taken from the record unchanged. */
171
+ readonly deadlines?: unknown;
172
+ readonly recoveryClient?: string;
173
+ readonly [key: string]: unknown;
174
+ }
175
+
176
+ /** The deal record the payload is built from. Only the fields `buildPayload` reads are named here. */
177
+ export interface RecoverySwapRecord {
178
+ readonly id: string;
179
+ readonly side?: string | null;
180
+ readonly network?: string | null;
181
+ readonly moneroNetwork?: string | null;
182
+ readonly createdAt?: string | number | null;
183
+ readonly payToken?: string | null;
184
+ readonly payAmount?: string | number | null;
185
+ readonly xmrAmount?: string | number | null;
186
+ readonly rate?: string | number | null;
187
+ readonly maker?: string | null;
188
+ readonly quoteSignature?: string | null;
189
+ readonly escrow?: RecoveryEscrow | null;
190
+ readonly timeline?: unknown;
191
+ readonly [key: string]: unknown;
192
+ }
193
+
194
+ /** Everything `buildPayload` assembles the payload from. `escrow` is the sender's COPY, not necessarily the one on the deal. */
195
+ export interface RecoveryBuildRequest {
196
+ readonly swap: RecoverySwapRecord;
197
+ /** The wallet the file is issued for; without it no wallet block is written at all. */
198
+ readonly swapWallet?: RecoveryWallet | null;
199
+ readonly escrow?: RecoveryEscrow | null;
200
+ readonly receiveAddress?: string | null;
201
+ readonly restoreHeight?: number | null;
202
+ }
203
+
204
+ /**
205
+ * The payload of one deal: the deal fields, the escrow, the wallet data and the deadlines.
206
+ *
207
+ * The version tells the truth about the CONTENT, not about the module: a halves wallet is 3 and a mnemonic wallet
208
+ * is 2, whatever version this build happens to be.
209
+ */
210
+ export declare function buildPayload(request: RecoveryBuildRequest): RecoveryPayload;
211
+
212
+ /** The verdict of a successful validation: the warnings a sweep page should show, each one naming what is missing. */
213
+ export interface RecoveryValidation {
214
+ readonly warnings: readonly string[];
215
+ }
216
+
217
+ /**
218
+ * Validates a payload AFTER decryption: garbage can decrypt too, so the magic, the version, the deal id and the
219
+ * wallet fields are each checked, and a refusal throws with a clear cause - and never with a secret in its text.
220
+ */
221
+ export declare function validatePayload(payload: RecoveryPayload): RecoveryValidation;
222
+
223
+ /**
224
+ * Encrypts one payload into a recovery file envelope: a fresh salt and IV, the key derived from the passphrase, and
225
+ * the header authenticated as AAD. A passphrase shorter than 12 characters is refused.
226
+ */
227
+ export declare function encryptPayload(payload: RecoveryPayload, passphrase: string): Promise<RecoveryEnvelope>;
228
+
229
+ /**
230
+ * Decrypts a recovery file: the envelope version, the algorithm, the required fields and the iteration count are
231
+ * checked BEFORE the key is derived, a version-1 file is read without AAD, and the payload is validated before it
232
+ * is returned. A wrong password and an altered file are both named refusals, never a silent failure.
233
+ */
234
+ export declare function decryptFile(fileObject: unknown, passphrase: string): Promise<RecoveryPayload>;
235
+
236
+ /** The whole file as TEXT, ready to be written: the envelope, pretty-printed, with BigInt key halves as hex strings. */
237
+ export declare function createRecoveryFile(payload: RecoveryPayload, passphrase: string): Promise<string>;
238
+
239
+ /**
240
+ * The name the file is written under: lowercase, tied to the deal, `ninsei-pending.json.enc` while there is no id
241
+ * yet. Stable, so a repeated download shows up as a second copy of one name rather than a new random file.
242
+ */
243
+ export declare function recoveryFileName(swapId?: string | null): string;
@@ -0,0 +1,19 @@
1
+ // THE RECOVERY FILE AS IT TRAVELS: the envelope a wallet writes and a sweep page reads back.
2
+ //
3
+ // A sweep page opens a file the user was given earlier and has to prove it is the file this protocol wrote, take
4
+ // the keys out of it and start scanning from the right height. Those rules live in the engine next to this file;
5
+ // this entry only NAMES them, so the page does not keep a copy of them to stay in step with the package.
6
+
7
+ export {
8
+ ENVELOPE_VERSION,
9
+ FILE_MAGIC,
10
+ PAYLOAD_VERSION,
11
+ buildPayload,
12
+ createRecoveryFile,
13
+ decryptFile,
14
+ encryptPayload,
15
+ envelopeAad,
16
+ recoveryFileName,
17
+ scanStartHeight,
18
+ validatePayload,
19
+ } from "../../engine/recovery/recoveryFile.js";
@@ -0,0 +1,318 @@
1
+ // DECLARATIONS FOR THE `./xmr` ENTRY: the Monero side for a consumer that is not the browser.
2
+ //
3
+ // src/public/xmr.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/monero/address.js`, `../../engine/atomic/halves.js`, `../../engine/atomic/dleq2.js`,
6
+ // `../../engine/monero/halvesSweep.js`, `../../engine/core/sellWindow.js`), and a symbol the barrel does not
7
+ // re-export is not declared here.
8
+ //
9
+ // THE PRIMITIVES ARE HANDED IN, NOT IMPORTED. These modules pull no dependencies of their own: the curve and hash
10
+ // libraries arrive as arguments, so the types below describe the MEMBERS THE ENGINE CALLS. A real
11
+ // `@noble/curves`/`@noble/hashes` object satisfies them.
12
+
13
+ /** The Monero networks this package can build an address for, and check one against. */
14
+ export type MoneroNetwork = "mainnet" | "stagenet" | "testnet";
15
+
16
+ /** The three shapes of a Monero address. */
17
+ export type MoneroAddressKind = "primary" | "integrated" | "subaddress";
18
+
19
+ /**
20
+ * Network and kind -> prefix byte, the reverse table an address is assembled from.
21
+ *
22
+ * `fakechain` (monerod's own name for regtest) is deliberately absent: the withdrawal page picks that network up
23
+ * from the node, and there is no prefix byte to build an address with.
24
+ */
25
+ export declare const PREFIX_BY_NETWORK: Readonly<Record<MoneroNetwork, Readonly<Record<MoneroAddressKind, number>>>>;
26
+
27
+ /** The prefix byte of one network and kind. An unknown network or kind is refused, not guessed. */
28
+ export declare function prefixFor(network: MoneroNetwork, kind?: MoneroAddressKind): number;
29
+
30
+ /**
31
+ * The quick SHAPE check: base58 alphabet, 95 (plain) or 106 (integrated) characters, and a first letter that
32
+ * belongs to a Monero prefix. It does NOT verify the checksum - any typo inside passes here. The real check is
33
+ * `checkAddress`; this one only screens out obvious garbage before the heavy decode.
34
+ */
35
+ export declare function formatCheck(addr: string | null | undefined): boolean;
36
+
37
+ /** The network an address's first letter names, or null when the shape is not a Monero address at all. */
38
+ export declare function networkFromShape(addr: string | null | undefined): MoneroNetwork | null;
39
+
40
+ /** Whether the address is shaped as a stagenet one. */
41
+ export declare function isStagenetShaped(addr: string | null | undefined): boolean;
42
+
43
+ /**
44
+ * Block-based Monero base58: a block is a NUMBER, full 8-byte blocks come first (11 characters each) and the
45
+ * partial remainder goes LAST, so this is not bitcoin-base58 and `bs58` does not fit.
46
+ */
47
+ export declare function decodeMoneroBase58(str: string): Uint8Array;
48
+
49
+ /** The inverse of `decodeMoneroBase58`: bytes to a Monero base58 string. */
50
+ export declare function encodeMoneroBase58(bytes: Uint8Array | readonly number[]): string;
51
+
52
+ /** The address payload without its checksum: prefix + public spend + public view [+ payment id]. */
53
+ export interface AddressPayloadRequest {
54
+ readonly network: MoneroNetwork;
55
+ readonly kind?: MoneroAddressKind;
56
+ /** The 32-byte public spend key, hex. */
57
+ readonly spendPub: string;
58
+ /** The 32-byte public view key, hex. */
59
+ readonly viewPub: string;
60
+ /** The 8-byte payment id, hex: required for `kind: "integrated"`. */
61
+ readonly paymentId?: string;
62
+ }
63
+
64
+ /** The address payload: prefix, both public keys, and the payment id when the address is integrated. */
65
+ export declare function addressPayload(request: AddressPayloadRequest): Uint8Array;
66
+
67
+ /** keccak256 over bytes. The address checksum is its first four bytes, as in Monero itself. */
68
+ export type Keccak256Bytes = (bytes: Uint8Array) => Uint8Array;
69
+
70
+ /** What assembling an address needs: keccak256, and a Buffer shim for implementations taking only their own. */
71
+ export interface AddressKeysDeps {
72
+ readonly keccak256: Keccak256Bytes;
73
+ readonly Buffer?: { readonly from: (bytes: Uint8Array) => Uint8Array };
74
+ }
75
+
76
+ /** The address string built from public keys, with the checksum appended. */
77
+ export declare function addressFromKeys(request: AddressPayloadRequest, deps: AddressKeysDeps): string;
78
+
79
+ /**
80
+ * The full check: format, block decode, keccak256 checksum and network.
81
+ *
82
+ * `verified` says whether the checksum could be verified at all: without the Monero bundle the answer is
83
+ * `ok: true, verified: false` with the reason named, never a false "checked". A refusal always names WHY.
84
+ */
85
+ export type AddressCheck =
86
+ | { readonly ok: false; readonly reason: string }
87
+ | { readonly ok: true; readonly verified: false; readonly network: MoneroNetwork | null; readonly reason: string }
88
+ | { readonly ok: true; readonly verified: true; readonly network: MoneroNetwork; readonly kind: MoneroAddressKind; readonly bytes: number };
89
+
90
+ /** The full check of one address. Refuses an address of another network when `expectNetwork` names one. */
91
+ export declare function checkAddress(addr: string, options?: { readonly expectNetwork?: MoneroNetwork }): Promise<AddressCheck>;
92
+
93
+ /** An address of the right network, built from the wallet's own keys. Needs the Monero bundle for keccak256. */
94
+ export declare function addressForNetwork(request: AddressPayloadRequest): Promise<string>;
95
+
96
+ /** A curve point, as the halves and the DLEQ modules call it. */
97
+ export interface CurvePoint {
98
+ readonly multiply: (scalar: bigint) => CurvePoint;
99
+ readonly add: (other: CurvePoint) => CurvePoint;
100
+ readonly toHex: () => string;
101
+ }
102
+
103
+ /** The curve members the halves module calls. */
104
+ export interface CurvePrimitive {
105
+ readonly Point: {
106
+ readonly BASE: CurvePoint;
107
+ /** The group order: the smaller of the two is the limit a half must fit into. */
108
+ readonly Fn: { readonly ORDER: bigint };
109
+ readonly fromHex: (hex: string) => CurvePoint;
110
+ };
111
+ }
112
+
113
+ /** The five primitives the halves module is handed. */
114
+ export interface HalvesPrimitives {
115
+ readonly ed25519: CurvePrimitive;
116
+ readonly secp256k1: CurvePrimitive;
117
+ readonly keccak256: Keccak256Bytes;
118
+ readonly randomBytes: (length: number) => Uint8Array;
119
+ }
120
+
121
+ /** The halves module: one half of a key per side, public on both curves. */
122
+ export interface Halves {
123
+ /** The smaller of the two group orders: a half must fit into both groups. */
124
+ readonly LIMIT: bigint;
125
+ readonly ED_ORDER: bigint;
126
+ readonly SECP_ORDER: bigint;
127
+ /** The domain strings of the spend half and the view half: quoting and payout take them from here. */
128
+ readonly HALF_DOMAIN: string;
129
+ readonly VIEW_HALF_DOMAIN: string;
130
+ /** A uniform scalar below the limit, drawn by rejection sampling. */
131
+ readonly newHalf: () => bigint;
132
+ /**
133
+ * The half derived from a secret and the order binding, so the same order always gives the same half and only
134
+ * the secret holder can compute it.
135
+ */
136
+ readonly halfFromSeed: (seedBytes: Uint8Array, binding: string | number | bigint, domain?: string) => bigint;
137
+ /** The public halves on both curves. */
138
+ readonly publicHalves: (half: bigint | string | number) => { readonly ed: string; readonly secp: string };
139
+ /** The sum of two halves, privately. */
140
+ readonly combineHalves: (a: bigint | string | number, b: bigint | string | number) => bigint;
141
+ /** The sum of two halves, publicly (point addition). */
142
+ readonly combinePublic: (edHexA: string, edHexB: string) => string;
143
+ }
144
+
145
+ /** Creates the halves module from the five primitives. Each one is required, and its absence is named. */
146
+ export declare function createHalves(primitives: HalvesPrimitives): Halves;
147
+
148
+ /** A curve point, as the DLEQ module calls it. */
149
+ export interface Dleq2Point {
150
+ readonly multiply: (scalar: bigint) => Dleq2Point;
151
+ readonly add: (other: Dleq2Point) => Dleq2Point;
152
+ readonly subtract: (other: Dleq2Point) => Dleq2Point;
153
+ readonly equals: (other: Dleq2Point) => boolean;
154
+ readonly toHex: () => string;
155
+ /** True when the point is in the prime-order subgroup (`[l]*P = 0`), not merely free of a small component. */
156
+ readonly isTorsionFree: () => boolean;
157
+ /** Builds the multiplication table of the given window size at the first multiplication. */
158
+ readonly precompute: (windowSize: number) => void;
159
+ }
160
+
161
+ /** The curve members the DLEQ module calls. */
162
+ export interface Dleq2CurvePrimitive {
163
+ readonly Point: {
164
+ readonly BASE: Dleq2Point;
165
+ readonly Fn: { readonly ORDER: bigint };
166
+ readonly fromHex: (hex: string) => Dleq2Point;
167
+ };
168
+ }
169
+
170
+ /** The primitives the DLEQ module is handed. SHA3-512 is mandatory: the challenge is its wide reduction. */
171
+ export interface Dleq2Primitives {
172
+ readonly ed25519: Dleq2CurvePrimitive;
173
+ readonly secp256k1: Dleq2CurvePrimitive;
174
+ readonly keccak256: Keccak256Bytes;
175
+ /** SHA3-512, NOT keccak256: the challenge is a 64-byte hash read least-significant byte first. */
176
+ readonly sha3_512: (bytes: Uint8Array) => Uint8Array;
177
+ readonly randomBytes: (length: number) => Uint8Array;
178
+ }
179
+
180
+ /** The OR proof of one bit on both curves. Scalars and points travel as hex, so the proof survives JSON. */
181
+ export interface Dleq2Signature {
182
+ readonly eA: string;
183
+ readonly eB: string;
184
+ readonly a0: string;
185
+ readonly a1: string;
186
+ readonly b0: string;
187
+ readonly b1: string;
188
+ }
189
+
190
+ /** The proof of one bit: the bit's commitments on both curves and the ring signature over them. */
191
+ export interface Dleq2ProofPart {
192
+ readonly cA: string;
193
+ readonly cB: string;
194
+ readonly sig: Dleq2Signature;
195
+ }
196
+
197
+ /** The proof: the point X on both curves, and one part per bit of the smaller group order. */
198
+ export interface Dleq2Proof {
199
+ readonly XA: string;
200
+ readonly XB: string;
201
+ readonly proofs: readonly Dleq2ProofPart[];
202
+ }
203
+
204
+ /** One bit prepared for proving: the bit, its commitments and its randomness. The half is NOT here. */
205
+ export interface Dleq2PreparedBit {
206
+ readonly i: number;
207
+ readonly bit: 0 | 1;
208
+ readonly cA: string;
209
+ readonly cB: string;
210
+ readonly rA: string;
211
+ readonly rB: string;
212
+ }
213
+
214
+ /** Step 1's output: the points and one prepared bit per bit. Depends on the running sum, so it is one piece. */
215
+ export interface Dleq2Prepared {
216
+ readonly XA: string;
217
+ readonly XB: string;
218
+ readonly context: string;
219
+ readonly bits: readonly Dleq2PreparedBit[];
220
+ }
221
+
222
+ /** The verifier's answer. A refusal NAMES the bit and the reason; it never throws. */
223
+ export interface Dleq2Verdict {
224
+ readonly ok: boolean;
225
+ readonly reason?: string;
226
+ }
227
+
228
+ /** One of the two curves the module holds, with the alternate base point its ring signature uses. */
229
+ export interface Dleq2Curve {
230
+ readonly name: string;
231
+ readonly P: Dleq2CurvePrimitive["Point"];
232
+ readonly order: bigint;
233
+ readonly G: Dleq2Point;
234
+ readonly Galt: Dleq2Point;
235
+ }
236
+
237
+ /** The DLEQ module: proving is assembled from three steps, and the steps can be run apart. */
238
+ export interface Dleq2 {
239
+ /** The bit length of the smaller order: the number of proof parts an honest proof carries. */
240
+ readonly BITS: number;
241
+ readonly curves: { readonly A: Dleq2Curve; readonly B: Dleq2Curve };
242
+ /** The whole proof. `onBit` reports progress every 16 bits and at the last. */
243
+ readonly prove: (x: bigint | string | number, context: string, onBit?: (done: number, total: number) => void) => Dleq2Proof;
244
+ /** Verifies a proof of untrusted shape: form is checked before counting, and every refusal is named. */
245
+ readonly verify: (proof: unknown, context: string) => Dleq2Verdict;
246
+ /** Step 1: the commitments. Sequential by construction. */
247
+ readonly prepareProof: (x: bigint | string | number, context: string) => Dleq2Prepared;
248
+ /** Step 2: one bit, on the prepared data alone - it needs neither the half nor the other bits. */
249
+ readonly proveBit: (part: Dleq2PreparedBit, context: string) => Dleq2ProofPart;
250
+ /** Step 3: the parts are put in bit order, and a missing bit is refused. */
251
+ readonly assembleProof: (prepared: Dleq2Prepared, parts: readonly Dleq2ProofPart[]) => Dleq2Proof;
252
+ /** The strict subgroup check. secp256k1 has cofactor 1 and passes without a check. */
253
+ readonly torsionFree: (curve: Dleq2Curve, point: Dleq2Point) => boolean;
254
+ }
255
+
256
+ /** Creates the DLEQ module from the five primitives. Without SHA3-512 it refuses immediately. */
257
+ export declare function createDleq2(primitives: Dleq2Primitives): Dleq2;
258
+
259
+ /**
260
+ * What sweeping from a revealed half is handed: the half maths, this side's two halves as the file carries them,
261
+ * the counterparty's public spend key and view half, and the half the counterparty revealed.
262
+ */
263
+ export interface SpendKeysFromRevealedHalfRequest {
264
+ readonly halves: Halves;
265
+ /** This side's spend half, from the file: it arrives as a string and is converted before any arithmetic. */
266
+ readonly ownSpendHalf: string | number | bigint;
267
+ /** This side's view half, from the file. */
268
+ readonly ownViewHalf: string | number | bigint;
269
+ /** The counterparty's public spend key, hex: the point the order carried. */
270
+ readonly otherSpendPoint: string;
271
+ /** The counterparty's view half, hex. */
272
+ readonly otherViewHalf: string | number | bigint;
273
+ /** The revealed half, hex. */
274
+ readonly revealedHalf: string | number | bigint;
275
+ }
276
+
277
+ /** The two private keys a sweep writes with, in Monero notation: 64 hex, least significant byte first. */
278
+ export interface SpendKeys {
279
+ readonly privateSpendKey: string;
280
+ readonly privateViewKey: string;
281
+ }
282
+
283
+ /**
284
+ * The spend and view keys a revealed half yields, or a THROW when the half does not belong to this order.
285
+ *
286
+ * The commitment only proves that the HASH of the revealed half matched the order, and ed25519 does not exist in
287
+ * the EVM, so the check that `(s_a + s_b)*G` equals the order's public spend key happens HERE, before any
288
+ * synchronisation: a half from another order, or a substituted one, yields no keys.
289
+ */
290
+ export declare function spendKeysFromRevealedHalf(request: SpendKeysFromRevealedHalfRequest): SpendKeys;
291
+
292
+ /**
293
+ * A Monero-notation key read as a number: 64 hex, least significant byte first. A key that is not in that notation
294
+ * is refused rather than reinterpreted - `BigInt("0x" + key)` silently reverses the byte order and gives another
295
+ * scalar.
296
+ */
297
+ export declare function scalarFromMoneroHex(hex: string): bigint;
298
+
299
+ /**
300
+ * The XMR sell window: the SOFT on-screen promise (how long the provider holds the price) and the HARD contract
301
+ * window, which goes into the order binding and can never be shorter than the contract's own.
302
+ */
303
+ export declare const SELL_WINDOW: {
304
+ /** The provider's on-screen promise, in seconds. */
305
+ readonly softSec: number;
306
+ /** The contract readiness window, in seconds: ten Monero confirmations, and the maximum that goes on chain. */
307
+ readonly hardSec: number;
308
+ };
309
+
310
+ /**
311
+ * How long the sell window lasts, given the named pool state: while the FULL expected amount is not visible in
312
+ * the pool the promise is extended to the contract maximum, and the contract window is a CEILING on it.
313
+ */
314
+ export declare function sellWindowSec(request?: {
315
+ readonly poolSeen?: boolean;
316
+ readonly soft?: number;
317
+ readonly hard?: number;
318
+ }): number;
@@ -0,0 +1,29 @@
1
+ // THE XMR SIDE, FOR A CONSUMER THAT IS NOT THE BROWSER.
2
+ //
3
+ // A node that quotes, signs and later reveals a half key needs the same arithmetic the page uses: an address
4
+ // built from two public points, the half keys, the DLEQ proof, the maker's sell window. All of it lives in the
5
+ // engine next to this file; this entry only NAMES it, so a node can depend on a released version of this
6
+ // package instead of reaching into another repository's source. Nothing is re-implemented here, and nothing
7
+ // here is browser-specific - these modules touch no window, document or storage.
8
+
9
+ export {
10
+ PREFIX_BY_NETWORK,
11
+ addressForNetwork,
12
+ addressFromKeys,
13
+ addressPayload,
14
+ checkAddress,
15
+ decodeMoneroBase58,
16
+ encodeMoneroBase58,
17
+ formatCheck,
18
+ isStagenetShaped,
19
+ networkFromShape,
20
+ prefixFor,
21
+ } from "../../engine/monero/address.js";
22
+
23
+ export { createHalves } from "../../engine/atomic/halves.js";
24
+ export { createDleq2 } from "../../engine/atomic/dleq2.js";
25
+
26
+ // THE SWEEP SIDE: the spend keys a revealed half yields, and the scalar a Monero hex string carries.
27
+ export { scalarFromMoneroHex, spendKeysFromRevealedHalf } from "../../engine/monero/halvesSweep.js";
28
+
29
+ export { SELL_WINDOW, sellWindowSec } from "../../engine/core/sellWindow.js";
@@ -0,0 +1,9 @@
1
+ // THE PACKAGE VERSION AS A VALUE, BAKED INTO THE CODE.
2
+ //
3
+ // A consumer that has to agree with a peer on a minor line - a node refusing a client built from a different
4
+ // minor - needs the number at runtime, and a page cannot fetch it: package.json is not part of a bundle and need
5
+ // not be served beside it. So the number is a constant, and this file is GENERATED from package.json by
6
+ // tools/sync-version.mjs. Do not edit it by hand - set the version in package.json and run `npm run sync:version`.
7
+ // The `version` check in tools/check.mjs refuses a mismatch, and refuses a runtime read of package.json here.
8
+
9
+ export const SDK_VERSION = "0.42.0";