@ninsei-labs/sdk 0.39.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 (81) hide show
  1. package/CHANGELOG.md +866 -0
  2. package/LICENSE +21 -0
  3. package/README.md +187 -0
  4. package/engine/atomic/dleq2.js +330 -0
  5. package/engine/atomic/halfenc.js +205 -0
  6. package/engine/atomic/halves.js +119 -0
  7. package/engine/atomic/order-client.js +57 -0
  8. package/engine/atomic/order.js +132 -0
  9. package/engine/atomic/orderContext.js +70 -0
  10. package/engine/atomic/orderHalfCommit.js +29 -0
  11. package/engine/core/chainSource.js +306 -0
  12. package/engine/core/clock.js +40 -0
  13. package/engine/core/config.js +885 -0
  14. package/engine/core/finality.js +93 -0
  15. package/engine/core/format.js +168 -0
  16. package/engine/core/preSignGuards.js +495 -0
  17. package/engine/core/rfqSource.js +193 -0
  18. package/engine/core/sellWindow.js +43 -0
  19. package/engine/core/store.js +100 -0
  20. package/engine/core/swap.js +1153 -0
  21. package/engine/evm/amounts.js +131 -0
  22. package/engine/evm/auth.js +156 -0
  23. package/engine/evm/claimGas.js +87 -0
  24. package/engine/evm/depositor.js +127 -0
  25. package/engine/evm/dex.js +836 -0
  26. package/engine/evm/escrow.js +208 -0
  27. package/engine/evm/factory.js +149 -0
  28. package/engine/evm/fees.js +90 -0
  29. package/engine/evm/funding.js +309 -0
  30. package/engine/evm/gasReserve.js +88 -0
  31. package/engine/evm/index.js +123 -0
  32. package/engine/evm/injected.js +175 -0
  33. package/engine/evm/permit.js +125 -0
  34. package/engine/evm/prices.js +179 -0
  35. package/engine/evm/session.js +777 -0
  36. package/engine/mock/chain.js +98 -0
  37. package/engine/monero/address.js +265 -0
  38. package/engine/monero/halvesSweep.js +68 -0
  39. package/engine/monero/node.js +110 -0
  40. package/engine/monero/scan.js +325 -0
  41. package/engine/monero/swapKeys.js +69 -0
  42. package/engine/monero/unlock.js +46 -0
  43. package/engine/monero/wallet.js +168 -0
  44. package/engine/recovery/recoveryFile.js +363 -0
  45. package/engine/recovery/restore.js +176 -0
  46. package/engine/sdk/bridge.js +609 -0
  47. package/engine/ui/dom.js +90 -0
  48. package/engine/ui/walletModal.js +132 -0
  49. package/index.d.ts +1417 -0
  50. package/package.json +74 -0
  51. package/src/actions.mjs +72 -0
  52. package/src/adapters/evm-wallet.mjs +126 -0
  53. package/src/adapters/index.mjs +21 -0
  54. package/src/adapters/local-storage.mjs +32 -0
  55. package/src/adapters/monero-wallet.mjs +185 -0
  56. package/src/adapters/monero.mjs +51 -0
  57. package/src/adapters/node-test-evm-wallet.mjs +345 -0
  58. package/src/codes.mjs +188 -0
  59. package/src/config.mjs +277 -0
  60. package/src/engine.mjs +53 -0
  61. package/src/errors.mjs +24 -0
  62. package/src/http.mjs +72 -0
  63. package/src/index.mjs +256 -0
  64. package/src/legs/evm.mjs +118 -0
  65. package/src/legs/index.mjs +50 -0
  66. package/src/levelsWalkSpec.mjs +183 -0
  67. package/src/lock.mjs +140 -0
  68. package/src/order.mjs +212 -0
  69. package/src/preflight.mjs +137 -0
  70. package/src/primitives.mjs +47 -0
  71. package/src/quoteEip712.mjs +35 -0
  72. package/src/quoteEip712Spec.mjs +214 -0
  73. package/src/quotes.mjs +465 -0
  74. package/src/record.mjs +65 -0
  75. package/src/recovery.mjs +40 -0
  76. package/src/sim.mjs +37 -0
  77. package/src/swap-flow.mjs +492 -0
  78. package/src/swaps.mjs +935 -0
  79. package/src/sweep.mjs +222 -0
  80. package/src/wallet.mjs +164 -0
  81. package/src/xmr-arrival.mjs +289 -0
