@ninsei-labs/sdk 0.39.3 → 0.40.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/engine/atomic/dleq2.js +86 -111
  3. package/engine/atomic/halfenc.js +58 -72
  4. package/engine/atomic/halves.js +31 -53
  5. package/engine/atomic/order-client.js +15 -16
  6. package/engine/atomic/order.js +44 -62
  7. package/engine/atomic/orderContext.js +26 -50
  8. package/engine/atomic/orderHalfCommit.js +17 -18
  9. package/engine/core/chainSource.js +54 -69
  10. package/engine/core/clock.js +11 -12
  11. package/engine/core/config.js +561 -451
  12. package/engine/core/finality.js +34 -53
  13. package/engine/core/format.js +35 -36
  14. package/engine/core/preSignGuards.js +109 -117
  15. package/engine/core/rfqSource.js +57 -75
  16. package/engine/core/sellWindow.js +23 -24
  17. package/engine/core/store.js +20 -21
  18. package/engine/core/swap.js +242 -243
  19. package/engine/evm/amounts.js +50 -51
  20. package/engine/evm/asyncExec.js +577 -0
  21. package/engine/evm/asyncLegs.js +308 -0
  22. package/engine/evm/asyncRoute.js +179 -0
  23. package/engine/evm/auth.js +41 -48
  24. package/engine/evm/claimGas.js +28 -47
  25. package/engine/evm/depositor.js +39 -43
  26. package/engine/evm/dex.js +266 -267
  27. package/engine/evm/escrow.js +113 -129
  28. package/engine/evm/factory.js +71 -77
  29. package/engine/evm/fees.js +41 -42
  30. package/engine/evm/funding.js +160 -162
  31. package/engine/evm/gasReserve.js +67 -36
  32. package/engine/evm/index.js +46 -53
  33. package/engine/evm/injected.js +34 -42
  34. package/engine/evm/permit.js +129 -56
  35. package/engine/evm/prices.js +30 -45
  36. package/engine/evm/session.js +188 -189
  37. package/engine/mock/chain.js +17 -18
  38. package/engine/monero/address.js +33 -48
  39. package/engine/monero/halvesSweep.js +25 -43
  40. package/engine/monero/node.js +14 -15
  41. package/engine/monero/scan.js +82 -95
  42. package/engine/monero/swapKeys.js +30 -33
  43. package/engine/monero/unlock.js +17 -20
  44. package/engine/monero/wallet.js +32 -42
  45. package/engine/recovery/recoveryFile.js +154 -161
  46. package/engine/recovery/restore.js +50 -61
  47. package/engine/sdk/bridge.js +244 -234
  48. package/engine/ui/dom.js +4 -5
  49. package/engine/ui/walletModal.js +20 -31
  50. package/index.d.ts +8 -0
  51. package/package.json +1 -1
  52. package/src/adapters/evm-wallet.mjs +16 -0
  53. package/src/adapters/monero-wallet.mjs +1 -1
  54. package/src/adapters/monero.mjs +1 -1
  55. package/src/adapters/node-test-evm-wallet.mjs +20 -8
  56. package/src/config.mjs +1 -1
  57. package/src/engine.mjs +4 -0
  58. package/src/legs/cow-spec.mjs +254 -0
  59. package/src/legs/cow.mjs +315 -0
  60. package/src/legs/evm.mjs +86 -2
  61. package/src/legs/kyber-spec.mjs +265 -0
  62. package/src/legs/kyber.mjs +425 -0
  63. package/src/legs/shape.mjs +35 -0
  64. package/src/legs/uniswaprelay-spec.mjs +541 -0
  65. package/src/legs/uniswaprelay.mjs +422 -0
  66. package/src/legs/uniswapx-spec.mjs +329 -0
  67. package/src/legs/uniswapx.mjs +362 -0
  68. package/src/quotes.mjs +1 -1
  69. package/src/swaps.mjs +1 -1
