@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 +37 -0
- package/README.md +11 -0
- package/engine/core/config.js +1 -1
- package/engine/evm/factory.js +0 -12
- package/engine/evm/funding.js +6 -22
- package/engine/evm/index.js +3 -7
- package/engine/evm/permit.js +3 -3
- package/engine/evm/session.js +3 -5
- package/engine/sdk/bridge.js +1 -1
- package/index.d.ts +53 -0
- package/package.json +25 -3
- package/src/index.mjs +1 -0
- package/src/public/core.d.ts +13 -0
- package/src/public/core.mjs +6 -0
- package/src/public/evm.d.ts +184 -0
- package/src/public/evm.mjs +29 -0
- package/src/public/order.d.ts +161 -0
- package/src/public/order.mjs +22 -0
- package/src/public/recovery.d.ts +243 -0
- package/src/public/recovery.mjs +19 -0
- package/src/public/xmr.d.ts +318 -0
- package/src/public/xmr.mjs +29 -0
- package/src/quoteEip712Spec.mjs +1 -1
- package/src/swap-flow.mjs +0 -7
- package/src/version.mjs +9 -0
- package/engine/evm/depositor.js +0 -123
|
@@ -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";
|
package/src/quoteEip712Spec.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// signature covers exactly the XMR the chain verifies at order birth. Earlier this field
|
|
23
23
|
// rode in the quote object OUTSIDE the digest on purpose - a field the chain could not
|
|
24
24
|
// see must not be signed; once the contract learned it, it moved in here together with
|
|
25
|
-
// the typehash, the
|
|
25
|
+
// the typehash, the order-quote form and the independent vector.
|
|
26
26
|
// commitHalfClaimer, - the provider's own commitment and ed25519 spend point: both are inputs to the
|
|
27
27
|
// edPointClaimer escrow's termsHash, so they are bound and cannot be swapped for another order's.
|
|
28
28
|
// chainId - bound in the domain AND in the message. A quote signed for one network does not
|
package/src/swap-flow.mjs
CHANGED
|
@@ -66,10 +66,6 @@ export function escrowCashierAddress(chainSlug) {
|
|
|
66
66
|
// The full flow. onStage(stage, detail) is called on every step - the interface must show what is going on.
|
|
67
67
|
export async function runAtomicOrder({
|
|
68
68
|
chainSlug, locker, claimer, amountWei, amountLabel, providerId = null,
|
|
69
|
-
// THE DEPOSITOR'S SIGNATURE (issue #97, parts A/C): when given, the order is placed through
|
|
70
|
-
// createOrderAndFundByDepositor - any wallet may send the transaction, while the signer is recorded as the
|
|
71
|
-
// depositor.
|
|
72
|
-
depositorSignature = null,
|
|
73
69
|
claimWindowSeconds = 3600, readyWindowSeconds = 14400, salt: providedSalt, onStage,
|
|
74
70
|
// THE SUBSTITUTION POINTS FOR THE SAKE OF THE CHECK, and they are not decoration. The live flow could be
|
|
75
71
|
// neither run in a check nor covered otherwise: it requires a browser Worker and a signature in the wallet.
|
|
@@ -261,9 +257,6 @@ export async function runAtomicOrder({
|
|
|
261
257
|
// (or from the caller in a simulation) and already participates in the context over which our half was
|
|
262
258
|
// computed.
|
|
263
259
|
factory, locker, claimer: order.claimer,
|
|
264
|
-
// WE PASS THE DEPOSITOR'S SIGNATURE FURTHER instead of losing it halfway: without it the funding would go
|
|
265
|
-
// the previous way, and the relayer would get a NotLocker refusal only after the person had signed.
|
|
266
|
-
depositorSignature,
|
|
267
260
|
// THE DEADLINES GO INTO THE CONTRACT EXACTLY THE ONES THAT ENTERED THE CONTEXT. This is not duplication but
|
|
268
261
|
// a condition: diverge by even a second - and the proof by which the provider confirms the half stops
|
|
269
262
|
// agreeing.
|
package/src/version.mjs
ADDED
|
@@ -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";
|
package/engine/evm/depositor.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/depositor.js for the SDK package.
|
|
2
|
-
// Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
|
|
3
|
-
// Everything below this header is identical to the source.
|
|
4
|
-
|
|
5
|
-
// THE DEPOSITOR BY SIGNATURE, CLIENT SIDE: EIP-712 typed data over the QUOTE DIGEST.
|
|
6
|
-
//
|
|
7
|
-
// WHY. The factory has a path createOrderAndFundByDepositor: ANYONE may send the transaction (a relayer, our
|
|
8
|
-
// service), but the depositor is whoever signed the quote digest, so the depositor must sign first. A wallet
|
|
9
|
-
// signature is typed data, not a transaction: it costs no gas and moves no money, so a person without ETH can give it.
|
|
10
|
-
//
|
|
11
|
-
// WHY THIS FORM, NOT AN INVENTED ONE. The field set and domain are PART OF THE PROTOCOL - the same OrderQuote
|
|
12
|
-
// the escrow verifies and the node signs. Two swapped fields give a DIFFERENT digest and the factory refuses
|
|
13
|
-
// BadDepositorSignature. So the form is ONE list below, and a check computes the digest with it and compares.
|
|
14
|
-
//
|
|
15
|
-
// DOMAIN. verifyingContract is the FACTORY ADDRESS FROM THE QUOTE (field `factory`), chainId is live: a signature
|
|
16
|
-
// made for another factory or network will not recover here. There is no second source of the domain.
|
|
17
|
-
//
|
|
18
|
-
// NO CRYPTOGRAPHY HERE: the wallet computes the digest (eth_signTypedData_v4). Here is only the form and its check.
|
|
19
|
-
|
|
20
|
-
import { SIGNING_DOMAIN } from "../core/config.js";
|
|
21
|
-
|
|
22
|
-
// The domain name and version are quote protocol v8. They are NOT brand literals here: they belong to the registry
|
|
23
|
-
// contract (it returns DOMAIN_SEPARATOR()), so they are read from the config. The defaults equal today's values.
|
|
24
|
-
export const DEPOSITOR_DOMAIN_NAME = SIGNING_DOMAIN.name;
|
|
25
|
-
export const DEPOSITOR_DOMAIN_VERSION = SIGNING_DOMAIN.version;
|
|
26
|
-
|
|
27
|
-
// FIELD SET IN CONTRACT ORDER. The order is part of the ABI: a swap gives another digest. One list feeds both
|
|
28
|
-
// the type string and the message, so they cannot diverge.
|
|
29
|
-
export const ORDER_QUOTE_TYPED_FIELDS = [
|
|
30
|
-
["provider", "address"],
|
|
31
|
-
["quoteKey", "address"],
|
|
32
|
-
["registry", "address"],
|
|
33
|
-
["cashier", "address"],
|
|
34
|
-
["locker", "address"],
|
|
35
|
-
["claimer", "address"],
|
|
36
|
-
["amount", "uint256"],
|
|
37
|
-
["readyBy", "uint64"],
|
|
38
|
-
["t1", "uint64"],
|
|
39
|
-
["salt", "bytes32"],
|
|
40
|
-
["commitHalfClaimer", "bytes32"],
|
|
41
|
-
["edPointClaimer", "bytes32"],
|
|
42
|
-
["chainId", "uint256"],
|
|
43
|
-
["feeBps", "uint256"],
|
|
44
|
-
["fee", "uint256"],
|
|
45
|
-
["feeRecipient", "address"],
|
|
46
|
-
["validUntil", "uint64"],
|
|
47
|
-
["nonce", "uint256"],
|
|
48
|
-
// THE XMR SIDE OF THE DEAL, in atomic units: it joined the signed set together with the contract
|
|
49
|
-
// (OrderQuote.sol), the node/SDK spec and the vector, so the depositor's signature covers the same XMR the chain
|
|
50
|
-
// verifies. A field the chain could not see used to stay OUT of this list on purpose - that is no longer the case.
|
|
51
|
-
["xmrAmount", "uint256"],
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// The message type. Built from the list: editing the list changes both the type and the signature, so it cannot
|
|
55
|
-
// go unnoticed.
|
|
56
|
-
export const ORDER_QUOTE_TYPED_TYPE = "OrderQuote(" + ORDER_QUOTE_TYPED_FIELDS.map(([n, t]) => t + " " + n).join(",") + ")";
|
|
57
|
-
|
|
58
|
-
const isHex = (v, bytes) => new RegExp("^(0x)?[0-9a-fA-F]{" + bytes * 2 + "}$").test(String(v === undefined || v === null ? "" : v).trim());
|
|
59
|
-
const isNum = (v) => /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(String(v === undefined || v === null ? "" : v).trim());
|
|
60
|
-
|
|
61
|
-
// FORM IS NORMALISED, NOT DEMANDED. The quote carries ed25519 points WITHOUT the 0x prefix, while the wallet and
|
|
62
|
-
// contract expect the same BYTES. So we normalise here and reject anything that is not address/bytes32/number.
|
|
63
|
-
function normaliseField(name, type, value) {
|
|
64
|
-
const raw = String(value === undefined || value === null ? "" : value).trim();
|
|
65
|
-
if (raw === "") throw new Error("the quote carries no depositor-signature field: " + name);
|
|
66
|
-
if (type === "address") {
|
|
67
|
-
if (!isHex(raw, 20)) throw new Error("field " + name + " is not an address: " + raw);
|
|
68
|
-
return "0x" + raw.replace(/^0x/, "").toLowerCase();
|
|
69
|
-
}
|
|
70
|
-
if (type === "bytes32") {
|
|
71
|
-
if (!isHex(raw, 32)) throw new Error("field " + name + " is not bytes32: " + raw);
|
|
72
|
-
return "0x" + raw.replace(/^0x/, "").toLowerCase();
|
|
73
|
-
}
|
|
74
|
-
// uint64 / uint256: the wallet accepts a string; decimal and hex are parsed the same.
|
|
75
|
-
if (!isNum(raw)) throw new Error("field " + name + " is not a number: " + raw);
|
|
76
|
-
return String(BigInt(raw));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* EIP-712 typed data that the depositor signs (eth_signTypedData_v4).
|
|
81
|
-
* The domain is built FROM THE QUOTE: verifyingContract = quote.factory, chainId = quote.chainId.
|
|
82
|
-
* @returns {{ domain: object, types: object, primaryType: string, message: object }}
|
|
83
|
-
*/
|
|
84
|
-
export function orderQuoteTypedData(quote) {
|
|
85
|
-
if (!quote || typeof quote !== "object") throw new Error("no signed quote: nothing to sign");
|
|
86
|
-
if (!isHex(quote.factory, 20)) throw new Error("the quote has no factory address - cannot build the signing domain");
|
|
87
|
-
if (!isNum(quote.chainId)) throw new Error("the quote has no chainId - cannot build the signing domain");
|
|
88
|
-
const message = {};
|
|
89
|
-
for (const [name, type] of ORDER_QUOTE_TYPED_FIELDS) {
|
|
90
|
-
message[name] = normaliseField(name, type, quote[name]);
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
domain: {
|
|
94
|
-
name: DEPOSITOR_DOMAIN_NAME,
|
|
95
|
-
version: DEPOSITOR_DOMAIN_VERSION,
|
|
96
|
-
chainId: Number(BigInt(quote.chainId)),
|
|
97
|
-
verifyingContract: "0x" + String(quote.factory).replace(/^0x/, "").toLowerCase(),
|
|
98
|
-
},
|
|
99
|
-
types: {
|
|
100
|
-
// EIP712Domain explicit: wallets show it to the person, and it must match what the chain computes.
|
|
101
|
-
EIP712Domain: [
|
|
102
|
-
{ name: "name", type: "string" },
|
|
103
|
-
{ name: "version", type: "string" },
|
|
104
|
-
{ name: "chainId", type: "uint256" },
|
|
105
|
-
{ name: "verifyingContract", type: "address" },
|
|
106
|
-
],
|
|
107
|
-
OrderQuote: ORDER_QUOTE_TYPED_FIELDS.map(([name, type]) => ({ name, type })),
|
|
108
|
-
},
|
|
109
|
-
primaryType: "OrderQuote",
|
|
110
|
-
message,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// SIGNATURE ON THE WIRE - 65 BYTES r || s || v. The form is checked HERE: the factory takes the signature as
|
|
115
|
-
// three words, and a "signature-like" string must not reach the contract as consent.
|
|
116
|
-
export function depositorSignatureShape(signature) {
|
|
117
|
-
const hex = String(signature === undefined || signature === null ? "" : signature).replace(/^0x/, "");
|
|
118
|
-
if (!/^[0-9a-fA-F]{130}$/.test(hex)) return { ok: false, reason: "the depositor signature must be 65 bytes (r || s || v), got " + hex.length / 2 + " bytes" };
|
|
119
|
-
let v = parseInt(hex.slice(128, 130), 16);
|
|
120
|
-
if (v < 27) v += 27;
|
|
121
|
-
if (v !== 27 && v !== 28) return { ok: false, reason: "depositor signature v outside {27,28}: " + v };
|
|
122
|
-
return { ok: true, r: "0x" + hex.slice(0, 64).toLowerCase(), s: "0x" + hex.slice(64, 128).toLowerCase(), v: 27 + (v - 27) };
|
|
123
|
-
}
|