@@ -0,0 +1,93 @@
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.
4
+ // Everything below this header is identical to the source.
5
+
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 и этой таблицей не ограничен.
30
+ 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 с (замер)" },
36
+
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 блок, замер): глубина выбрана" },
41
+ };
42
+
43
+ // СЕТЬ НЕ ОПИСАНА - ГОВОРИМ ОБ ЭТОМ, А НЕ ПОДСТАВЛЯЕМ МОЛЧА. Глубина 12 - прежнее общее значение, и оно
44
+ // честнее пустого места: решение принимается, но цена риска названа как неизмеренная.
45
+ export const DEFAULT_FINALITY = {
46
+ mode: "depth", depth: 12, costSec: 0,
47
+ reason: "сеть не описана в таблице: берём общую глубину, риск не измерен",
48
+ };
49
+
50
+ export function finalityFor(network) {
51
+ return FINALITY[network] || DEFAULT_FINALITY;
52
+ }
53
+
54
+ // Сколько эта сеть заставляет ждать перед необратимым решением - в секундах. Складывать с окнами сделки
55
+ // (readyBy, t1) обязан тот, кто эти окна назначает: окно, которое короче ожидания, убивает сделку.
56
+ export function finalityWaitSec(network) {
57
+ return Number(finalityFor(network).costSec || 0);
58
+ }
59
+
60
+ // ЧТО СКАЗАТЬ ЧЕЛОВЕКУ. Строка для экрана: уровень и цена ожидания словами, без чисел-обманок.
61
+ export function finalityNote(network) {
62
+ const f = finalityFor(network);
63
+ if (f.mode === "tag") {
64
+ const min = Math.max(1, Math.round(f.costSec / 60));
65
+ return "ждём уровень " + f.tag + ": это ≈" + min + " мин";
66
+ }
67
+ return "ждём " + f.depth + " подтверждений: это ≈" + Math.max(1, Math.round(f.costSec)) + " с";
68
+ }
69
+
70
+ // ЗНАЧОК ПРОВАЙДЕРА ДЛЯ СПИСКА. Короткий: человек решает, кого выбрать, и цена ожидания - часть выбора.
71
+ // Свой замер важнее слов провайдера: если его уровень назван тегом, берём измеренную цену из таблицы.
72
+ export function finalityBadge(policy) {
73
+ if (!policy) return "finality not stated";
74
+ if (policy.mode === "tag") {
75
+ const known = Object.values(FINALITY).find((f) => f.mode === "tag" && f.tag === policy.tag);
76
+ const cost = Number(policy.costSec || (known && known.costSec) || 0);
77
+ return cost >= 60 ? policy.tag + " · ≈" + Math.round(cost / 60) + " мин" : policy.tag + " · ≈" + Math.round(cost) + " с";
78
+ }
79
+ const d = Number(policy.depth || 0);
80
+ 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)) + " с";
82
+ }
83
+
84
+ // ОКНА ОРДЕРА ПО УРОВНЮ ПРОВАЙДЕРА. Окно короче ожидания убивает сделку: отметка готовности не успевает
85
+ // попасть в контракт, и сделка распадается. Поэтому окна считаются от цены ожидания, а не берутся числом.
86
+ // ЧИСЛА - РЕШЕНИЕ ВЛАДЕЛЬЦА (issue #88): при ПОКУПКЕ окно готовности 4 часа (человек должен успеть увидеть
87
+ // XMR и отметиться), а окно забора ПОСЛЕ readyBy - 1 час; формула ниже держит именно эту разницу: claim на час
88
+ // позже ready. При продаже сроки назначает нода (rfq/reverseOrder.mjs), там окно 1 час.
89
+ export function orderWindows(policy, base = { readyWindowSeconds: 14400, claimWindowSeconds: 3600 }) {
90
+ const wait = Number((policy && policy.costSec) || 0);
91
+ const ready = Math.max(base.readyWindowSeconds, wait + 900); // запас на внос и на дорогу транзакции
92
+ return { readyWindowSeconds: ready, claimWindowSeconds: Math.max(base.claimWindowSeconds, ready + 3600) };
93
+ }
@@ -0,0 +1,168 @@
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.
4
+ // Everything below this header is identical to the source.
5
+
6
+ // Форматирование чисел, времени и адресов.
7
+
8
+ const nf = (min, max) => new Intl.NumberFormat("en-US", { minimumFractionDigits: min, maximumFractionDigits: max });
9
+
10
+ export function amount(v, dp = 2) {
11
+ const n = Number(v);
12
+ if (!isFinite(n)) return "-";
13
+ const max = Math.min(dp, 6);
14
+ // ПЫЛЬ НЕ ИСЧЕЗАЕТ. Раньше печать была жёстко «шесть знаков», и сумма меньше 0.000001 показывалась как
15
+ // 0.000000 - то есть ненулевое значение выглядело нулём. Для денег это худший вид отображения: человек
16
+ // видит ноль там, где у него что-то есть. Знаки добавляются ровно в том случае, когда печать обнулила
17
+ // ненулевое, и не больше 18 (предел точности wei).
18
+ if (n !== 0 && Math.abs(n) < Math.pow(10, -max)) {
19
+ const need = Math.min(18, Math.max(max + 1, -Math.floor(Math.log10(Math.abs(n)))));
20
+ return nf(need, need).format(n).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
21
+ }
22
+ return nf(max, max).format(n);
23
+ }
24
+
25
+
26
+ export function usd(v) {
27
+ const n = Number(v);
28
+ if (!isFinite(n)) return "-";
29
+ return "$" + nf(0, Math.abs(n) < 100 ? 2 : 0).format(n);
30
+ }
31
+
32
+ export function pct(v, dp = 2) {
33
+ return (Number(v) * 100).toFixed(dp) + "%";
34
+ }
35
+
36
+ export function compact(v) {
37
+ const n = Number(v);
38
+ if (!isFinite(n)) return "-";
39
+ if (Math.abs(n) >= 1e9) return (n / 1e9).toFixed(2) + "B";
40
+ if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(2) + "M";
41
+ if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1) + "K";
42
+ return String(Math.round(n));
43
+ }
44
+
45
+ export function shortHash(h, head = 6, tail = 4) {
46
+ if (!h) return "-";
47
+ const s = String(h);
48
+ if (s.length <= head + tail + 2) return s;
49
+ return s.slice(0, head) + "..." + s.slice(-tail);
50
+ }
51
+
52
+ // sim-время -> настенные часы демки (UTC, как в макетах литпепера)
53
+ export function clockTime(ms) {
54
+ const d = new Date(ms);
55
+ const hh = String(d.getUTCHours()).padStart(2, "0");
56
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
57
+ const ss = String(d.getUTCSeconds()).padStart(2, "0");
58
+ return `${hh}:${mm}:${ss}`;
59
+ }
60
+
61
+ export function clockTimeShort(ms) {
62
+ return clockTime(ms).slice(0, 5);
63
+ }
64
+
65
+ // Длительность в секундах -> "12m 30s"
66
+ export function duration(ms) {
67
+ const total = Math.max(0, Math.round(ms / 1000));
68
+ const m = Math.floor(total / 60);
69
+ const s = total % 60;
70
+ if (m === 0) return `${s}s`;
71
+ return `${m}m ${String(s).padStart(2, "0")}s`;
72
+ }
73
+
74
+ export function countdown(ms) {
75
+ const total = Math.max(0, Math.round(ms / 1000));
76
+ const h = Math.floor(total / 3600);
77
+ const m = Math.floor((total % 3600) / 60);
78
+ const s = total % 60;
79
+ const pad = (x) => String(x).padStart(2, "0");
80
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
81
+ }
82
+
83
+ export function dateTime(iso) {
84
+ if (!iso) return "-";
85
+ const d = new Date(iso);
86
+ return d.toISOString().replace("T", " ").slice(0, 19) + " UTC";
87
+ }
88
+
89
+ export function randomHex(bytes = 16) {
90
+ const a = new Uint8Array(bytes);
91
+ crypto.getRandomValues(a);
92
+ return toHex(a);
93
+ }
94
+
95
+ // Байты (Uint8Array/Buffer) -> hex-строка без префикса 0x.
96
+ export function toHex(bytes) {
97
+ const a = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes || []);
98
+ return Array.from(a, (b) => b.toString(16).padStart(2, "0")).join("");
99
+ }
100
+
101
+ // РАЗБОР СУММЫ, КОТОРУЮ ВВЁЛ ЧЕЛОВЕК.
102
+ //
103
+ // ПОЧЕМУ ЭТО ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ replace ПРЯМО В ПОЛЕ. В полях суммы стояло
104
+ // `e.target.value.replace(/[^0-9.]/g, "")`: всё, кроме цифр и точки, СТИРАЛОСЬ. Европейская запятая
105
+ // ("0,05" - так пишут в половине стран) превращалась в "005", то есть в 5 - ошибка в СТО РАЗ, и заметить её
106
+ // в интерфейсе почти невозможно: поле показывает "005", котировка считается по пяти, человек платит в сто
107
+ // раз больше. Запятая без точки - десятичный разделитель, запятая при наличии точки - разделитель тысяч;
108
+ // так же это делают кошельки Monero.
109
+ //
110
+ // Возвращает КАНОНИЧЕСКУЮ строку (только цифры и максимум одна точка). Именно её видно в поле и именно её
111
+ // парсит остальной код - поэтому «что написано» и «что посчитано» не могут разойтись.
112
+ export function normalizeAmountInput(raw) {
113
+ let s = String(raw == null ? "" : raw).trim().replace(/[^0-9.,]/g, "");
114
+ if (s.includes(".")) {
115
+ // Точка есть - запятые разделяют тысячи ("1,234.5" -> "1234.5").
116
+ s = s.replace(/,/g, "");
117
+ } else if (s.includes(",")) {
118
+ // Одна запятая без точки - десятичный разделитель ("0,05" -> "0.05"). Всё, что стоит ПОСЛЕ второй
119
+ // запятой, отбрасываем: угадывать, что "1,2,3" значило 123, значит на опечатке увеличить платёж в сто раз.
120
+ const i = s.indexOf(",");
121
+ const rest = s.slice(i + 1);
122
+ const cut = rest.indexOf(",");
123
+ s = s.slice(0, i) + "." + (cut < 0 ? rest : rest.slice(0, cut));
124
+ }
125
+ // Вторая точка - тоже опечатка, и хвост за ней отбрасывается, а не склеивается с числом ("1.2.3" -> "1.2").
126
+ const dot = s.indexOf(".");
127
+ if (dot >= 0) {
128
+ const rest = s.slice(dot + 1);
129
+ const cut = rest.indexOf(".");
130
+ s = s.slice(0, dot + 1) + (cut < 0 ? rest : rest.slice(0, cut));
131
+ }
132
+ s = s.replace(/^0+(?=\d)/, ""); // "005" -> "5", но "0.5" не трогаем
133
+ if (!/[0-9]/.test(s)) return "";
134
+ return s;
135
+ }
136
+
137
+ /** Число из введённого человеком или null, если числа там нет. Никаких «догадок»: пусто - значит пусто. */
138
+ export function parseAmount(raw) {
139
+ const s = normalizeAmountInput(raw);
140
+ if (!s) return null;
141
+ const n = Number(s);
142
+ return Number.isFinite(n) ? n : null;
143
+ }
144
+
145
+ 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
+ // восстановления, в ссылках и в состоянии - они бы разъехались.
151
+ return randomHex(8);
152
+ }
153
+
154
+ // БЫСТРАЯ ПРИКИДКА ФОРМЫ Monero-адреса: base58, длина 95 (обычный) / 106 (integrated), префикс сети.
155
+ //
156
+ // ЧЕГО ЭТА ФУНКЦИЯ НЕ ДЕЛАЕТ: НЕ ПРОВЕРЯЕТ КОНТРОЛЬНУЮ СУММУ. Любая опечатка внутри адреса пройдёт здесь
157
+ // незамеченной. Раньше она называлась isValidMoneroAddress - и это имя было ловушкой: функция, которую
158
+ // зовут "проверкой адреса", но которая пропускает опечатку, рано или поздно попадает на путь денег.
159
+ //
160
+ // НАСТОЯЩАЯ ПРОВЕРКА - checkAddress() из ../monero/address.js: формат + разбор base58 + keccak256-контрольная
161
+ // сумма + совпадение сети. Именно её зовёт форма. Здесь - только чтобы отсеять явный мусор до тяжёлого разбора.
162
+ export function looksLikeMoneroAddress(addr) {
163
+ const a = String(addr || "").trim();
164
+ if (![95, 106].includes(a.length)) return false;
165
+ if (!/^[1-9A-HJ-NP-Za-km-z]+$/.test(a)) return false;
166
+ return ["4", "5", "7", "8", "9", "A", "B"].includes(a[0]);
167
+ }
168
+