@@ -1,93 +1,74 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/finality.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
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
- // ЗРЕЛОСТЬ СОСТОЯНИЯ ПО СЕТЯМ: на каком уровне цепь признаёт запись окончательной.
7
- //
8
- // ЗАЧЕМ ОТДЕЛЬНАЯ ТАБЛИЦА. Сети отличаются и временем блока, и смыслом тегов. Замер 28.09.2026 по живым
9
- // узлам (те самые адреса, что в настройках):
10
- // arbitrum 0.267 с/блок safe отстаёт ≈12.6 мин finalized ≈19.6 мин
11
- // arbitrum-sepolia 0.252 с/блок safe ≈8.6 мин finalized ≈14.9 мин
12
- // mainnet 12.060 с/блок safe ≈11.8 мин finalized ≈18.2 мин
13
- // base 2.000 с/блок safe ≈0.4 мин finalized ≈25.1 мин
14
- // bsc 0.450 с/блок safe и finalized отстают на 0-1 блок
15
- // monad 0.302 с/блок то же
16
- // hyperevm 0.984 с/блок то же
17
- // Отсюда два вывода, на которых стоит таблица:
18
- // 1) одно значение на все сети неверно: на base safe стоит 25 секунд, на arbitrum - 13 минут;
19
- // 2) на bsc, monad и hyperevm теги ФОРМАЛЬНЫ: они отвечают, но отставания нет, то есть "читать по
20
- // finalized" там выглядит защитой, а защиты не даёт. Пустышку опаснее отсутствия тега: отсутствие
21
- // видно, а пустышка молчит. Поэтому там - числовая глубина с НАЗВАННЫМ риском.
22
- //
23
- // ЭТА КОПИЯ - ДЛЯ СТРАНИЦЫ: она обслуживает все сети сразу, поэтому таблица нужна ей целиком.
24
- // Такая же таблица живёт у служб (rfq/finality.mjs); совпадение копий проверяет страж tools/check-finality.mjs,
25
- // потому что общего модуля у страницы и у служб быть не может.
26
- //
27
- // ОБЛАСТЬ ПРИМЕНЕНИЯ. Таблица отвечает на один вопрос: когда состояние EVM-стороны считается достаточно
28
- // зрелым, чтобы принять по нему НЕОБРАТИМОЕ решение - отправить XMR. Показ состояния на странице живёт по
29
- // latest и этой таблицей не ограничен.
5
+ // STATE MATURITY PER NETWORK: at which level the chain treats a record as final.
6
+ // One value for all networks is wrong: on base `safe` is ~25 s, on arbitrum ~13 min (measured on live nodes).
7
+ // On bsc, monad and hyperevm the tags are FORMAL: they answer but lag 0-1 block, so reading by `finalized`
8
+ // looks like protection and gives none. A named numeric depth is used there instead.
9
+ // A twin table lives in the node; the two copies are pinned by a check.
10
+ // SCOPE: the table answers one question - when the EVM side is mature enough for an IRREVERSIBLE decision
11
+ // (send XMR). Displaying state on screen uses `latest` and is not limited by this.
30
12
  export const FINALITY = {
31
- // ТЕГ ОСМЫСЛЕН: отставание измерено и названо.
32
- "arbitrum-sepolia": { mode: "tag", tag: "safe", costSec: 520, reason: "тег осмыслен: отставание ≈8.6 мин (замер)" },
33
- "arbitrum": { mode: "tag", tag: "safe", costSec: 760, reason: "тег осмыслен: отставание ≈12.6 мин (замер)" },
34
- "mainnet": { mode: "tag", tag: "safe", costSec: 710, reason: "тег осмыслен: отставание ≈11.8 мин (замер)" },
35
- "base": { mode: "tag", tag: "safe", costSec: 25, reason: "safe и осмыслен, и быстр: ≈25 с (замер)" },
13
+ // A MEANINGFUL TAG: the lag is measured and named.
14
+ "arbitrum-sepolia": { mode: "tag", tag: "safe", costSec: 520, reason: "tag is meaningful: lag ~8.6 min (measured)" },
15
+ "arbitrum": { mode: "tag", tag: "safe", costSec: 760, reason: "tag is meaningful: lag ~12.6 min (measured)" },
16
+ "mainnet": { mode: "tag", tag: "safe", costSec: 710, reason: "tag is meaningful: lag ~11.8 min (measured)" },
17
+ "base": { mode: "tag", tag: "safe", costSec: 25, reason: "safe is both meaningful and fast: ~25 s (measured)" },
36
18
 
37
- // ТЕГ ПУСТЫШКА: формально отвечает, отставания нет. Числовая глубина - с названной ценой риска.
38
- "bsc": { mode: "depth", depth: 15, costSec: 7, reason: "теги формальны (отставание 0-1 блок, замер): глубина ВЫБРАНА, а не измерена - риск назван" },
39
- "monad": { mode: "depth", depth: 12, costSec: 4, reason: "теги формальны (отставание 0-1 блок, замер): глубина выбрана" },
40
- "hyperevm": { mode: "depth", depth: 12, costSec: 12, reason: "теги формальны (отставание 0-1 блок, замер): глубина выбрана" },
19
+ // A FORMAL TAG: it answers, but there is no lag. A numeric depth with a named risk cost.
20
+ "bsc": { mode: "depth", depth: 15, costSec: 7, reason: "tags are formal (lag 0-1 block, measured): the depth is CHOSEN, not measured - risk named" },
21
+ "monad": { mode: "depth", depth: 12, costSec: 4, reason: "tags are formal (lag 0-1 block, measured): depth chosen" },
22
+ "hyperevm": { mode: "depth", depth: 12, costSec: 12, reason: "tags are formal (lag 0-1 block, measured): depth chosen" },
41
23
  };
