@ninsei-labs/sdk 0.39.2 → 0.40.0-develop.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +4 -1
- package/engine/atomic/dleq2.js +86 -111
- package/engine/atomic/halfenc.js +58 -72
- package/engine/atomic/halves.js +31 -53
- package/engine/atomic/order-client.js +15 -16
- package/engine/atomic/order.js +44 -62
- package/engine/atomic/orderContext.js +26 -50
- package/engine/atomic/orderHalfCommit.js +17 -18
- package/engine/core/chainSource.js +54 -69
- package/engine/core/clock.js +11 -12
- package/engine/core/config.js +554 -449
- package/engine/core/finality.js +34 -53
- package/engine/core/format.js +35 -36
- package/engine/core/preSignGuards.js +109 -117
- package/engine/core/rfqSource.js +57 -75
- package/engine/core/sellWindow.js +23 -24
- package/engine/core/store.js +20 -21
- package/engine/core/swap.js +242 -243
- package/engine/evm/amounts.js +50 -51
- package/engine/evm/asyncExec.js +577 -0
- package/engine/evm/asyncLegs.js +308 -0
- package/engine/evm/asyncRoute.js +179 -0
- package/engine/evm/auth.js +41 -48
- package/engine/evm/claimGas.js +28 -47
- package/engine/evm/depositor.js +39 -43
- package/engine/evm/dex.js +266 -267
- package/engine/evm/escrow.js +113 -129
- package/engine/evm/factory.js +71 -77
- package/engine/evm/fees.js +41 -42
- package/engine/evm/funding.js +160 -162
- package/engine/evm/gasReserve.js +67 -36
- package/engine/evm/index.js +46 -53
- package/engine/evm/injected.js +34 -42
- package/engine/evm/permit.js +129 -56
- package/engine/evm/prices.js +30 -45
- package/engine/evm/session.js +188 -189
- package/engine/mock/chain.js +17 -18
- package/engine/monero/address.js +33 -48
- package/engine/monero/halvesSweep.js +25 -43
- package/engine/monero/node.js +14 -15
- package/engine/monero/scan.js +82 -95
- package/engine/monero/swapKeys.js +30 -33
- package/engine/monero/unlock.js +17 -20
- package/engine/monero/wallet.js +32 -42
- package/engine/recovery/recoveryFile.js +154 -161
- package/engine/recovery/restore.js +50 -61
- package/engine/sdk/bridge.js +244 -234
- package/engine/ui/dom.js +4 -5
- package/engine/ui/walletModal.js +20 -31
- package/index.d.ts +26 -0
- package/package.json +1 -1
- package/src/adapters/evm-wallet.mjs +16 -0
- package/src/adapters/monero-wallet.mjs +1 -1
- package/src/adapters/monero.mjs +1 -1
- package/src/adapters/node-test-evm-wallet.mjs +5 -5
- package/src/codes.mjs +5 -0
- package/src/config.mjs +1 -1
- package/src/engine.mjs +4 -0
- package/src/index.mjs +16 -2
- package/src/legs/cow-spec.mjs +254 -0
- package/src/legs/cow.mjs +315 -0
- package/src/legs/evm.mjs +86 -2
- package/src/legs/kyber-spec.mjs +265 -0
- package/src/legs/kyber.mjs +425 -0
- package/src/legs/shape.mjs +35 -0
- package/src/legs/uniswaprelay-spec.mjs +541 -0
- package/src/legs/uniswaprelay.mjs +422 -0
- package/src/legs/uniswapx-spec.mjs +329 -0
- package/src/legs/uniswapx.mjs +362 -0
- package/src/quotes.mjs +23 -5
- package/src/swaps.mjs +18 -1
|
@@ -1,55 +1,51 @@
|
|
|
1
1
|
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/preSignGuards.js for the SDK package.
|
|
2
|
-
// Edit the source under www/js
|
|
3
|
-
// tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
|
|
2
|
+
// Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
|
|
4
3
|
// Everything below this header is identical to the source.
|
|
5
4
|
|
|
6
|
-
//
|
|
5
|
+
// PRE-SIGNING GUARDS: AN INADMISSIBLE DEAL DOES NOT START.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// начинать подпись или нет.
|
|
7
|
+
// WHY A SEPARATE MODULE. The guards used to live apart: one in the route registry (the DEX leg's coverage and
|
|
8
|
+
// price), the rest scattered across the form and the order flow. That breeds exactly the defect this file exists
|
|
9
|
+
// for: the rule is there but the signature does not stop, because the screen never asked. Here the rules sit in
|
|
10
|
+
// ONE place with ONE answer shape, and the summary (signingGateVerdict) answers the only question that matters:
|
|
11
|
+
// start the signature or not.
|
|
14
12
|
//
|
|
15
|
-
//
|
|
16
|
-
// blocks -
|
|
17
|
-
// checked -
|
|
18
|
-
// reason -
|
|
19
|
-
//
|
|
13
|
+
// WHAT AN ANSWER MEANS. Every verdict carries three fields of different meaning:
|
|
14
|
+
// blocks - signing is NOT ALLOWED (the guard itself; the caller MUST honour this field);
|
|
15
|
+
// checked - whether the check could be performed at all (true/false);
|
|
16
|
+
// reason - words, and they carry NUMBERS: a person needs not "no" but "no, because this much".
|
|
17
|
+
// Telling checked from blocks is mandatory; two examples from this file show why:
|
|
20
18
|
//
|
|
21
|
-
// *
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// *
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// рынком: "объём не назван - ограничивать нечем", www/js/mock/market.js: coversSize).
|
|
19
|
+
// * AN UNCHECKED BALANCE BLOCKS. Reading a balance always works here, so "could not read" means "we do not
|
|
20
|
+
// know whether there is enough money for what the person is about to sign". Skipping it would send the person
|
|
21
|
+
// to sign a wallet refusal.
|
|
22
|
+
// * AN UNMEASURED GAS RESERVE DOES NOT BLOCK, AND THAT IS A DECISION, NOT AN OVERSIGHT. Unlike the DEX price
|
|
23
|
+
// check: a bad price means the deal is WRONG, while an unmeasured gas figure only means "the reserve is
|
|
24
|
+
// unknown". The deal does not become inadmissible, and a wallet will name a shortfall anyway. So this case is
|
|
25
|
+
// marked (checked: false, kind reserve-unstated) and does NOT lock the signature.
|
|
26
|
+
// * A SIZE RANGE THE PROVIDER DID NOT NAME - SAME THING: marked, not blocking (the market already decided:
|
|
27
|
+
// "no size named - nothing to cap", see coversSize in the mock market module).
|
|
31
28
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
29
|
+
// ONE MORE THING THAT IS FUNDAMENTAL HERE. All verdicts are PURE functions: no network, no wallet, no DOM. So
|
|
30
|
+
// each guard can be run against the numbers that BREAK it and be seen red: "the code is there" is not "the
|
|
31
|
+
// refusal works".
|
|
35
32
|
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
33
|
+
// THE THRESHOLDS COME IN AS DATA (the registry: SIGNING_GUARDS in core/config.js), not as numbers here: changing
|
|
34
|
+
// them is a data edit, not a logic edit.
|
|
38
35
|
|
|
39
|
-
// wei
|
|
40
|
-
//
|
|
36
|
+
// wei from whatever the caller supplied (BigInt, string, number). Unparsable - null, and that is NOT zero: a zero
|
|
37
|
+
// balance and an unknown balance are two different states and must not be confused.
|
|
41
38
|
const wei = (v) => {
|
|
42
39
|
if (v === null || v === undefined || v === "") return null;
|
|
43
40
|
try { const n = BigInt(v); return n >= 0n ? n : null; } catch { return null; }
|
|
44
41
|
};
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// неверный ответ ("минимум 0.00001 XMR" вместо "0.1 XMR"). Поэтому формат свой.
|
|
42
|
+
// A QUANTITY IN HUMAN FORM - SEPARATE FROM wei. The swap size and the range bounds arrive ALREADY human (ASSET
|
|
43
|
+
// units of the pair), not wei, so there is nothing to divide by 10^decimals: a shared function would put numbers
|
|
44
|
+
// into the refusal that differ from the real ones by a factor of 10^14 - a confident but wrong answer.
|
|
49
45
|
const qty = (v) => String(Number(Number(v).toFixed(4)));
|
|
50
46
|
const finite = (v) => (v === null || v === undefined || v === "" ? null : (Number.isFinite(Number(v)) ? Number(v) : null));
|
|
51
|
-
//
|
|
52
|
-
//
|
|
47
|
+
// A number in human form for the words: six decimals suit both a stable and a native, and nobody reads full wei
|
|
48
|
+
// in text.
|
|
53
49
|
const human = (v, decimals = 18) => {
|
|
54
50
|
const n = Number(v) / 10 ** Number(decimals);
|
|
55
51
|
if (!Number.isFinite(n)) return String(v);
|
|
@@ -57,11 +53,11 @@ const human = (v, decimals = 18) => {
|
|
|
57
53
|
};
|
|
58
54
|
|
|
59
55
|
// ---------------------------------------------------------------------------
|
|
60
|
-
// 1)
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
56
|
+
// 1) SIZE AGAINST THE QUOTE RANGE. The range is the provider's (min/max/step) and it will not take a size outside
|
|
57
|
+
// it: a size out of range is a REFUSAL, not a "let us try", and it must happen BEFORE the signature, not at the
|
|
58
|
+
// provider after it.
|
|
59
|
+
// THE UNIT IS PART OF THE CHECK, NOT DECORATION: the size and the bounds must be in ONE unit (the ASSET of the
|
|
60
|
+
// pair), else different quantities are compared and "in range" means nothing.
|
|
65
61
|
// ---------------------------------------------------------------------------
|
|
66
62
|
export function rangeVerdict({ size = null, unit = null, min = null, max = null, step = null, quoteUnit = null } = {}) {
|
|
67
63
|
const u = String(unit || quoteUnit || "").toUpperCase();
|
|
@@ -83,7 +79,7 @@ export function rangeVerdict({ size = null, unit = null, min = null, max = null,
|
|
|
83
79
|
}
|
|
84
80
|
const lo = finite(min), hi = finite(max), st = finite(step);
|
|
85
81
|
if (lo === null && hi === null) {
|
|
86
|
-
//
|
|
82
|
+
// The provider named no bounds - that is its word, not our error: mark it and do not block.
|
|
87
83
|
return {
|
|
88
84
|
ok: true, checked: false, blocks: false, kind: "range-unstated",
|
|
89
85
|
reason: "the provider did not state a volume range (min/max), so the size could not be checked against it",
|
|
@@ -100,8 +96,8 @@ export function rangeVerdict({ size = null, unit = null, min = null, max = null,
|
|
|
100
96
|
reason: `the order size ${qty(s)} ${u} is above the provider maximum ${qty(hi)} ${u} - over by ${qty(s - hi)} ${u}: the provider does not take this size` };
|
|
101
97
|
}
|
|
102
98
|
if (st !== null && st > 0) {
|
|
103
|
-
//
|
|
104
|
-
//
|
|
99
|
+
// The step is counted FROM min, not from zero: min=0.05 with step 0.1 yields no further size, and that is the
|
|
100
|
+
// market rule, not an invention of this function.
|
|
105
101
|
const from = lo !== null ? lo : 0;
|
|
106
102
|
const grid = (s - from) / st;
|
|
107
103
|
if (Math.abs(grid - Math.round(grid)) > 1e-6) {
|
|
@@ -114,9 +110,9 @@ export function rangeVerdict({ size = null, unit = null, min = null, max = null,
|
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
// ---------------------------------------------------------------------------
|
|
117
|
-
// 2)
|
|
118
|
-
//
|
|
119
|
-
//
|
|
113
|
+
// 2) PAYMENT BALANCE. Not enough of what pays - there is nothing to sign: the transaction would fail in the
|
|
114
|
+
// wallet and the person would learn of the shortfall AFTER signing. An unknown balance (not read) also locks: see
|
|
115
|
+
// the reasoning in the header.
|
|
120
116
|
// ---------------------------------------------------------------------------
|
|
121
117
|
export function payBalanceVerdict({ payBalanceWei = null, payAmountWei = null, symbol = null, decimals = 18 } = {}) {
|
|
122
118
|
const have = wei(payBalanceWei);
|
|
@@ -140,11 +136,11 @@ export function payBalanceVerdict({ payBalanceWei = null, payAmountWei = null, s
|
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
// ---------------------------------------------------------------------------
|
|
143
|
-
// 3)
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
139
|
+
// 3) GAS RESERVE. Funding is a transaction and costs gas in the NATIVE coin, so both the order amount and the
|
|
140
|
+
// order amount PLUS the reserve are checked: an account holding exactly `amount` will not pass. The required value
|
|
141
|
+
// is computed by the caller (amount + reserve), not here: one quantity instead of two branches - less room to
|
|
142
|
+
// diverge.
|
|
143
|
+
// An unmeasured reserve does NOT lock: see the header (the difference from the price check is deliberate).
|
|
148
144
|
// ---------------------------------------------------------------------------
|
|
149
145
|
export function gasReserveVerdict({ nativeBalanceWei = null, requiredNativeWei = null, gasReserveWei = null, symbol = null, decimals = 18 } = {}) {
|
|
150
146
|
const need = wei(requiredNativeWei);
|
|
@@ -175,12 +171,11 @@ export function gasReserveVerdict({ nativeBalanceWei = null, requiredNativeWei =
|
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
// ---------------------------------------------------------------------------
|
|
178
|
-
// 4)
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
// "срок котировки провайдер не назвал" на экране, а не закрытие рынка).
|
|
174
|
+
// 4) QUOTE FRESHNESS. Product rule: nobody shows a stale quote (older than its own ttlMs) and it cannot be signed
|
|
175
|
+
// - the price in it is no longer what the person sees. The age is computed from the quote's OWN timestamp (at),
|
|
176
|
+
// not from the response time: otherwise the age would be rejuvenated by every poll.
|
|
177
|
+
// No timestamp or no ttl - nothing to check; that is marked and does not block (the market decided the same:
|
|
178
|
+
// "the provider named no quote ttl" on screen, not a closed market).
|
|
184
179
|
// ---------------------------------------------------------------------------
|
|
185
180
|
export function quoteFreshnessVerdict({ at = null, ttlMs = null, expiresAt = null, nowMs = null } = {}) {
|
|
186
181
|
const now = finite(nowMs) === null ? Date.now() : Number(nowMs);
|
|
@@ -210,12 +205,11 @@ export function quoteFreshnessVerdict({ at = null, ttlMs = null, expiresAt = nul
|
|
|
210
205
|
}
|
|
211
206
|
|
|
212
207
|
// ---------------------------------------------------------------------------
|
|
213
|
-
// 5)
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// Пороги - данные (SIGNING_GUARDS).
|
|
208
|
+
// 5) ORDER DEADLINES (readyBy / t1). A deadline in the past means an order created already closed: nothing can be
|
|
209
|
+
// claimed, the money hangs until refund. Deadlines that are too tight mean the same, but invisibly: the readiness
|
|
210
|
+
// window is shorter than the XMR confirmation itself, and the settlement window shorter than the maker needs to
|
|
211
|
+
// claim. A deadline too far off is the third extreme: ETH can be returned only before `readyBy` and after `t1`, so
|
|
212
|
+
// an order with `t1` a year away locks the money for a year. Hence the ceiling. The thresholds are data.
|
|
219
213
|
// ---------------------------------------------------------------------------
|
|
220
214
|
export function deadlineVerdict({ nowSec = null, readyBy = null, t1 = null, minReadyLeadSec = null, minClaimWindowSec = null, maxDeadlineSec = null } = {}) {
|
|
221
215
|
const now = finite(nowSec) === null ? Math.floor(Date.now() / 1000) : Number(nowSec);
|
|
@@ -241,9 +235,9 @@ export function deadlineVerdict({ nowSec = null, readyBy = null, t1 = null, minR
|
|
|
241
235
|
return { ok: false, checked: true, blocks: true, kind: "claim-window-too-short", readyBy: r, t1: c, minClaimWindowSec: claim,
|
|
242
236
|
reason: `the claim window is ${Math.round(c - r)}s while at least ${claim}s is needed to take the funds before the refund opens` };
|
|
243
237
|
}
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
238
|
+
// THE DEADLINE CEILING. Without it a deadline can be set a year ahead: both sides sign, the ETH goes into the
|
|
239
|
+
// escrow and cannot be returned before `readyBy` or after `t1` - a whole year. The ceiling is computed from `t1`
|
|
240
|
+
// (the farthest deadline): `readyBy` is always nearer, so this check covers both.
|
|
247
241
|
const cap = finite(maxDeadlineSec);
|
|
248
242
|
if (cap !== null && c - now > cap) {
|
|
249
243
|
return { ok: false, checked: true, blocks: true, kind: "deadline-too-far", readyBy: r, t1: c, nowSec: now, maxDeadlineSec: cap,
|
|
@@ -254,12 +248,12 @@ export function deadlineVerdict({ nowSec = null, readyBy = null, t1 = null, minR
|
|
|
254
248
|
}
|
|
255
249
|
|
|
256
250
|
// ---------------------------------------------------------------------------
|
|
257
|
-
// 6)
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
251
|
+
// 6) ESCROW STATE. The contract forbids a second deposit (AlreadyFunded) and does not allow claiming twice, so
|
|
252
|
+
// "signing again" at an already funded, claimed or refunded escrow is not a "just in case" check: the second
|
|
253
|
+
// signature either reverts or creates a SECOND order instead of the old one. Read by calling status() at the
|
|
254
|
+
// predicted address - before signing.
|
|
255
|
+
// About an empty answer: for an address that does not exist yet eth_call returns empty - that is "a new order",
|
|
256
|
+
// not "the check failed". A call error is another matter, and it locks.
|
|
263
257
|
// ---------------------------------------------------------------------------
|
|
264
258
|
export function escrowStateVerdict({ status = null, error = null, address = null } = {}) {
|
|
265
259
|
const where = address ? " the escrow at " + address : " the escrow";
|
|
@@ -288,16 +282,14 @@ export function escrowStateVerdict({ status = null, error = null, address = null
|
|
|
288
282
|
}
|
|
289
283
|
|
|
290
284
|
// ---------------------------------------------------------------------------
|
|
291
|
-
// 7)
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
// (
|
|
298
|
-
//
|
|
299
|
-
// она по-прежнему не включена: форма ещё не свопает (своп - платный этап, док 48), и включать проверку
|
|
300
|
-
// в подпись раньше появления самого действия значило бы запирать человека на том, чего он ещё не делает.
|
|
285
|
+
// 7) ERC-20 CONSENT. It will arrive with DEX-leg execution: swapping a token requires approve, and permission to
|
|
286
|
+
// spend is a SEPARATE action of the person, not a consequence of signing the swap. So there are two checks, both
|
|
287
|
+
// mandatory: the allowance is granted (enough) AND the person explicitly consented. Enough without consent and
|
|
288
|
+
// consent without enough are different refusals, named differently.
|
|
289
|
+
// THIS GUARD ALREADY HAS A CONSUMER: the DEX-leg execution builder (swapAllowanceVerdict) calls it to tell
|
|
290
|
+
// "allowance short" from "the person did not consent" - different refusals, named differently. It is still NOT
|
|
291
|
+
// part of the form's signature: the form does not swap yet (the swap is a paid stage), and adding the check to the
|
|
292
|
+
// signature before the action exists would lock a person on something he does not yet do.
|
|
301
293
|
// ---------------------------------------------------------------------------
|
|
302
294
|
export function allowanceVerdict({ tokenIsNative = false, allowanceWei = null, amountWei = null, consent = false, symbol = null, decimals = 18 } = {}) {
|
|
303
295
|
if (tokenIsNative) {
|
|
@@ -328,10 +320,10 @@ export function allowanceVerdict({ tokenIsNative = false, allowanceWei = null, a
|
|
|
328
320
|
}
|
|
329
321
|
|
|
330
322
|
// ---------------------------------------------------------------------------
|
|
331
|
-
// 8)
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
323
|
+
// 8) WALLET NETWORK. Balances are read through the wallet, i.e. FROM ITS NETWORK, and the transaction goes there
|
|
324
|
+
// too. So a network mismatch is a refusal, not a warning: "page on one network, wallet on another" means we know
|
|
325
|
+
// neither the balances nor where the money goes. The wallet's network marker comes from the session (chainIdOf),
|
|
326
|
+
// the settlement network marker from the registry (chainId of the selected network).
|
|
335
327
|
// ---------------------------------------------------------------------------
|
|
336
328
|
export function networkGateVerdict({ walletChainId = null, selectedChainId = null, settlementChainId = null, selectedChainName = null } = {}) {
|
|
337
329
|
const w = finite(walletChainId), s = finite(selectedChainId), set = finite(settlementChainId);
|
|
@@ -356,18 +348,18 @@ export function networkGateVerdict({ walletChainId = null, selectedChainId = nul
|
|
|
356
348
|
}
|
|
357
349
|
|
|
358
350
|
// ---------------------------------------------------------------------------
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
351
|
+
// THE SUMMARY. The caller assembles what it has and gets ONE answer: whether the signature may start. The order
|
|
352
|
+
// of checks is set by the order of the array: the first cause in the list is named, because with a wrong network
|
|
353
|
+
// the question of size coverage no longer matters.
|
|
362
354
|
// ---------------------------------------------------------------------------
|
|
363
|
-
//
|
|
364
|
-
// standIn:
|
|
365
|
-
//
|
|
355
|
+
// A LIVE NETWORK DOES NOT SIGN A DEAL WITH A STAND-IN. In the demo the counterparty side is assembled locally and
|
|
356
|
+
// marked standIn: then DLEQ is checked against itself and proves nothing - a showcase mode, not a deal. On a live
|
|
357
|
+
// network there must be no such signature: the money would go against a party that does not exist.
|
|
366
358
|
export function counterpartyVerdict({ standIn = false, liveChain = false, network = null, chainId = null } = {}) {
|
|
367
359
|
const v = (ok, kind, words) => ({ ok, checked: true, blocks: !ok, kind, words, reason: words });
|
|
368
|
-
//
|
|
369
|
-
// (
|
|
370
|
-
//
|
|
360
|
+
// THE NETWORK IS NAMED BY NUMBER AND CODE, not by the word "live": a refusal must show which network is meant
|
|
361
|
+
// (one node can serve several), and that is the same standard as elsewhere - a cause without a number is a
|
|
362
|
+
// defect, because it leaves no way to understand what happened.
|
|
371
363
|
const where = (network || "this network") + (chainId === null || chainId === undefined ? "" : " (chainId " + chainId + ")");
|
|
372
364
|
if (!standIn) return v(true, "real-provider", "counterparty is a real provider: the proof is checked against it");
|
|
373
365
|
if (liveChain) {
|
|
@@ -379,12 +371,12 @@ export function counterpartyVerdict({ standIn = false, liveChain = false, networ
|
|
|
379
371
|
|
|
380
372
|
|
|
381
373
|
// ---------------------------------------------------------------------------
|
|
382
|
-
// 9)
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
374
|
+
// 9) ORDER POINTS ON CHAIN AGAINST THE POINTS DLEQ AGREED ON (an audit finding). DLEQ proves a point's link to a
|
|
375
|
+
// half, but by itself does not stop OTHER points from going into the order: the proof would hold for one set while
|
|
376
|
+
// createOrderAndFund gets another - and after claim the XMR sweep would not assemble, because the spend key is
|
|
377
|
+
// derived from other halves. The chain cannot check this (EVM has no ed25519), so the side that risks checks it,
|
|
378
|
+
// BEFORE acting. Here only the question "is the signature locked" is decided; the comparison takes the points
|
|
379
|
+
// FROM THE LIVE ESCROW SLOTS, not from form memory.
|
|
388
380
|
// ---------------------------------------------------------------------------
|
|
389
381
|
const normHex = (v) => (v === null || v === undefined ? "" : String(v).replace(/^0x/i, "").toLowerCase());
|
|
390
382
|
export function escrowPointsVerdict({ role = null, what = null, point = null, onchainPoint = null, commit = null, onchainCommit = null, readError = null } = {}) {
|
|
@@ -393,9 +385,9 @@ export function escrowPointsVerdict({ role = null, what = null, point = null, on
|
|
|
393
385
|
return { ok: false, checked: false, blocks: true, kind: "points-unchecked",
|
|
394
386
|
reason: `${who} of the live escrow could not be read (${String(readError)}): whether the order holds the points that DLEQ proved is unknown - signing is blocked` };
|
|
395
387
|
}
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
388
|
+
// THE BRANCH IS CHOSEN BY WHAT IS ASKED TO BE COMPARED. The order of checks here is not cosmetic: if the
|
|
389
|
+
// commitment is asked about while the point is checked first, a missing POINT makes the verdict say "points not
|
|
390
|
+
// read" and the real cause (a wrong commitment) goes unnamed - already caught by a tooth.
|
|
399
391
|
let compared = false;
|
|
400
392
|
if (normHex(point)) {
|
|
401
393
|
compared = true;
|
|
@@ -408,8 +400,8 @@ export function escrowPointsVerdict({ role = null, what = null, point = null, on
|
|
|
408
400
|
reason: `${who} in the escrow (0x${normHex(onchainPoint)}) is NOT the point DLEQ proved (0x${normHex(point)}): the Monero address would be assembled from other halves and the XMR could not be swept after claim` };
|
|
409
401
|
}
|
|
410
402
|
}
|
|
411
|
-
//
|
|
412
|
-
//
|
|
403
|
+
// BOTH FIELDS ARE COMPARED INDEPENDENTLY. An "else" branch here was a bug: when the point matched, a commitment
|
|
404
|
+
// divergence simply went unchecked and a swapped half slipped past the verdict. Caught by a tooth.
|
|
413
405
|
if (normHex(commit)) {
|
|
414
406
|
compared = true;
|
|
415
407
|
if (!normHex(onchainCommit)) {
|
|
@@ -429,10 +421,10 @@ export function escrowPointsVerdict({ role = null, what = null, point = null, on
|
|
|
429
421
|
reason: `${who} in the escrow matches what we hold${normHex(point) && normHex(commit) ? " (point and commitment)" : ""}` };
|
|
430
422
|
}
|
|
431
423
|
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
424
|
+
// LIVE ORDER SLOTS AGAINST WHAT THE PAGE ASSEMBLED. One question, four answers: the points of both sides and the
|
|
425
|
+
// commitments to both halves. EVERY field we have is compared, and a field missing on chain locks just like a
|
|
426
|
+
// mismatch. Built from the same primitive as the single point check (escrowPointsVerdict), so refusals and
|
|
427
|
+
// comparison rules are one for all.
|
|
436
428
|
export function liveOrderSlotsVerdict({ slots = null, expect = null, readError = null } = {}) {
|
|
437
429
|
if (readError || !slots) {
|
|
438
430
|
return { ok: false, checked: false, blocks: true, kind: "slots-unchecked",
|
|
@@ -448,7 +440,7 @@ export function liveOrderSlotsVerdict({ slots = null, expect = null, readError =
|
|
|
448
440
|
const checked = [];
|
|
449
441
|
const missing = [];
|
|
450
442
|
for (const [key, what, kind] of fields) {
|
|
451
|
-
if (!normHex(expect && expect[key])) continue; //
|
|
443
|
+
if (!normHex(expect && expect[key])) continue; // what we do not have we do not compare - and that is named below
|
|
452
444
|
if (!normHex(slots[key])) { missing.push(what); continue; }
|
|
453
445
|
const v = kind === "point"
|
|
454
446
|
? escrowPointsVerdict({ what, point: expect[key], onchainPoint: slots[key] })
|
|
@@ -479,14 +471,14 @@ export function signingGateVerdict(checks = []) {
|
|
|
479
471
|
reason: blocking.length ? blocking.map((c) => c.reason).filter(Boolean).join("; ") : null,
|
|
480
472
|
blockedBy: blocking.map((c) => c.kind),
|
|
481
473
|
unchecked: unchecked.map((c) => c.kind),
|
|
482
|
-
//
|
|
483
|
-
//
|
|
474
|
+
// WHAT WAS CHECKED IS RETURNED WHOLE: the screen and the report must name not only the verdict but what it was
|
|
475
|
+
// taken from, and the numbers there are already computed.
|
|
484
476
|
checks: list,
|
|
485
477
|
};
|
|
486
478
|
}
|
|
487
479
|
|
|
488
|
-
//
|
|
489
|
-
//
|
|
480
|
+
// Words for the button and the caption under it. One phrase per verdict, so the screen and the report say the
|
|
481
|
+
// same thing: once they diverge they will explain one thing to the person and another to the operator.
|
|
490
482
|
export function signingGateWords(gate) {
|
|
491
483
|
if (!gate || !gate.blocked) return null;
|
|
492
484
|
const first = (gate.checks || []).find((c) => c.blocks === true) || null;
|