@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,609 @@
1
+ // GENERATED FILE - a byte-for-byte copy of the engine module www/js/sdk/bridge.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
+ // СКЛЕЙНИК СТРАНИЦЫ С ЯДРОМ: ЕДИНСТВЕННОЕ МЕСТО, ГДЕ ЭКРАНЫ ЗНАЮТ ПРО SDK (sdk/).
7
+ //
8
+ // ЗАЧЕМ ОН. Логика обмена живёт в ядре (sdk/, пакет @ninsei-labs/sdk), а страница должна делать только
9
+ // разметку, состояние экрана и тексты. Пока этого шва не было, каждый экран звал движок сам, и логика
10
+ // расползалась по двум местам (.hermes/docs/59-ui-on-sdk.md). Теперь экран зовёт СКЛЕЙНИК, а про ядро знает
11
+ // только он.
12
+ //
13
+ // ЧТО СКЛЕЙНИК ПОДСОВЫВАЕТ ЯДРУ - ровно те швы, которые ядро просит снаружи и само достать не может:
14
+ // storage - хранилище страницы: сделки и сессия читаются оттуда же, где их кладёт страница (core/store.js);
15
+ // serverAuth - пропуск на наш сервис: подпись сообщения о входе умеет только кошелёк (evm/auth.js signIn),
16
+ // ядро получает уже ГОТОВЫЙ пропуск;
17
+ // evmCall - чтение цепи КОШЕЛЬКОМ СТРАНИЦЫ: своего провайдера у ядра нет и быть не должно;
18
+ // wallet - адаптер кошелька страницы (sdk/src/adapters/evm-wallet.mjs): подпись возможна только в нём.
19
+ //
20
+ // ЧЕГО СКЛЕЙНИК НЕ ДЕЛАЕТ. Он не считает ни порогов, ни порядка шагов, ни адреса эскроу: имена методов и
21
+ // пути - только те, что ЕСТЬ в ядре, а решения остаются в ядре.
22
+ //
23
+ // ПРО ДОСТУПНОСТЬ ЯДРА СТРАНИЦЕ - ТЕПЕРЬ ОНО СОБРАНО, А НЕ ЖДЁТ СБОРКИ.
24
+ // * sdk/src/primitives.mjs импортирует @noble/curves и @noble/hashes ГОЛЫМИ ИМЕНАМИ пакетов, поэтому
25
+ // странице отдаётся не исходник sdk/, а СОБРАННЫЙ БАНДЛ (tools/build-sdk.mjs -> www/assets/vendors/sdk/
26
+ // sdk-browser.js, тот же путь, что у крипто-бандла атомарного свопа). Бандл отдаёт ФАСАД ЦЕЛИКОМ:
27
+ // createNinsei (config/wallet/quotes/preflight/swaps/actions/order/sweep/recovery/sim) плюс адаптеры
28
+ // браузера (evmWallet, localStorageAdapter/memoryAdapter). Поэтому запуск сделки (swaps.start) и
29
+ // котировки фасада странице ДОСТУПНЫ - отказ вида "фасад странице не отдаётся" больше не ожидается.
30
+ // * Ссылка на бандл несёт ?v=<хеш> (tools/assetVersions.mjs, tools/stamp-assets.mjs): кеш пограничного
31
+ // слоя сбрасывается сам после пересборки.
32
+ //
33
+ // ЗАПАСНОГО ПУТИ У ДЕЙСТВИЙ ОРДЕРА БОЛЬШЕ НЕТ, И ЭТО НЕ ПОТЕРЯ, А СЛЕДСТВИЕ ПЕРЕЕЗДА (#32, волна 3). Раньше
34
+ // www/js/core/swap-flow.js лежал на странице, и при недоступном бандле действия ордера шли им же - «одни и те
35
+ // же функции», как здесь и было написано. Теперь движок сделки - СОБСТВЕННЫЙ модуль пакета
36
+ // (sdk/src/swap-flow.mjs): второй копии на странице нет, значит и запасного пути быть не может. Бандл не
37
+ // загрузился (нет файла, старый кеш, сеть) - это НАЗВАННЫЙ отказ с причиной в консоли (sdkBundle ниже), а не
38
+ // молчание и не подмена.
39
+
40
+ import { chainById, DEFAULT_CHAIN, MONERO, allowedProvidersFor, knownFactoryCodesFor } from "../core/config.js";
41
+ import { storage as pageStorage } from "../core/store.js";
42
+ import { token as authToken, signIn } from "../evm/auth.js";
43
+ import * as evm from "../evm/index.js";
44
+ // УСЛОВИЯ ОРДЕРА ДЛЯ ЯДРА СОБИРАЮТСЯ ИЗ ДАННЫХ, А НЕ ИЗ ПРАВИЛ: окна берутся у политики финальности
45
+ // (core/finality.js), соль - генератором движка (evm/funding.js). Своих сроков и своих чисел здесь нет.
46
+ import { orderWindows } from "../core/finality.js";
47
+ import { newSecret } from "../evm/funding.js";
48
+ // ВЫЗОВЫ ОРДЕРА - ДЕЙСТВИЕ И ЧТЕНИЕ - ОТ ЯДРА. Движок сделки переехал в пакет (#32, волна 3), и наружу
49
+ // склейник отдаёт ИМЕННО те же имена (markReadyOrder/claimOrder/refundOrder/orderStatus/orderDeadlines),
50
+ // поэтому экран меняет только источник, а не строку вызова - и стража, которая держит место вызова
51
+ // (tools/check-signing-guards.mjs, tools/check-arrival-depositor.mjs), видит прежний вызов.
52
+ // СПИСОК СДЕЛОК И ЕГО ОБСЛУЖИВАНИЕ (экран #/swaps). Состояния отдаёт ЯДРО: swaps.list - показ, swaps.sync -
53
+ // слияние с серверным списком, swaps.forget - уборка записи. Ядру для этого нужны только швы страницы
54
+ // (пропуск и хранилище), и оба уже подсунуты выше - своих правил слияния склейник не заводит.
55
+ //
56
+ // СЛЕЖЕНИЕ - МЕХАНИЗМ СТРАНИЦЫ, А НЕ ЯДРА: такт опроса живёт в app.js, поэтому фокус остаётся в
57
+ // core/swap.js, а экран только объявляет его склейнику.
58
+ import { setLiveFocus, dealSwapIds, serverRestoreHeightFor, STEP_LABELS,
59
+ confirmReady as engineConfirmReady, refundEth as engineRefundEth, sweepNow as engineSweepNow,
60
+ getSwap as engineGetSwap, saveSwap as engineSaveSwap } from "../core/swap.js";
61
+ // СТАВКА КОМИССИИ И СОСТОЯНИЕ ОРДЕРА - ЧТЕНИЕ ЦЕПИ КОШЕЛЬКОМ СТРАНИЦЫ: их берут справочник ("как это
62
+ // работает") и разбор recovery-файла. Страница получает их ОТСЮДА, а не из evm/* напрямую.
63
+ import { cachedFeeRate } from "../evm/fees.js";
64
+ // АДРЕС И ВЫСОТА MONERO - ОСОЗНАННЫЕ ЧТЕНИЯ СТРАНИЦЫ (экран хода): проверка формы адреса получателя и высота
65
+ // узла для высоты скана в файле восстановления. Логика адреса живёт в ядре (swaps.addressNetwork), здесь -
66
+ // запасной путь тем же модулем; высота узла - чтение ноды страницы, своего узла у ядра нет.
67
+ import { networkFromShape } from "../monero/address.js";
68
+ import { height as nodeHeightValue } from "../monero/node.js";
69
+
70
+ // АДРЕС ЯДРА СТРОКОЙ - СОБРАННЫЙ БАНДЛ ПОД www/ (tools/build-sdk.mjs): страница получает ядро ОДНИМ
71
+ // модулем, а не пачкой исходников sdk/. ?v=<хеш> проставляет tools/stamp-assets.mjs.
72
+ const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=bdb79a39";
73
+
74
+ // ОДНА ЗАГРУЗКА НА СТРАНИЦУ. Отказ - это состояние, а не поломка: он назван в консоли, и вызывающий решает.
75
+ let loading = null;
76
+ export async function sdkBundle() {
77
+ if (!loading) {
78
+ loading = (async () => {
79
+ try { return await import(SDK_BUNDLE); }
80
+ catch (error) {
81
+ console.warn("[bridge] собранное ядро SDK странице не отдалось (" + ((error && error.message) || error) +
82
+ "): действия идут прежним вызовом движка; см. www/js/sdk/bridge.js");
83
+ return null;
84
+ }
85
+ })();
86
+ }
87
+ return loading;
88
+ }
89
+
90
+ // ПОВЕРХНОСТИ ИЗ БАНДЛА - ИМЕННО ТЕМИ ИМЕНАМИ, ЧТО ИХ ЗОВУТ НИЖЕ. Так экрану не нужно знать, что ядро
91
+ // пришло одним файлом, а не тремя: форма вызова не меняется.
92
+ export async function sdkCoreModules() {
93
+ const mod = await sdkBundle();
94
+ if (!mod) return null;
95
+ return {
96
+ actions: { createActions: mod.createActions },
97
+ wallet: { evmWallet: mod.evmWallet },
98
+ recovery: { createRecovery: mod.createRecovery },
99
+ };
100
+ }
101
+
102
+ // ФАСАД ЯДРА (config/quotes/swaps/actions/recovery целиком) - ТОТ ЖЕ бандл, только нужен его createNinsei.
103
+ async function coreFacade() {
104
+ const mod = await sdkBundle();
105
+ return mod ? { factory: mod.createNinsei } : null;
106
+ }
107
+
108
+ function modeOf(chain) {
109
+ return chain && chain.escrow && chain.escrow.mode === "live" ? "live" : "sim";
110
+ }
111
+
112
+ // НАСТРОЙКИ ЯДРА ИЗ СТРАНИЦЫ. Ни одно значение здесь не решает ничего ЗА ядро: это адрес сервиса, сеть,
113
+ // хранилище и швы, о которых ядро спрашивает снаружи.
114
+ function optionsFor(chain) {
115
+ return {
116
+ settlement: { chain: chain.id },
117
+ xmrNetwork: MONERO.networkType,
118
+ apiBase: "/api",
119
+ assetsBase: "/",
120
+ mode: modeOf(chain),
121
+ // ХРАНИЛИЩЕ СТРАНИЦЫ СТАНОВИТСЯ ХРАНИЛИЩЕМ ЯДРА: записи сделок и сессия лежат в одном месте.
122
+ storage: pageStorage(),
123
+ // ПРОПУСК НА НАШ СЕРВИС: ядро только предъявляет его, добывает - кошелёк страницы.
124
+ serverAuth: async () => {
125
+ if (authToken()) return authToken();
126
+ try { await signIn(); } catch { return null; }
127
+ return authToken();
128
+ },
129
+ // ЧТЕНИЕ ЦЕПИ - КОШЕЛЬКОМ СТРАНИЦЫ (ядру нужен этот шов: своего провайдера у него нет).
130
+ evmCall: (request) => evm.readContract(request),
131
+ // КОД КОНТРАКТА - КОШЕЛЬКОМ СТРАНИЦЫ: нужен проверке кода фабрики (#103), extcodehash считается по нему.
132
+ evmCode: (address) => evm.readCode({ address }),
133
+ // НАШ СПИСОК ДОПУЩЕННЫХ - ПОЛИТИКА ИНТЕРФЕЙСА (#76/#77). Ядро само подтверждает у ЦЕПИ, чей ключ
134
+ // подписал котировку (реестр мейкеров); этот список решает, с кем торгует СТРАНИЦА. Провайдер вне
135
+ // списка не проходит кодом `quote-provider-not-allowed` - и отказ виден ДО подписи, потому что без
136
+ // твёрдой котировки нет ни ставки комиссии, ни замка. Список берётся из реестра сетей
137
+ // (core/config.js, allowedProvidersFor), а не выдумывается здесь; сеть без списка = политики нет.
138
+ allowedProviders: allowedProvidersFor(chain),
139
+ // СПИСОК ИЗВЕСТНЫХ СБОРОК ФАБРИКИ - ПОЛИТИКА ИНТЕРФЕЙСА (#103), рядом с allowedProviders. Фабрику ядро
140
+ // берёт из записи реестра провайдера, а её КОД сверяет с этим списком (extcodehash). Пусто = политики
141
+ // нет; тогда фабрика всё равно обязана совпасть с подписанной котировкой.
142
+ knownFactoryCodes: knownFactoryCodesFor(chain),
143
+ };
144
+ }
145
+
146
+ // ЭКЗЕМПЛЯР ЯДРА НА СЕТЬ: фасад держит хранилище и подписки, второй экземпляр на ту же сеть завёл бы второй
147
+ // опрос. Недоступен - null, и вызывающий сам называет отказ.
148
+ const instances = new Map();
149
+ async function instanceFor(chainSlug) {
150
+ const facade = await coreFacade();
151
+ if (!facade) return null;
152
+ const chain = chainById(chainSlug || DEFAULT_CHAIN);
153
+ if (!instances.has(chain.id)) instances.set(chain.id, facade.factory(optionsFor(chain)));
154
+ return { chain, sdk: instances.get(chain.id) };
155
+ }
156
+
157
+ // КОШЕЛОК СТРАНИЦЫ ДЛЯ ЯДРА: адаптер поверх ЕГО провайдера. Берётся В МОМЕНТ действия: провайдер
158
+ // появляется вместе с подключением, а не при загрузке страницы.
159
+ function walletFor(mods) {
160
+ const provider = typeof evm.currentProvider === "function" ? evm.currentProvider() : null;
161
+ if (!provider) return null;
162
+ return mods.wallet.evmWallet(provider);
163
+ }
164
+
165
+ // --- ДЕЙСТВИЯ ОРДЕРА -----------------------------------------------------------------------------
166
+ // ИМЕНА И ФОРМА ЗАПРОСА - ТЕ ЖЕ, ЧТО У ПРЕЖНИХ ВЫЗОВОВ ЭКРАНА. Экран передаёт ожидания ордера (expect) и
167
+ // ничего не пересчитывает: ядро сверяет слоты с цепью ПЕРЕД подписью и без ожиданий не подписывает.
168
+ // Кошелёк - тот же, что подписывает страница; сеть ядру здесь не нужна (чтение и отправка идут тем же
169
+ // провайдером, что и раньше).
170
+ export async function markReadyOrder({ escrow, expect } = {}) {
171
+ const mods = await sdkCoreModules();
172
+ const wallet = mods ? walletFor(mods) : null;
173
+ // ЯДРО И КОШЕЛЁК - ОБЯЗАТЕЛЬНЫ: запасного пути (движок на странице) больше нет. Отказ назван, а не молчание.
174
+ if (!mods || !wallet) throw new Error("[bridge] отметку готовности подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
175
+ return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
176
+ .markReady({ escrow, expect }, wallet);
177
+ }
178
+
179
+ export async function claimOrder({ escrow, halfClaimer, expect } = {}) {
180
+ const mods = await sdkCoreModules();
181
+ const wallet = mods ? walletFor(mods) : null;
182
+ if (!mods || !wallet) throw new Error("[bridge] забор подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
183
+ return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
184
+ .claim({ escrow, halfClaimer, expect }, wallet);
185
+ }
186
+
187
+ export async function refundOrder({ escrow, halfLocker } = {}) {
188
+ const mods = await sdkCoreModules();
189
+ const wallet = mods ? walletFor(mods) : null;
190
+ if (!mods || !wallet) throw new Error("[bridge] возврат подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
191
+ // ОЖИДАНИЙ ЗДЕСЬ НЕТ НАМЕРЕННО: экран возврата их не передавал, и дописать их значило бы включить сверку
192
+ // слотов там, где её не было, - то есть изменить поведение возврата.
193
+ return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
194
+ .refund({ escrow, halfLocker }, wallet);
195
+ }
196
+
197
+ // --- АВТОМАТИЧЕСКИЙ РАННИЙ ВОЗВРАТ (issue #88) ------------------------------------------------------
198
+ // РЕШЕНИЕ ОСТАЁТСЯ В ЯДРЕ. Экран лишь передаёт запись сделки, ожидания ордера и сеть; саму проверку прихода
199
+ // (две ноды разных операторов на одном блоке) и решение "пора вернуть" делает ядро - swaps.refundIfXmrMissing.
200
+ // Ноды-свидетели берутся из настроек ТОГО ЖЕ ядра (config.nodesFor), а не выдумываются здесь: список живёт
201
+ // одной правдой на сеть. Кошелёк - тот же адаптер страницы, что подписывает прочие действия ордера.
202
+ export async function sdkAutoRefund(request) {
203
+ const ctx = await instanceFor(request && request.chain);
204
+ if (!ctx) return null;
205
+ const req = { ...(request || {}) };
206
+ if (!req.nodes && ctx.sdk.config && typeof ctx.sdk.config.nodesFor === "function") {
207
+ req.nodes = ctx.sdk.config.nodesFor(req.xmrNetwork || MONERO.networkType);
208
+ }
209
+ if (!req.wallet) {
210
+ const mods = await sdkCoreModules();
211
+ const wallet = mods ? walletFor(mods) : null;
212
+ if (wallet) req.wallet = wallet;
213
+ }
214
+ return ctx.sdk.swaps.refundIfXmrMissing(req);
215
+ }
216
+
217
+ // --- РАЗБОР RECOVERY-ФАЙЛА -----------------------------------------------------------------------
218
+ // Модуль разбора браузеру отдаётся, поэтому это настоящий путь ядра, а не запасной.
219
+ export async function sdkOpenRecovery(file, passphrase) {
220
+ const mods = await sdkCoreModules();
221
+ if (!mods) return null;
222
+ return await mods.recovery.createRecovery().open(file, passphrase);
223
+ }
224
+
225
+ // --- ПОВЕРХНОСТИ ФАСАДА (котировки, состояние сделок, запуск сделки) -------------------------------
226
+ // Ядро собрано странице бандлом, поэтому эти вызовы идут через НАСТОЯЩИЙ фасад. null они отвечают только
227
+ // если сам бандл не загрузился - и тогда причина названа в консоли, а не спрятана.
228
+
229
+ export async function sdkWatchQuotes(request, onSnapshot) {
230
+ const ctx = await instanceFor(request && request.chain);
231
+ if (!ctx) return null;
232
+ return ctx.sdk.quotes.watch(request, onSnapshot);
233
+ }
234
+
235
+ export async function sdkQuotesSnapshot(chainSlug) {
236
+ const ctx = await instanceFor(chainSlug);
237
+ return ctx ? ctx.sdk.quotes.snapshot() : null;
238
+ }
239
+
240
+ // --- ИНДИКАТИВНАЯ КНИГА КОТИРОВОК НА ЯДРЕ ---------------------------------------------------------
241
+ // КНИГА (предложения и отказы) ПРИХОДИТ ОТ ЯДРА: quotes.watch подписывается на опрос ядра, quotes.snapshot
242
+ // отдаёт последний снимок без ожидания. Склейник здесь делает ровно ОДНУ работу - ПЕРЕВОД ФОРМЫ: имена
243
+ // полей снимка ядра приводятся к тем, которые уже рисует экран. Ни одного правила отбора или приоритета
244
+ // здесь нет: что предложение, что отказ, что лучший - решает ядро (sdk/src/quotes.mjs, readBook). Ядро
245
+ // молчит или отказало - книга пуста, и прежняя (движковая) книга не подставляется: её больше нет.
246
+ //
247
+ // ПОДПИСКА ОДНА. Ядро опрашивает на время подписки; второй слушатель был бы вторым опросом, поэтому
248
+ // прежняя подписка снимается ПЕРЕД новой. Последний снимок переносится через переподписку: читающий видит
249
+ // прежнюю книгу, пока не придёт свежая, - а не пустоту на каждом такте.
250
+ let bookStop = null; // отписка от текущей подписки ядра
251
+ let bookKey = null; // под какой запрос (сеть|сторона|токен|размер) эта подписка поставлена
252
+ let bookCore = null; // последний снимок ЯДРА (не перевода): перевод делается на чтение
253
+ let bookResume = null; // ожидающий первого такта после подписки
254
+ // ШОВ-ПОДСТАНОВКА (sdkBookUse): поверхность котировок ядра, принесённая снаружи. Нужна проверкам, которые
255
+ // идут БЕЗ браузера: самого ядра в бандле там нет, а книгу проверить надо. В бою не задаётся.
256
+ let bookQuotes = null;
257
+
258
+ // ЯДРО ДЛЯ ПРОВЕРОК БЕЗ БРАУЗЕРА: кладём поверхность котировок (watch/snapshot) и работаем с ней.
259
+ export function sdkBookUse(quotesApi) {
260
+ bookQuotes = quotesApi || null;
261
+ bookCore = null;
262
+ bookKey = null;
263
+ bookResume = null;
264
+ if (bookStop) { try { bookStop(); } catch { /* уже снята */ } }
265
+ bookStop = null;
266
+ }
267
+
268
+ // ПЕРЕВОД СТРОКИ ЯДРА В СТРОКУ ЭКРАНА. Только имена: значения переносятся как есть. Возраст считается
269
+ // ЗДЕСЬ (от собственной метки котировки): ядро посчитало его на своём такте, а показ спрашивает свежесть
270
+ // сейчас - иначе срок жизни котировки «омолаживался» бы на такт опроса.
271
+ function bookRow(o) {
272
+ const at = Number.isFinite(Number(o.at)) ? Number(o.at) : null;
273
+ return {
274
+ providerId: o.providerId, displayName: o.displayName, pair: o.pair,
275
+ asset: o.asset, currency: o.currency,
276
+ assetNetwork: o.assetNetwork, currencyNetwork: o.currencyNetwork, networkStated: o.networkStated === true,
277
+ rateQuote: o.rateQuote, rate: o.rate,
278
+ min: o.min, max: o.max, step: o.step, ttlMs: o.ttlMs,
279
+ at, ageMs: at === null ? null : Math.max(0, Date.now() - at),
280
+ seq: o.seq, why: o.why, signature: o.signature, keyId: o.keyId, finality: o.finality,
281
+ networksDerived: o.networksDerived === true, networksUnnamed: o.networksUnnamed === true,
282
+ };
283
+ }
284
+
285
+ // ВИД ОТКАЗА, КОТОРЫМ ЭКРАН НАЗЫВАЕТ ПРИЧИНУ. Код ядра `disabled` экран зовёт `off`; имена прочих кодов
286
+ // совпадают, и переименовывать их незачем.
287
+ const REFUSAL_KIND = { disabled: "off" };
288
+ function bookRefusal(r) {
289
+ return { ...bookRow(r), code: r.code, kind: REFUSAL_KIND[r.code] || r.code, why: r.why };
290
+ }
291
+
292
+ // СНИМОК ЯДРА -> СНИМОК ЭКРАНА. Форма та же, что была у экрана: ok/error/data/at, а в data - references
293
+ // (справочный курс) и best. Отказ связи не превращается в прежние числа: ok снимается, и экран показывает
294
+ // честное состояние. Молчание ядра (снимка нет) - тоже пустая книга, а не прежняя.
295
+ function bookOf(core) {
296
+ if (!core) return { ok: false, error: null, data: null, at: 0, fetching: false };
297
+ const offers = (Array.isArray(core.offers) ? core.offers : []).map(bookRow);
298
+ const refused = (Array.isArray(core.refused) ? core.refused : []).map(bookRefusal);
299
+ const stamps = offers.map((o) => o.at).filter((v) => Number.isFinite(v));
300
+ return {
301
+ ok: !core.error,
302
+ error: core.error ? String(core.error.code || "unknown") : null,
303
+ at: core.at,
304
+ fetching: false,
305
+ data: {
306
+ offers, refused, quotes: offers,
307
+ // ЛУЧШЕЕ, КАК ЕГО НАЗВАЛ СЕРВИС: по нему экран подтверждает свою метку «лучшее» по паре и провайдеру.
308
+ best: core.best || null,
309
+ seq: core.seq === undefined ? null : core.seq,
310
+ references: Array.isArray(core.references) ? core.references : [],
311
+ at: stamps.length ? Math.max(...stamps) : null,
312
+ },
313
+ };
314
+ }
315
+
316
+ // ЗАПРОС КНИГИ У ЯДРА. Подписка ОДНА и переигрывается только когда СМЕНИЛСЯ ЗАПРОС: пока размер и сторона
317
+ // те же, ядро опрашивает книгу своим тактом, и второго опроса не заводится. Когда запрос сменился,
318
+ // вызывающий ждёт первый такт и получает СВЕЖИЙ снимок, а не прежний. Ядро не поднялось - пусто (и это
319
+ // называет причина в консоли).
320
+ export async function sdkBook({ chain, direction, token, size } = {}) {
321
+ let quotes = bookQuotes;
322
+ let instance = "injected"; // у подстановки (шва) сети нет: её экземпляр один
323
+ if (!quotes) {
324
+ const ctx = await instanceFor(chain);
325
+ if (!ctx) {
326
+ if (bookStop) { try { bookStop(); } catch { /* уже снята */ } bookStop = null; }
327
+ bookKey = null;
328
+ bookResume = null;
329
+ bookCore = null;
330
+ return null;
331
+ }
332
+ quotes = ctx.sdk.quotes;
333
+ instance = ctx.chain.id; // книги разных сетей смешивать нельзя: сеть - часть ключа
334
+ }
335
+ const dir = direction === "sell" ? "sell" : "buy";
336
+ const key = instance + "|" + dir + "|" + String(token || "") + "|" + String(size === undefined || size === null ? "" : size);
337
+ // ТОТ ЖЕ ЗАПРОС - ОПРОС УЖЕ ИДЁТ: возвращаем последний снимок, второй подписки не заводим. Иначе каждый
338
+ // такт экрана сбрасывал бы состояние ядра и на мгновение отдавал бы пустую книгу читающему её напрямую.
339
+ if (bookStop && bookKey === key) return bookCore;
340
+ if (bookStop) { try { bookStop(); } catch { /* уже снята */ } bookStop = null; }
341
+ bookKey = key;
342
+ bookResume = null;
343
+ const wait = new Promise((resolve) => { bookResume = resolve; });
344
+ try {
345
+ bookStop = quotes.watch({ direction: dir, token, size }, (snap) => {
346
+ bookCore = snap;
347
+ if (bookResume) { const resume = bookResume; bookResume = null; resume(); }
348
+ });
349
+ } catch (error) {
350
+ bookResume = null;
351
+ bookCore = null;
352
+ console.warn("[bridge] книга котировок ядру не отдалась (" + ((error && error.message) || error) +
353
+ "): предложений не будет; см. www/js/sdk/bridge.js");
354
+ return null;
355
+ }
356
+ await wait;
357
+ return bookCore;
358
+ }
359
+
360
+ // ПОСЛЕДНИЙ СНИМОК БЕЗ ОЖИДАНИЯ - в форме экрана. Ядро не опрошено ни разу - пусто (и это честно).
361
+ export function sdkBookSnapshot() {
362
+ return bookOf(bookCore);
363
+ }
364
+
365
+ export async function sdkListSwaps(chainSlug) {
366
+ const ctx = await instanceFor(chainSlug);
367
+ return ctx ? ctx.sdk.swaps.list() : null;
368
+ }
369
+
370
+ export async function sdkGetSwap(id, chainSlug) {
371
+ const ctx = await instanceFor(chainSlug);
372
+ return ctx ? ctx.sdk.swaps.get(id) : null;
373
+ }
374
+
375
+ // УСЛОВИЯ ОРДЕРА - ДАННЫЕ ДЛЯ ЯДРА, А НЕ ЕГО ПРАВИЛА.
376
+ // Ядро требует эти восемь полей СНАРУЖИ (sdk/index.d.ts, StartRequest.order) и без них отказывает: к ним
377
+ // привязаны и доказательство DLEQ, и подпись твёрдой котировки (atomic/orderContext.js, FIELDS). Здесь они не
378
+ // выводятся заново, а берутся из того же, чем пользовалась страница: сеть и фабрика - из реестра сетей,
379
+ // вносит - подключённый кошелёк, сумма - из котировки, окна - из политики финальности провайдера
380
+ // (core/finality.js, orderWindows), соль - генератором движка (evm/funding.js, newSecret). Своих окон и
381
+ // своих чисел здесь не появляется: правило о минимальных окнах живёт в core/finality.js и core/preSignGuards.js.
382
+ export function sdkOrderTerms({ chain, amountWei, finality = null, salt = null, asset = "XMR" } = {}) {
383
+ const windows = orderWindows(finality);
384
+ const nowSec = Math.floor(Date.now() / 1000);
385
+ const escrow = (chain && chain.escrow) || {};
386
+ // КАССА КОМИССИИ - ЧАСТЬ УСЛОВИЙ ОРДЕРА (#103), как и фабрика: приходит из реестра сетей, а не из
387
+ // контракта. Без неё ордер не собрать: условие обязательное и входит в termsHash.
388
+ if (!escrow.cashier) throw new Error("в этой сети не задана касса комиссии (escrow.cashier) - ордер собрать нечем");
389
+ return {
390
+ chainId: chain.chainId,
391
+ factory: escrow.address,
392
+ cashier: escrow.cashier,
393
+ locker: evm.address(),
394
+ amount: String(amountWei),
395
+ // ЧТО ЧЕЛОВЕК ПОЛУЧАЕТ. В канонический контекст ордера поле не входит (это следствие пары и сети), но
396
+ // провайдер его ждёт - по нему он понимает, что выплата в XMR, и кладёт в свою запись котировки.
397
+ // Значение приходит данными от экрана; умолчание - XMR (экран покупки XMR за нативную монету).
398
+ asset: String(asset || "XMR"),
399
+ readyBy: String(nowSec + windows.readyWindowSeconds),
400
+ t1: String(nowSec + windows.claimWindowSeconds),
401
+ salt: salt || newSecret(),
402
+ };
403
+ }
404
+
405
+ // ТВЁРДАЯ КОТИРОВКА ПОД ОРДЕР - ДО ПОДПИСИ. Экран показывает ставку и получателя комиссии РАНЬШЕ, чем
406
+ // человек подпишет (пункт 7 из #76), а ТУ ЖЕ САМУЮ котировку отдаёт в запуск сделки (sdkStartSwap,
407
+ // поле orderQuote): подписывается ровно то, что показано, и второго запроса к ноде не делается.
408
+ //
409
+ // ПОЧЕМУ ЭТО МЕСТО, А НЕ ЭКРАН. Условия ордера (сеть, фабрика, вносивший, сумма, окна, соль) собираются
410
+ // ОДНИМ местом - sdkOrderTerms; экран не должен собирать их второй редакцией. Здесь они собираются, по ним
411
+ // запрашивается твёрдая котировка тем же путём ядра, и наружу уходят оба: `order` и `quote`.
412
+ export async function sdkOrderQuote({ chain, providerId, amountWei, finality = null, salt = null, asset = "XMR" } = {}) {
413
+ const ctx = await instanceFor(chain);
414
+ if (!ctx) return null;
415
+ const order = sdkOrderTerms({ chain: ctx.chain, amountWei, finality, salt, asset });
416
+ const quote = await ctx.sdk.quotes.firm({ providerId, order });
417
+ return { order, quote };
418
+ }
419
+
420
+ // ЗАПУСК СДЕЛКИ ЯДРОМ ЦЕЛИКОМ. Порядок шагов, сторона ордера, сроки в контракт, адрес эскроу ИЗ КВИТАНЦИИ,
421
+ // отметка у нас и наблюдение за XMR - всё это держит ядро (sdk/src/swaps.mjs, swaps.start). Экран отдаёт
422
+ // только данные: котировку, куда получить, пароль, кошелёк и обработчик подтверждения файла. Своей стороны
423
+ // ордера, своих адресов и своих транзакций страница здесь не собирает.
424
+ export async function sdkStartSwap(request) {
425
+ const ctx = await instanceFor(request && request.chain);
426
+ if (!ctx) return null;
427
+ const req = { ...(request || {}) };
428
+ // КОШЕЛОК ДЛЯ ЗАМКА - АДАПТЕР КОШЕЛЬКА СТРАНИЦЫ, ЕСЛИ ВЫЗЫВАЮЩИЙ ЕГО НЕ ПРИНЁС. Подпись возможна только
429
+ // в кошелёк-провайдере страницы, а своего провайдера у ядра нет: ядро получает готовый адаптер (send/receipt).
430
+ if (!req.wallet) {
431
+ const mods = await sdkCoreModules();
432
+ const wallet = mods ? walletFor(mods) : null;
433
+ if (wallet) req.wallet = wallet;
434
+ }
435
+ // КОШЕЛОК СТРАНИЦЫ ОТДАЁТСЯ ЯДРУ И КАК ЕГО КОШЕЛЁК: стражи сети (preflight) спрашивают сеть У КОШЕЛЬКА
436
+ // ЯДРА, а не у того, что передан на замок, - иначе запуск отказывает кодом network { wallet-unknown }
437
+ // (так делает и живой прогон из Node: sdk.wallet.use(...); await sdk.wallet.connect()). Подпись от этого
438
+ // не меняется: и подключение, и отправка идут через тот же провайдер страницы.
439
+ if (req.wallet) {
440
+ try { ctx.sdk.wallet.use(req.wallet); await ctx.sdk.wallet.connect({ chainId: ctx.chain.chainId }); }
441
+ catch { /* кошелёк не поднялся - отказ назовёт само ядро (network/wallet-unknown), тишины нет */ }
442
+ }
443
+ // Условий ордера нет - собираем их из данных (см. sdkOrderTerms). Контракт StartRequest допускает и
444
+ // готовые: тогда вызывающий приносит свою сторону ордера со своими точками (инъекция записи).
445
+ if (!req.order) {
446
+ req.order = sdkOrderTerms({ chain: ctx.chain, amountWei: req.amountWei, finality: req.finality,
447
+ salt: req.salt, asset: req.receiveAsset });
448
+ }
449
+ // ТВЁРДАЯ КОТИРОВКА ПОД ЭТОТ ОРДЕР - И ОНА ЖЕ УХОДИТ В ЗАМОК. Экран, показавший комиссию ДО подписи,
450
+ // приносит котировку ГОТОВОЙ (sdkOrderQuote) - тогда второй раз к ноде не идём и подписывается ровно то,
451
+ // что человек видел. Если не принёс, запрашиваем здесь тем же путём ядра (config.route("orderQuote")).
452
+ //
453
+ // ЗАЧЕМ ЭТО ОТДЕЛЬНОЕ ПОЛЕ. Замок берёт котировку НЕ из req.quote: req.quote - индикативная котировка
454
+ // книги для стражей (свежесть, сроки), а фабрике нужен ПОДПИСАННЫЙ НАБОР (комиссия, реестр, провайдер).
455
+ // Ядро читает его как `request.orderQuote` (sdk/src/swaps.mjs). Пока мост его не клал, запуск доходил до
456
+ // внесения и падал там: "нужна подписанная котировка: фабрика принимает котировку, а не голые условия ордера".
457
+ if (!req.orderQuote && req.providerId) {
458
+ req.orderQuote = await ctx.sdk.quotes.firm({ providerId: req.providerId, order: req.order });
459
+ }
460
+ if (req.orderQuote) {
461
+ // ЗАБИРАЮЩЕГО НАЗЫВАЕТ ПРОВАЙДЕР, и он входит в контекст ордера: без него доказательство не сойдётся
462
+ // (ядро так и откажет - context mismatch). Здесь только переносим его значение в условия.
463
+ if (req.orderQuote.claimer) req.order = { ...req.order, claimer: req.orderQuote.claimer };
464
+ // ID КОТИРОВКИ ПОД ОРДЕР УХОДИТ В ЗАПИСЬ СДЕЛКИ: по нему наш сервис связывает эскроу с записью ноды
465
+ // провайдера, и только после этого нода может забрать ETH. Экран это делал сам (swap.orderQuoteId) -
466
+ // значит данные остаются теми же, и терять их при переводе нельзя.
467
+ if (req.orderQuote.id) req.quote = { ...(req.quote || {}), id: req.orderQuote.id };
468
+ // СТОРОНА КОНТРАГЕНТА - ЭТА ЖЕ КОТИРОВКА: в ней половина, точка и доказательство провайдера, и её же
469
+ // проверяет ядро (order.acceptCounterparty). Своей стороны контрагента мост не выдумывает.
470
+ if (!req.counterparty) req.counterparty = req.orderQuote;
471
+ }
472
+ // СРОКИ ОРДЕРА УХОДЯТ И В КОТИРОВКУ: стражи сроков (preflight -> deadlineVerdict) читают readyBy/t1 ИЗ
473
+ // КОТИРОВКИ, а не из условий. Значения ТЕ ЖЕ, что ушли в order - второго счёта и второго мнения не
474
+ // появляется; иначе ядро честно отказывает кодом deadline { kind: "deadlines-unstated" }.
475
+ req.quote = { ...(req.quote || {}), readyBy: Number(req.order.readyBy), t1: Number(req.order.t1) };
476
+ return ctx.sdk.swaps.start(req);
477
+ }
478
+
479
+ // НАБЛЮДЕНИЕ ЗА ПРИХОДОМ XMR - ТОТ ЖЕ ШАГ ЯДРА, ЧТО И У ЗАПУСКА (GET /api/swaps/{id}). Отдельная функция
480
+ // нужна экранам состояния: они ждут приход, а не начинают сделку. Ожидания ордера и кошелёк передаёт
481
+ // вызывающий - без них ядро не подписывает отметку готовности, а лишь называет её следующий шаг.
482
+ export async function sdkWatchSwap(request) {
483
+ const ctx = await instanceFor(request && request.chain);
484
+ if (!ctx) return null;
485
+ return ctx.sdk.swaps.watch(request);
486
+ }
487
+
488
+ // --- СПИСОК СДЕЛОК ОТ ЯДРА (экран #/swaps) ---------------------------------------------------------
489
+ // ЧТЕНИЕ, СЛИЯНИЕ И УБОРКА ИДУТ ЧЕРЕЗ ЯДРО, А НЕ ЧЕРЕЗ ДВИЖОК НА ЭКРАНЕ. sdkListSwaps выше отдаёт
490
+ // состояния сделок (swaps.list); ниже - серверный список (swaps.sync: путь и правило слияния живут в ядре)
491
+ // и уборка записи (swaps.forget: она убирает запись только здесь, на сервере и в контракте всё остаётся).
492
+ // Ядро не поднялось - null, и причину называет консоль (см. sdkBundle), а не молчание.
493
+ export async function sdkSyncSwaps(chainSlug) {
494
+ const ctx = await instanceFor(chainSlug);
495
+ return ctx ? ctx.sdk.swaps.sync() : null;
496
+ }
497
+
498
+ export async function sdkForgetSwap(id, chainSlug) {
499
+ const ctx = await instanceFor(chainSlug);
500
+ return ctx ? ctx.sdk.swaps.forget(id) : null;
501
+ }
502
+
503
+ // СЛЕЖЕНИЕ И ЧТЕНИЯ СТРАНИЦЫ - ЧЕРЕЗ ТОТ ЖЕ ШОВ, ПОД ПРЕЖНИМИ ИМЕНАМИ. Экран объявляет фокус опроса и
504
+ // читает ставку комиссии (справочник) ЗДЕСЬ, а не из core/ и evm/ напрямую. Имена оставлены ДОСЛОВНО теми
505
+ // же, что были у движка (тот же приём, что у markReadyOrder/claimOrder/refundOrder выше): экран меняет
506
+ // ИСТОЧНИК импорта, а не строку вызова. Состояние ордера (orderStatus/orderDeadlines) объявлено функциями
507
+ // выше - это те же чтения, но теперь правила чтения живут в пакете, а не в www/js/core/swap-flow.js.
508
+ export { setLiveFocus, cachedFeeRate, dealSwapIds, serverRestoreHeightFor, STEP_LABELS };
509
+
510
+ // СОСТОЯНИЕ ОРДЕРА И ЕГО СРОКИ - ЧТЕНИЕ ЦЕПИ КОШЕЛЬКОМ СТРАНИЦЫ, НО ЧЕРЕЗ ЯДРО (#32, волна 3). Сами правила
511
+ // чтения (status()/t1()/termsHash()/readyBy() и разбор ответов) переехали в пакет (sdk/src/swap-flow.mjs,
512
+ // фасад order.status/order.deadlines). Склейник отдаёт их экрану ПОД ПРЕЖНИМИ ИМЕНАМИ, а кошелёк-читатель
513
+ // подсовывает ядру тот же, что и у действий ордера (optionsFor -> evmCall), поэтому читает тот же кошелёк,
514
+ // что и подпишет. Ядро не загрузилось - НАЗВАННЫЙ отказ, а не молчание и не пустое состояние.
515
+ export async function orderStatus(escrow) {
516
+ const ctx = await instanceFor(undefined);
517
+ if (!ctx) throw new Error("[bridge] состояние ордера не прочитать: ядро SDK странице не отдалось");
518
+ return ctx.sdk.order.status(escrow);
519
+ }
520
+
521
+ export async function orderDeadlines(escrow) {
522
+ const ctx = await instanceFor(undefined);
523
+ if (!ctx) throw new Error("[bridge] сроки ордера не прочитать: ядро SDK странице не отдалось");
524
+ return ctx.sdk.order.deadlines(escrow);
525
+ }
526
+
527
+ // ВЫСОТА УЗЛА MONERO - ЧТЕНИЕ СТРАНИЦЫ. Своего нода у ядра нет (все чтения цепи идут через адаптер кошелька
528
+ // страницы), поэтому высота для высоты скана берётся здесь, а не выдумывается. Имя сохранено (nodeHeight).
529
+ export async function nodeHeight() { return nodeHeightValue(); }
530
+
531
+ // --- СОСТОЯНИЕ И ДЕЙСТВИЯ ДЕМОНСТРАЦИОННОЙ СДЕЛКИ (экран хода, progress.js) -------------------------
532
+ // СОСТОЯНИЕ ОТДАЁТ ЯДРО: swaps.progress собирает снимок сделки (запись + вывод состояния + кошелёк из половин).
533
+ // Прежней движковой дороги у экрана НЕТ (в отличие от действий ордера выше): ядро не поднялось - null, и экран
534
+ // честно говорит, что состояния нет. Так же устроен и экран списка (sdkListSwaps).
535
+ export async function sdkProgress(id, chainSlug) {
536
+ const ctx = await instanceFor(chainSlug);
537
+ return ctx ? ctx.sdk.swaps.progress(id) : null;
538
+ }
539
+
540
+ // ДЕЙСТВИЯ И ЗАПИСЬ ДЕМОНСТРАЦИОННОЙ СДЕЛКИ - через ядро, с прежним вызовом движка как запасным путём: это ТЕ ЖЕ
541
+ // функции (sdk/src/swaps.mjs зовёт engine.swap.*). Имена - ядра (swaps.confirmReady/refundEth/sweepNow/
542
+ // setDestination), а не строки вызова экрана.
543
+ export async function sdkConfirmReady(id) {
544
+ const ctx = await instanceFor(undefined);
545
+ if (ctx) return ctx.sdk.swaps.confirmReady(id);
546
+ return engineConfirmReady(id);
547
+ }
548
+
549
+ export async function sdkRefundEth(id, by = "you") {
550
+ const ctx = await instanceFor(undefined);
551
+ if (ctx) return ctx.sdk.swaps.refundEth(id, by);
552
+ return engineRefundEth(id, by);
553
+ }
554
+
555
+ export async function sdkSweepNow(id) {
556
+ const ctx = await instanceFor(undefined);
557
+ if (ctx) return ctx.sdk.swaps.sweepNow(id);
558
+ return engineSweepNow(id);
559
+ }
560
+
561
+ export async function sdkSetDestination(id, address) {
562
+ const ctx = await instanceFor(undefined);
563
+ if (ctx) return ctx.sdk.swaps.setDestination(id, address);
564
+ const swap = engineGetSwap(id);
565
+ if (!swap) return null;
566
+ swap.receiveAddress = address;
567
+ return engineSaveSwap(swap);
568
+ }
569
+
570
+ // СЕТЬ АДРЕСА MONERO - ПОВЕРХНОСТЬ ЯДРА (оно владеет форматом адреса): склейник только отдаёт её экрану.
571
+ // Запасной путь - тот же модуль, что зеркалит ядро (www/js/monero/address.js): второй таблицы префиксов нет.
572
+ export async function sdkAddressNetwork(address) {
573
+ const ctx = await instanceFor(undefined);
574
+ if (ctx) return ctx.sdk.swaps.addressNetwork(address);
575
+ try { return networkFromShape(address); } catch { return null; }
576
+ }
577
+
578
+ // --- ВИТРИНА И МЕХАНИЗМЫ СТРАНИЦЫ: ФОРМА, ПОДПИСЬ, ПРОДАЖА, ОСНОВАНИЕ -------------------------------
579
+ // ЗДЕСЬ ЖИВЁТ ТО, ЧТО ЭКРАНЫ БРАЛИ У ДВИЖКА НАПРЯМУЮ, а теперь берут ТЕМ ЖЕ ШВОМ - под теми же именами.
580
+ // Это ОСОЗНАННО оставшееся и названное, а не забытое:
581
+ // * evm, swapCore, moneroNode - механизмы СТРАНИЦЫ: кошелёк (подпись возможна только в его провайдере),
582
+ // свод сделок и такт опроса, чтения и опрос узла Monero (своего узла у ядра нет);
583
+ // * цены, комиссии, газ и DEX-нога формы - ВИТРИНА: числа читаются у цепи модулями движка, а экран
584
+ // получает их швом, а не второй дорогой в движок;
585
+ // * networkFromShape/networkLabel - форма адреса Monero и её название: тот же модуль формата, что
586
+ // зеркалит ядро (второй таблицы префиксов нет);
587
+ // * createOrderWorker - воркер половин атомарного свопа: криптография живёт в одном месте и зовётся
588
+ // воркером, чтобы главный поток не замирал;
589
+ // * createSwap/createReverseSwap/activeSwaps - ЗАПИСЬ демонстрационной и обратной сделки (запись ведёт
590
+ // движок; ядро отдаёт её СОСТОЯНИЕ через swaps.list/swaps.progress).
591
+ // * createSwapWallet - КОШЕЛЁК СДЕЛКИ ФОРМЫ (состояние между экранами, ui/formState.js): кошелёк страницы
592
+ // со своими ключами. Ядру он не нужен, поэтому шов отдаёт его тем же импортом, что и прочее.
593
+ // ИМЯ БЕРЁТСЯ ИЗ ДВИЖКА ЗДЕСЬ ЖЕ (импортом) И ОТДАЁТСЯ НАРУЖУ (экспортом) - один шов, одна копия. Ни
594
+ // одного нового правила здесь нет: экран меняет источник импорта, а не строку вызова.
595
+ import { networkLabel, probeMode, createSwapWallet } from "../monero/wallet.js";
596
+ import { amountInputValue } from "../evm/amounts.js";
597
+ import { refreshChainPrices, priceLabel } from "../evm/prices.js";
598
+ import { orderGasReservePlan } from "../evm/gasReserve.js";
599
+ import { quoteDexOut, dexLegVerdict } from "../evm/dex.js";
600
+ import { toWei } from "../evm/funding.js";
601
+ import { cachedFeeTerms, feeWeiFor, ensureFeeTerms } from "../evm/fees.js";
602
+ import { createOrderWorker } from "../atomic/order-client.js";
603
+ import { activeSwaps, createSwap, createReverseSwap } from "../core/swap.js";
604
+
605
+ export { evm, networkFromShape };
606
+ export * as swapCore from "../core/swap.js";
607
+ export * as moneroNode from "../monero/node.js";
608
+ export { networkLabel, probeMode, createSwapWallet, amountInputValue, refreshChainPrices, priceLabel, orderGasReservePlan, quoteDexOut, dexLegVerdict, toWei, cachedFeeTerms, feeWeiFor, ensureFeeTerms, createOrderWorker, activeSwaps, createSwap, createReverseSwap };
609
+