42
24
 
43
- // СЕТЬ НЕ ОПИСАНА - ГОВОРИМ ОБ ЭТОМ, А НЕ ПОДСТАВЛЯЕМ МОЛЧА. Глубина 12 - прежнее общее значение, и оно
44
- // честнее пустого места: решение принимается, но цена риска названа как неизмеренная.
25
+ // NETWORK NOT DESCRIBED - WE SAY SO, NOT PASTE SILENTLY. Depth 12 is the old common value and is more
26
+ // honest than a blank: a decision is made, but the risk cost is named as unmeasured.
45
27
  export const DEFAULT_FINALITY = {
46
28
  mode: "depth", depth: 12, costSec: 0,
47
- reason: "сеть не описана в таблице: берём общую глубину, риск не измерен",
29
+ reason: "network not described in the table: using the common depth, risk not measured",
48
30
  };
49
31
 
50
32
  export function finalityFor(network) {
51
33
  return FINALITY[network] || DEFAULT_FINALITY;
52
34
  }
53
35
 
54
- // Сколько эта сеть заставляет ждать перед необратимым решением - в секундах. Складывать с окнами сделки
55
- // (readyBy, t1) обязан тот, кто эти окна назначает: окно, которое короче ожидания, убивает сделку.
36
+ // How long this network makes us wait before an irreversible decision, in seconds. Whoever sets the swap
37
+ // windows (readyBy, t1) must add it: a window shorter than the wait kills the swap.
56
38
  export function finalityWaitSec(network) {
57
39
  return Number(finalityFor(network).costSec || 0);
58
40
  }
59
41
 
60
- // ЧТО СКАЗАТЬ ЧЕЛОВЕКУ. Строка для экрана: уровень и цена ожидания словами, без чисел-обманок.
42
+ // WHAT TO TELL THE PERSON. A screen line: the level and the wait cost in words.
61
43
  export function finalityNote(network) {
62
44
  const f = finalityFor(network);
63
45
  if (f.mode === "tag") {
64
46
  const min = Math.max(1, Math.round(f.costSec / 60));
65
- return "ждём уровень " + f.tag + ": это ≈" + min + " мин";
47
+ return "waiting for level " + f.tag + ": ~" + min + " min";
66
48
  }
67
- return "ждём " + f.depth + " подтверждений: это ≈" + Math.max(1, Math.round(f.costSec)) + " с";
49
+ return "waiting for " + f.depth + " confirmations: ~" + Math.max(1, Math.round(f.costSec)) + " s";
68
50
  }
69
51
 
70
- // ЗНАЧОК ПРОВАЙДЕРА ДЛЯ СПИСКА. Короткий: человек решает, кого выбрать, и цена ожидания - часть выбора.
71
- // Свой замер важнее слов провайдера: если его уровень назван тегом, берём измеренную цену из таблицы.
52
+ // PROVIDER BADGE FOR THE LIST. Short: the person chooses whom to pick, and the wait cost is part of it.
53
+ // Our own measurement beats the provider's words: if its level is a tag, we take the measured cost from the table.
72
54
  export function finalityBadge(policy) {
73
55
  if (!policy) return "finality not stated";
74
56
  if (policy.mode === "tag") {
75
57
  const known = Object.values(FINALITY).find((f) => f.mode === "tag" && f.tag === policy.tag);
76
58
  const cost = Number(policy.costSec || (known && known.costSec) || 0);
77
- return cost >= 60 ? policy.tag + " · ≈" + Math.round(cost / 60) + " мин" : policy.tag + " · ≈" + Math.round(cost) + " с";
59
+ return cost >= 60 ? policy.tag + " · ~" + Math.round(cost / 60) + " min" : policy.tag + " · ~" + Math.round(cost) + " s";
78
60
  }
79
61
  const d = Number(policy.depth || 0);
80
62
  const cost = Number(policy.costSec || d * (FINALITY[Object.keys(FINALITY)[0]] ? 0.3 : 0.3) || 0);
81
- return d + " блоков · ≈" + Math.max(1, Math.round(cost)) + " с";
63
+ return d + " blocks · ~" + Math.max(1, Math.round(cost)) + " s";
82
64
  }
83
65
 
84
- // ОКНА ОРДЕРА ПО УРОВНЮ ПРОВАЙДЕРА. Окно короче ожидания убивает сделку: отметка готовности не успевает
85
- // попасть в контракт, и сделка распадается. Поэтому окна считаются от цены ожидания, а не берутся числом.
86
- // ЧИСЛА - РЕШЕНИЕ ВЛАДЕЛЬЦА (issue #88): при ПОКУПКЕ окно готовности 4 часа (человек должен успеть увидеть
87
- // XMR и отметиться), а окно забора ПОСЛЕ readyBy - 1 час; формула ниже держит именно эту разницу: claim на час
88
- // позже ready. При продаже сроки назначает нода (rfq/reverseOrder.mjs), там окно 1 час.
66
+ // ORDER WINDOWS BY PROVIDER LEVEL. A window shorter than the wait kills the swap: the readiness mark cannot
67
+ // reach the contract in time and the swap falls apart, so the windows are derived from the wait cost.
68
+ // The numbers are the owner's decision: on a BUY the readiness window is 4 hours and the claim window AFTER
69
+ // readyBy is 1 hour - the formula below keeps exactly that: claim one hour after ready. On a sell the node sets the deadlines.
89
70
  export function orderWindows(policy, base = { readyWindowSeconds: 14400, claimWindowSeconds: 3600 }) {
90
71
  const wait = Number((policy && policy.costSec) || 0);
91
- const ready = Math.max(base.readyWindowSeconds, wait + 900); // запас на внос и на дорогу транзакции
72
+ const ready = Math.max(base.readyWindowSeconds, wait + 900); // margin for the deposit and tx travel
92
73
  return { readyWindowSeconds: ready, claimWindowSeconds: Math.max(base.claimWindowSeconds, ready + 3600) };
93
74
  }
@@ -1,9 +1,8 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/format.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
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
+ // Formatting of numbers, time and addresses.
7
6
 
8
7
  const nf = (min, max) => new Intl.NumberFormat("en-US", { minimumFractionDigits: min, maximumFractionDigits: max });
9
8
 
@@ -11,10 +10,10 @@ export function amount(v, dp = 2) {
11
10
  const n = Number(v);
12
11
  if (!isFinite(n)) return "-";
13
12
  const max = Math.min(dp, 6);
14
- // ПЫЛЬ НЕ ИСЧЕЗАЕТ. Раньше печать была жёстко «шесть знаков», и сумма меньше 0.000001 показывалась как
15
- // 0.000000 - то есть ненулевое значение выглядело нулём. Для денег это худший вид отображения: человек
16
- // видит ноль там, где у него что-то есть. Знаки добавляются ровно в том случае, когда печать обнулила
17
- // ненулевое, и не больше 18 (предел точности wei).
13
+ // DUST DOES NOT VANISH. Before, printing was hard-wired to "six digits", and an amount below 0.000001 showed as
14
+ // 0.000000 - a non-zero value looked like zero. For money this is the worst kind of display: the person
15
+ // sees zero where they actually have something. Extra digits are added only when the print zeroed a
16
+ // non-zero value, and never more than 18 (the wei precision limit).
18
17
  if (n !== 0 && Math.abs(n) < Math.pow(10, -max)) {
19
18
  const need = Math.min(18, Math.max(max + 1, -Math.floor(Math.log10(Math.abs(n)))));
20
19
  return nf(need, need).format(n).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
@@ -49,7 +48,7 @@ export function shortHash(h, head = 6, tail = 4) {
49
48
  return s.slice(0, head) + "..." + s.slice(-tail);
50
49
  }
51
50
 
52
- // sim-время -> настенные часы демки (UTC, как в макетах литпепера)
51
+ // sim-time -> the demo's wall clock (UTC, as in the litepaper mockups)
53
52
  export function clockTime(ms) {
54
53
  const d = new Date(ms);
55
54
  const hh = String(d.getUTCHours()).padStart(2, "0");
@@ -62,7 +61,7 @@ export function clockTimeShort(ms) {
62
61
  return clockTime(ms).slice(0, 5);
63
62
  }
64
63
 
65
- // Длительность в секундах -> "12m 30s"
64
+ // Duration in seconds -> "12m 30s"
66
65
  export function duration(ms) {
67
66
  const total = Math.max(0, Math.round(ms / 1000));
68
67
  const m = Math.floor(total / 60);
@@ -92,49 +91,49 @@ export function randomHex(bytes = 16) {
92
91
  return toHex(a);
93
92
  }
94
93
 
95
- // Байты (Uint8Array/Buffer) -> hex-строка без префикса 0x.
94
+ // Bytes (Uint8Array/Buffer) -> hex string without the 0x prefix.
96
95
  export function toHex(bytes) {
97
96
  const a = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes || []);
98
97
  return Array.from(a, (b) => b.toString(16).padStart(2, "0")).join("");
99
98
  }
100
99
 
101
- // РАЗБОР СУММЫ, КОТОРУЮ ВВЁЛ ЧЕЛОВЕК.
100
+ // PARSING AN AMOUNT ENTERED BY A PERSON.
102
101
  //
103
- // ПОЧЕМУ ЭТО ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ replace ПРЯМО В ПОЛЕ. В полях суммы стояло
104
- // `e.target.value.replace(/[^0-9.]/g, "")`: всё, кроме цифр и точки, СТИРАЛОСЬ. Европейская запятая
105
- // ("0,05" - так пишут в половине стран) превращалась в "005", то есть в 5 - ошибка в СТО РАЗ, и заметить её
106
- // в интерфейсе почти невозможно: поле показывает "005", котировка считается по пяти, человек платит в сто
107
- // раз больше. Запятая без точки - десятичный разделитель, запятая при наличии точки - разделитель тысяч;
108
- // так же это делают кошельки Monero.
102
+ // WHY A SEPARATE FUNCTION, NOT A replace RIGHT IN THE FIELD. The amount fields had
103
+ // `e.target.value.replace(/[^0-9.]/g, "")`: everything but digits and a dot was ERASED. The European comma
104
+ // ("0,05" - the way half the world writes) turned into "005", that is 5 - an error by A HUNDREDFOLD, and spotting
105
+ // it in the interface is nearly impossible: the field shows "005", the quote is computed for five, the person pays
106
+ // a hundred times more. A comma without a dot is a decimal separator; a comma when a dot is present is a
107
+ // thousands separator; the Monero wallets do the same.
109
108
  //
110
- // Возвращает КАНОНИЧЕСКУЮ строку (только цифры и максимум одна точка). Именно её видно в поле и именно её
111
- // парсит остальной код - поэтому «что написано» и «что посчитано» не могут разойтись.
109
+ // Returns a CANONICAL string (digits and at most one dot). It is exactly what is shown in the field and exactly
110
+ // what the rest of the code parses - so "what is written" and "what is computed" cannot diverge.
112
111
  export function normalizeAmountInput(raw) {
113
112
  let s = String(raw == null ? "" : raw).trim().replace(/[^0-9.,]/g, "");
114
113
  if (s.includes(".")) {
115
- // Точка есть - запятые разделяют тысячи ("1,234.5" -> "1234.5").
114
+ // A dot is present - commas separate thousands ("1,234.5" -> "1234.5").
116
115
  s = s.replace(/,/g, "");
117
116
  } else if (s.includes(",")) {
118
- // Одна запятая без точки - десятичный разделитель ("0,05" -> "0.05"). Всё, что стоит ПОСЛЕ второй
119
- // запятой, отбрасываем: угадывать, что "1,2,3" значило 123, значит на опечатке увеличить платёж в сто раз.
117
+ // A single comma without a dot is the decimal separator ("0,05" -> "0.05"). Everything AFTER the second
118
+ // comma is dropped: guessing that "1,2,3" meant 123 would, on a typo, inflate the payment a hundredfold.
120
119
  const i = s.indexOf(",");
121
120
  const rest = s.slice(i + 1);
122
121
  const cut = rest.indexOf(",");
123
122
  s = s.slice(0, i) + "." + (cut < 0 ? rest : rest.slice(0, cut));
124
123
  }
125
- // Вторая точка - тоже опечатка, и хвост за ней отбрасывается, а не склеивается с числом ("1.2.3" -> "1.2").
124
+ // A second dot is also a typo, and the tail after it is dropped, not glued to the number ("1.2.3" -> "1.2").
126
125
  const dot = s.indexOf(".");
127
126
  if (dot >= 0) {
128
127
  const rest = s.slice(dot + 1);
129
128
  const cut = rest.indexOf(".");
130
129
  s = s.slice(0, dot + 1) + (cut < 0 ? rest : rest.slice(0, cut));
131
130
  }
132
- s = s.replace(/^0+(?=\d)/, ""); // "005" -> "5", но "0.5" не трогаем
131
+ s = s.replace(/^0+(?=\d)/, ""); // "005" -> "5", but "0.5" is left alone
133
132
  if (!/[0-9]/.test(s)) return "";
134
133
  return s;
135
134
  }
136
135
 
137
- /** Число из введённого человеком или null, если числа там нет. Никаких «догадок»: пусто - значит пусто. */
136
+ /** Number from a human-entered value, or null if there is no number. No "guessing": empty means empty. */
138
137
  export function parseAmount(raw) {
139
138
  const s = normalizeAmountInput(raw);
140
139
  if (!s) return null;
@@ -143,22 +142,22 @@ export function parseAmount(raw) {
143
142
  }
144
143
 
145
144
  export function shortId() {
146
- // 16 hex-символов (8 байт = 2^64). Раньше было 4 символа (randomHex(2)): при 100 000 сделок совпадение
147
- // практически неизбежно - 68.8% на 8 символах и 100% на 4, - а id служит ключом в состоянии и именем файла
148
- // восстановления, поэтому совпадение означало бы потерю записи, за которой стоят средства.
149
- // ВАЖНО: у уже созданных сделок id остаются короткими. Переименовывать их нельзя: id лежит в файле
150
- // восстановления, в ссылках и в состоянии - они бы разъехались.
145
+ // 16 hex chars (8 bytes = 2^64). Before it was 4 chars (randomHex(2)): with 100 000 swaps a collision is
146
+ // practically inevitable - 68.8% at 8 chars and 100% at 4 - and the id is a key in the state and the name of
147
+ // the recovery file, so a collision would mean losing a record that backs funds.
148
+ // IMPORTANT: already-created swaps keep short ids. They must not be renamed: the id lives in the recovery
149
+ // file, in links and in the state - they would fall out of sync.
151
150
  return randomHex(8);
152
151
  }
153
152
 
154
- // БЫСТРАЯ ПРИКИДКА ФОРМЫ Monero-адреса: base58, длина 95 (обычный) / 106 (integrated), префикс сети.
153
+ // QUICK CHECK OF A Monero ADDRESS SHAPE: base58, length 95 (plain) / 106 (integrated), network prefix.
155
154
  //
156
- // ЧЕГО ЭТА ФУНКЦИЯ НЕ ДЕЛАЕТ: НЕ ПРОВЕРЯЕТ КОНТРОЛЬНУЮ СУММУ. Любая опечатка внутри адреса пройдёт здесь
157
- // незамеченной. Раньше она называлась isValidMoneroAddress - и это имя было ловушкой: функция, которую
158
- // зовут "проверкой адреса", но которая пропускает опечатку, рано или поздно попадает на путь денег.
155
+ // WHAT THIS FUNCTION DOES NOT DO: IT DOES NOT VERIFY THE CHECKSUM. Any typo inside the address passes here
156
+ // unnoticed. It used to be named isValidMoneroAddress - and that name was a trap: a function called
157
+ // "address check" that lets a typo through ends up on the money path sooner or later.
159
158
  //
160
- // НАСТОЯЩАЯ ПРОВЕРКА - checkAddress() из ../monero/address.js: формат + разбор base58 + keccak256-контрольная
161
- // сумма + совпадение сети. Именно её зовёт форма. Здесь - только чтобы отсеять явный мусор до тяжёлого разбора.
159
+ // THE REAL CHECK IS checkAddress() from ../monero/address.js: format + base58 decode + keccak256 checksum
160
+ // + network match. The form calls that one. This one only screens out obvious garbage before the heavy decode.
162
161
  export function looksLikeMoneroAddress(addr) {
163
162
  const a = String(addr || "").trim();
164
163
  if (![95, 106].includes(a.length)) return false;