@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,85 +1,92 @@
1
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.
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
- // СКЛЕЙНИК СТРАНИЦЫ С ЯДРОМ: ЕДИНСТВЕННОЕ МЕСТО, ГДЕ ЭКРАНЫ ЗНАЮТ ПРО SDK (sdk/).
5
+ // THE PAGE-TO-CORE SHIM: THE ONE PLACE WHERE SCREENS KNOW ABOUT THE SDK (sdk/).
7
6
  //
8
- // ЗАЧЕМ ОН. Логика обмена живёт в ядре (sdk/, пакет @ninsei-labs/sdk), а страница должна делать только
9
- // разметку, состояние экрана и тексты. Пока этого шва не было, каждый экран звал движок сам, и логика
10
- // расползалась по двум местам (.hermes/docs/59-ui-on-sdk.md). Теперь экран зовёт СКЛЕЙНИК, а про ядро знает
11
- // только он.
7
+ // WHY IT EXISTS. The swap logic lives in the core (sdk/, the @ninsei-labs/sdk package) while the page should do
8
+ // only markup, screen state and texts. Before this shim every screen called the engine itself and the logic
9
+ // spread across two places. Now a screen calls the SHIM, and only it knows about the core.
12
10
  //
13
- // ЧТО СКЛЕЙНИК ПОДСОВЫВАЕТ ЯДРУ - ровно те швы, которые ядро просит снаружи и само достать не может:
14
- // storage - хранилище страницы: сделки и сессия читаются оттуда же, где их кладёт страница (core/store.js);
15
- // serverAuth - пропуск на наш сервис: подпись сообщения о входе умеет только кошелёк (evm/auth.js signIn),
16
- // ядро получает уже ГОТОВЫЙ пропуск;
17
- // evmCall - чтение цепи КОШЕЛЬКОМ СТРАНИЦЫ: своего провайдера у ядра нет и быть не должно;
18
- // wallet - адаптер кошелька страницы (sdk/src/adapters/evm-wallet.mjs): подпись возможна только в нём.
19
11
  //
20
- // ЧЕГО СКЛЕЙНИК НЕ ДЕЛАЕТ. Он не считает ни порогов, ни порядка шагов, ни адреса эскроу: имена методов и
21
- // пути - только те, что ЕСТЬ в ядре, а решения остаются в ядре.
12
+ // WHAT THE SHIM PASSES TO THE CORE - exactly the seams the core asks for from outside and cannot get itself:
13
+ // storage - the page's storage: deals and session are read from where the page puts them (core/store.js);
14
+ // serverAuth - a pass to our service: only the wallet can sign the sign-in message (evm/auth.js signIn),
15
+ // so the core gets the pass READY-MADE;
16
+ // evmCall - chain reads WITH THE PAGE'S WALLET: the core has no provider of its own and must not;
17
+ // wallet - the page wallet adapter (sdk/src/adapters/evm-wallet.mjs): signing happens only in it.
22
18
  //
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
- // слоя сбрасывается сам после пересборки.
19
+ // WHAT THE SHIM DOES NOT DO. It computes neither thresholds nor step order nor the escrow address: method names
20
+ // and paths are only those that EXIST in the core, and the decisions stay in the core.
21
+ //
22
+ // ABOUT THE CORE BEING AVAILABLE TO THE PAGE - IT IS NOW BUILT, NOT WAITING FOR A BUILD.
23
+ // * sdk/src/primitives.mjs imports @noble/curves and @noble/hashes by BARE package names, so the page is
24
+ // served not the sdk/ source but a BUILT BUNDLE (www/assets/vendors/sdk/sdk-browser.js, the same path as the
25
+ // atomic-swap crypto bundle). The bundle exposes the WHOLE FACADE:
26
+ // createNinsei (config/wallet/quotes/preflight/swaps/actions/order/sweep/recovery/sim) plus the browser
27
+ // adapters (evmWallet, localStorageAdapter/memoryAdapter). So starting a swap (swaps.start) and facade
28
+ // quotes ARE available to the page - a "facade not served to the page" refusal is no longer expected.
29
+ // * The bundle link carries ?v=<hash>, so the edge cache resets itself after a rebuild.
30
+ //
31
+ //
32
+ // THE ORDER ACTIONS HAVE NO FALLBACK PATH ANY MORE, AND THAT IS A CONSEQUENCE OF THE MOVE. The deal engine used
33
+ // to sit on the page and, with the bundle unavailable, the order actions ran on it - "the same functions", as it
34
+ // was written here. Now the deal engine is a module OF THE PACKAGE (sdk/src/swap-flow.mjs): there is no second
35
+ // copy on the page, so no fallback can exist. The bundle did not load (missing file, old cache, network) - that
36
+ // is a NAMED refusal with the cause in the console (sdkBundle below), not silence and not a substitution.
32
37
  //
33
- // ЗАПАСНОГО ПУТИ У ДЕЙСТВИЙ ОРДЕРА БОЛЬШЕ НЕТ, И ЭТО НЕ ПОТЕРЯ, А СЛЕДСТВИЕ ПЕРЕЕЗДА (#32, волна 3). Раньше
34
- // www/js/core/swap-flow.js лежал на странице, и при недоступном бандле действия ордера шли им же - «одни и те
35
- // же функции», как здесь и было написано. Теперь движок сделки - СОБСТВЕННЫЙ модуль пакета
36
- // (sdk/src/swap-flow.mjs): второй копии на странице нет, значит и запасного пути быть не может. Бандл не
37
- // загрузился (нет файла, старый кеш, сеть) - это НАЗВАННЫЙ отказ с причиной в консоли (sdkBundle ниже), а не
38
- // молчание и не подмена.
39
38
 
40
39
  import { chainById, DEFAULT_CHAIN, MONERO, allowedProvidersFor, knownFactoryCodesFor, knownImplementationCodesFor } from "../core/config.js";
41
40
  import { storage as pageStorage } from "../core/store.js";
42
41
  import { token as authToken, signIn } from "../evm/auth.js";
43
42
  import * as evm from "../evm/index.js";
44
- // УСЛОВИЯ ОРДЕРА ДЛЯ ЯДРА СОБИРАЮТСЯ ИЗ ДАННЫХ, А НЕ ИЗ ПРАВИЛ: окна берутся у политики финальности
45
- // (core/finality.js), соль - генератором движка (evm/funding.js). Своих сроков и своих чисел здесь нет.
43
+ // ORDER TERMS FOR THE CORE ARE ASSEMBLED FROM DATA, NOT FROM RULES: the windows come from the finality policy
44
+ // (core/finality.js), the salt from the engine's generator (evm/funding.js). No deadlines or numbers of its own here.
46
45
  import { orderWindows } from "../core/finality.js";
47
46
  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
- // (пропуск и хранилище), и оба уже подсунуты выше - своих правил слияния склейник не заводит.
47
+ // THE ORDER CALLS - ACTION AND READ - COME FROM THE CORE. The deal engine moved into the package, and the shim
48
+ // exposes EXACTLY the same names (markReadyOrder/claimOrder/refundOrder/orderStatus/orderDeadlines), so a screen
49
+ // changes only the import source, not the call expression - and the guard that fixes the call site sees the old call.
55
50
  //
56
- // СЛЕЖЕНИЕ - МЕХАНИЗМ СТРАНИЦЫ, А НЕ ЯДРА: такт опроса живёт в app.js, поэтому фокус остаётся в
57
- // core/swap.js, а экран только объявляет его склейнику.
51
+ // THE DEAL LIST AND ITS UPKEEP (the #/swaps screen). States come from the CORE: swaps.list - display, swaps.sync -
52
+ // merging with the server list, swaps.forget - dropping a record. The core needs only the page's seams for this
53
+ // (the pass and the storage), and both are already passed above - the shim adds no rules of its own.
54
+ //
55
+ // WATCHING IS A PAGE MECHANISM, NOT THE CORE'S: the poll tick lives in app.js, so the focus stays in
56
+ // core/swap.js and the screen only declares it to the shim.
58
57
  import { setLiveFocus, dealSwapIds, serverRestoreHeightFor, STEP_LABELS,
59
58
  confirmReady as engineConfirmReady, refundEth as engineRefundEth, sweepNow as engineSweepNow,
60
59
  getSwap as engineGetSwap, saveSwap as engineSaveSwap } from "../core/swap.js";
61
- // СТАВКА КОМИССИИ И СОСТОЯНИЕ ОРДЕРА - ЧТЕНИЕ ЦЕПИ КОШЕЛЬКОМ СТРАНИЦЫ: их берут справочник ("как это
62
- // работает") и разбор recovery-файла. Страница получает их ОТСЮДА, а не из evm/* напрямую.
60
+ // THE FEE RATE AND THE ORDER STATE - CHAIN READS WITH THE PAGE'S WALLET: the reference panel ("how it works")
61
+ // and recovery-file parsing take them. The page gets them FROM HERE, not directly from evm/*.
63
62
  import { cachedFeeRate } from "../evm/fees.js";
64
- // АДРЕС И ВЫСОТА MONERO - ОСОЗНАННЫЕ ЧТЕНИЯ СТРАНИЦЫ (экран хода): проверка формы адреса получателя и высота
65
- // узла для высоты скана в файле восстановления. Логика адреса живёт в ядре (swaps.addressNetwork), здесь -
66
- // запасной путь тем же модулем; высота узла - чтение ноды страницы, своего узла у ядра нет.
63
+ // MONERO ADDRESS AND HEIGHT - DELIBERATE PAGE READS (the progress screen): checking the recipient address
64
+ // format and the node height for the scan height in the recovery file. The address logic lives in the core
65
+ // (swaps.addressNetwork), here a fallback by the same module; the node height is the page node's read.
67
66
  import { networkFromShape } from "../monero/address.js";
68
67
  import { height as nodeHeightValue } from "../monero/node.js";
69
68
 
70
- // АДРЕС ЯДРА СТРОКОЙ - СОБРАННЫЙ БАНДЛ ПОД www/ (tools/build-sdk.mjs): страница получает ядро ОДНИМ
71
- // модулем, а не пачкой исходников sdk/. ?v=<хеш> проставляет tools/stamp-assets.mjs.
72
- const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=31e62de5";
69
+ // THE CORE ADDRESS AS A STRING - THE BUILT BUNDLE UNDER www/: the page gets the core as ONE module, not a pile
70
+ // of sdk/ sources; ?v=<hash> keeps the cache fresh.
71
+ const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=3ce64c53";
72
+
73
+ // A NAMED SEAM FOR CHECKS - THE SAME TECHNIQUE AS sdkBookUse BELOW. The asynchronous path is driven against a
74
+ // stand-in order book (the demo network has no provider), and the ordinary escrow funding that follows a settled
75
+ // swap can be replaced while that runs, because deploying an escrow factory on a throwaway chain is the stand's
76
+ // job, not a check's. The replacement is named, set only by a check, and unset by the same function; production
77
+ // never calls it, so the real funding path is what ships.
78
+ let startSwapOverride = null;
79
+ export function useStartSwap(fn = null) { startSwapOverride = typeof fn === "function" ? fn : null; return Boolean(startSwapOverride); }
73
80
 
74
- // ОДНА ЗАГРУЗКА НА СТРАНИЦУ. Отказ - это состояние, а не поломка: он назван в консоли, и вызывающий решает.
81
+ // ONE LOAD PER PAGE. A refusal is a state, not a breakdown: it is named in the console and the caller decides.
75
82
  let loading = null;
76
83
  export async function sdkBundle() {
77
84
  if (!loading) {
78
85
  loading = (async () => {
79
86
  try { return await import(SDK_BUNDLE); }
80
87
  catch (error) {
81
- console.warn("[bridge] собранное ядро SDK странице не отдалось (" + ((error && error.message) || error) +
82
- "): действия идут прежним вызовом движка; см. www/js/sdk/bridge.js");
88
+ console.warn("[bridge] the built SDK core was not served to the page (" + ((error && error.message) || error) +
89
+ "): actions go by the previous engine call; see www/js/sdk/bridge.js");
83
90
  return null;
84
91
  }
85
92
  })();
@@ -87,8 +94,8 @@ export async function sdkBundle() {
87
94
  return loading;
88
95
  }
89
96
 
90
- // ПОВЕРХНОСТИ ИЗ БАНДЛА - ИМЕННО ТЕМИ ИМЕНАМИ, ЧТО ИХ ЗОВУТ НИЖЕ. Так экрану не нужно знать, что ядро
91
- // пришло одним файлом, а не тремя: форма вызова не меняется.
97
+ // THE SURFACES FROM THE BUNDLE - EXACTLY THE NAMES USED BELOW. So a screen need not know the core came as one
98
+ // file rather than three: the call form does not change.
92
99
  export async function sdkCoreModules() {
93
100
  const mod = await sdkBundle();
94
101
  if (!mod) return null;
@@ -99,7 +106,7 @@ export async function sdkCoreModules() {
99
106
  };
100
107
  }
101
108
 
102
- // ФАСАД ЯДРА (config/quotes/swaps/actions/recovery целиком) - ТОТ ЖЕ бандл, только нужен его createNinsei.
109
+ // THE CORE FACADE (config/quotes/swaps/actions/recovery whole) - THE SAME bundle, only its createNinsei is needed.
103
110
  async function coreFacade() {
104
111
  const mod = await sdkBundle();
105
112
  return mod ? { factory: mod.createNinsei } : null;
@@ -109,8 +116,8 @@ function modeOf(chain) {
109
116
  return chain && chain.escrow && chain.escrow.mode === "live" ? "live" : "sim";
110
117
  }
111
118
 
112
- // НАСТРОЙКИ ЯДРА ИЗ СТРАНИЦЫ. Ни одно значение здесь не решает ничего ЗА ядро: это адрес сервиса, сеть,
113
- // хранилище и швы, о которых ядро спрашивает снаружи.
119
+ // CORE SETTINGS FROM THE PAGE. No value here decides anything FOR the core: it is the service address, the
120
+ // network, the storage and the seams the core asks for from outside.
114
121
  function optionsFor(chain) {
115
122
  return {
116
123
  settlement: { chain: chain.id },
@@ -118,38 +125,38 @@ function optionsFor(chain) {
118
125
  apiBase: "/api",
119
126
  assetsBase: "/",
120
127
  mode: modeOf(chain),
121
- // ХРАНИЛИЩЕ СТРАНИЦЫ СТАНОВИТСЯ ХРАНИЛИЩЕМ ЯДРА: записи сделок и сессия лежат в одном месте.
128
+ // THE PAGE STORAGE BECOMES THE CORE STORAGE: deal records and the session live in one place.
122
129
  storage: pageStorage(),
123
- // ПРОПУСК НА НАШ СЕРВИС: ядро только предъявляет его, добывает - кошелёк страницы.
130
+ // A PASS TO OUR SERVICE: the core only presents it; the page wallet obtains it.
124
131
  serverAuth: async () => {
125
132
  if (authToken()) return authToken();
126
133
  try { await signIn(); } catch { return null; }
127
134
  return authToken();
128
135
  },
129
- // ЧТЕНИЕ ЦЕПИ - КОШЕЛЬКОМ СТРАНИЦЫ (ядру нужен этот шов: своего провайдера у него нет).
136
+ // CHAIN READS - WITH THE PAGE'S WALLET (the core needs this seam: it has no provider of its own).
130
137
  evmCall: (request) => evm.readContract(request),
131
- // КОД КОНТРАКТА - КОШЕЛЬКОМ СТРАНИЦЫ: нужен проверке кода фабрики (#103), extcodehash считается по нему.
138
+ // CONTRACT CODE - WITH THE PAGE'S WALLET: needed by the factory-code check, extcodehash is computed from it.
132
139
  evmCode: (address) => evm.readCode({ address }),
133
- // НАШ СПИСОК ДОПУЩЕННЫХ - ПОЛИТИКА ИНТЕРФЕЙСА (#76/#77). Ядро само подтверждает у ЦЕПИ, чей ключ
134
- // подписал котировку (реестр мейкеров); этот список решает, с кем торгует СТРАНИЦА. Провайдер вне
135
- // списка не проходит кодом `quote-provider-not-allowed` - и отказ виден ДО подписи, потому что без
136
- // твёрдой котировки нет ни ставки комиссии, ни замка. Список берётся из реестра сетей
137
- // (core/config.js, allowedProvidersFor), а не выдумывается здесь; сеть без списка = политики нет.
140
+ // OUR ALLOW-LIST - INTERFACE POLICY. The core confirms with the CHAIN whose key signed the quote (the maker
141
+ // registry); this list decides whom the PAGE trades with. A provider outside the list fails with the code
142
+ // `quote-provider-not-allowed` - and the refusal is visible BEFORE signing, because without a firm quote
143
+ // there is neither a fee rate nor a lock. The list comes from the network registry (core/config.js,
144
+ // allowedProvidersFor), not invented here; a network without a list = no policy.
138
145
  allowedProviders: allowedProvidersFor(chain),
139
- // СПИСОК ИЗВЕСТНЫХ СБОРОК ФАБРИКИ - ПОЛИТИКА ИНТЕРФЕЙСА (#103), рядом с allowedProviders. Фабрику ядро
140
- // берёт из записи реестра провайдера, а её КОД сверяет с этим списком (extcodehash). Пусто = политики
141
- // нет; тогда фабрика всё равно обязана совпасть с подписанной котировкой.
146
+ // THE LIST OF KNOWN FACTORY BUILDS - INTERFACE POLICY, next to allowedProviders. The core takes the factory
147
+ // from the provider registry record and compares its CODE against this list (extcodehash). Empty = no policy;
148
+ // then the factory must still match the signed quote.
142
149
  knownFactoryCodes: knownFactoryCodesFor(chain),
143
- // ХЕШ ИЗВЕСТНОЙ СБОРКИ РЕАЛИЗАЦИИ ЭСКРОУ (#114) - РЯДОМ С knownFactoryCodes. Фабрика сверяет пару
144
- // (implementation, implementationCodeHash) при создании ордера; ядро сверяет ТУ ЖЕ пару на цепи, и этот
145
- // список - политика интерфейса: какие сборки реализации он принимает. Пусто = политики нет (сеть со
146
- // старой фабрикой), и тогда пара всё равно читается, а код по адресу реализации обязан быть непустым.
150
+ // THE HASH OF A KNOWN ESCROW-IMPLEMENTATION BUILD - NEXT TO knownFactoryCodes. The factory compares the pair
151
+ // (implementation, implementationCodeHash) at order creation; the core compares THE SAME pair on chain, and
152
+ // this list is interface policy: which implementation builds it accepts. Empty = no policy (a network with an
153
+ // old factory), and then the pair is still read and the code at the implementation address must be non-empty.
147
154
  knownImplementationCodes: knownImplementationCodesFor(chain),
148
155
  };
149
156
  }
150
157
 
151
- // ЭКЗЕМПЛЯР ЯДРА НА СЕТЬ: фасад держит хранилище и подписки, второй экземпляр на ту же сеть завёл бы второй
152
- // опрос. Недоступен - null, и вызывающий сам называет отказ.
158
+ // ONE CORE INSTANCE PER NETWORK: the facade holds storage and subscriptions, and a second instance for the same
159
+ // network would mean a second poll. Unavailable - null, and the caller names the refusal itself.
153
160
  const instances = new Map();
154
161
  async function instanceFor(chainSlug) {
155
162
  const facade = await coreFacade();
@@ -159,24 +166,24 @@ async function instanceFor(chainSlug) {
159
166
  return { chain, sdk: instances.get(chain.id) };
160
167
  }
161
168
 
162
- // КОШЕЛОК СТРАНИЦЫ ДЛЯ ЯДРА: адаптер поверх ЕГО провайдера. Берётся В МОМЕНТ действия: провайдер
163
- // появляется вместе с подключением, а не при загрузке страницы.
169
+ // THE PAGE WALLET FOR THE CORE: an adapter over ITS provider. Taken AT THE MOMENT of an action: the provider
170
+ // appears with the connection, not at page load.
164
171
  function walletFor(mods) {
165
172
  const provider = typeof evm.currentProvider === "function" ? evm.currentProvider() : null;
166
173
  if (!provider) return null;
167
174
  return mods.wallet.evmWallet(provider);
168
175
  }
169
176
 
170
- // --- ДЕЙСТВИЯ ОРДЕРА -----------------------------------------------------------------------------
171
- // ИМЕНА И ФОРМА ЗАПРОСА - ТЕ ЖЕ, ЧТО У ПРЕЖНИХ ВЫЗОВОВ ЭКРАНА. Экран передаёт ожидания ордера (expect) и
172
- // ничего не пересчитывает: ядро сверяет слоты с цепью ПЕРЕД подписью и без ожиданий не подписывает.
173
- // Кошелёк - тот же, что подписывает страница; сеть ядру здесь не нужна (чтение и отправка идут тем же
174
- // провайдером, что и раньше).
177
+ // --- ORDER ACTIONS -------------------------------------------------------------------------------
178
+ // THE NAMES AND THE REQUEST SHAPE ARE THE SAME AS THE SCREEN'S FORMER CALLS. The screen passes the order
179
+ // expectations (expect) and recomputes nothing: the core checks slots against the chain BEFORE signing and does
180
+ // not sign without expectations. The wallet is the one that signs the page; the core needs no network here
181
+ // (reading and sending go through the same provider as before).
175
182
  export async function markReadyOrder({ escrow, expect } = {}) {
176
183
  const mods = await sdkCoreModules();
177
184
  const wallet = mods ? walletFor(mods) : null;
178
- // ЯДРО И КОШЕЛЁК - ОБЯЗАТЕЛЬНЫ: запасного пути (движок на странице) больше нет. Отказ назван, а не молчание.
179
- if (!mods || !wallet) throw new Error("[bridge] отметку готовности подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
185
+ // THE CORE AND THE WALLET ARE MANDATORY: there is no fallback path (an engine on the page) any more. The refusal is named, not silence.
186
+ if (!mods || !wallet) throw new Error("[bridge] nothing to sign the readiness mark with: " + (mods ? "wallet not connected" : "the SDK core was not served to the page"));
180
187
  return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
181
188
  .markReady({ escrow, expect }, wallet);
182
189
  }
@@ -184,7 +191,7 @@ export async function markReadyOrder({ escrow, expect } = {}) {
184
191
  export async function claimOrder({ escrow, halfClaimer, expect } = {}) {
185
192
  const mods = await sdkCoreModules();
186
193
  const wallet = mods ? walletFor(mods) : null;
187
- if (!mods || !wallet) throw new Error("[bridge] забор подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
194
+ if (!mods || !wallet) throw new Error("[bridge] nothing to sign the claim with: " + (mods ? "wallet not connected" : "the SDK core was not served to the page"));
188
195
  return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
189
196
  .claim({ escrow, halfClaimer, expect }, wallet);
190
197
  }
@@ -192,18 +199,18 @@ export async function claimOrder({ escrow, halfClaimer, expect } = {}) {
192
199
  export async function refundOrder({ escrow, halfLocker } = {}) {
193
200
  const mods = await sdkCoreModules();
194
201
  const wallet = mods ? walletFor(mods) : null;
195
- if (!mods || !wallet) throw new Error("[bridge] возврат подписать нечем: " + (mods ? "кошелёк не подключён" : "ядро SDK странице не отдалось"));
196
- // ОЖИДАНИЙ ЗДЕСЬ НЕТ НАМЕРЕННО: экран возврата их не передавал, и дописать их значило бы включить сверку
197
- // слотов там, где её не было, - то есть изменить поведение возврата.
202
+ if (!mods || !wallet) throw new Error("[bridge] nothing to sign the refund with: " + (mods ? "wallet not connected" : "the SDK core was not served to the page"));
203
+ // THERE ARE NO EXPECTATIONS HERE ON PURPOSE: the refund screen did not pass any, and adding them would enable
204
+ // slot comparison where it was not - i.e. change the refund behaviour.
198
205
  return await mods.actions.createActions({ call: (r) => evm.readContract(r) })
199
206
  .refund({ escrow, halfLocker }, wallet);
200
207
  }
201
208
 
202
- // --- АВТОМАТИЧЕСКИЙ РАННИЙ ВОЗВРАТ (issue #88) ------------------------------------------------------
203
- // РЕШЕНИЕ ОСТАЁТСЯ В ЯДРЕ. Экран лишь передаёт запись сделки, ожидания ордера и сеть; саму проверку прихода
204
- // (две ноды разных операторов на одном блоке) и решение "пора вернуть" делает ядро - swaps.refundIfXmrMissing.
205
- // Ноды-свидетели берутся из настроек ТОГО ЖЕ ядра (config.nodesFor), а не выдумываются здесь: список живёт
206
- // одной правдой на сеть. Кошелёк - тот же адаптер страницы, что подписывает прочие действия ордера.
209
+ // --- AUTOMATIC EARLY REFUND ---------------------------------------------------------------------
210
+ // THE DECISION STAYS IN THE CORE. The screen only passes the deal record, the order expectations and the network;
211
+ // the arrival check (two nodes of different operators on one block) and the "time to refund" decision are the
212
+ // core's (swaps.refundIfXmrMissing). The witness nodes come from the settings of THE SAME core (config.nodesFor),
213
+ // not invented here: the list lives as one truth per network. The wallet is the same page adapter that signs the other order actions.
207
214
  export async function sdkAutoRefund(request) {
208
215
  const ctx = await instanceFor(request && request.chain);
209
216
  if (!ctx) return null;
@@ -219,17 +226,17 @@ export async function sdkAutoRefund(request) {
219
226
  return ctx.sdk.swaps.refundIfXmrMissing(req);
220
227
  }
221
228
 
222
- // --- РАЗБОР RECOVERY-ФАЙЛА -----------------------------------------------------------------------
223
- // Модуль разбора браузеру отдаётся, поэтому это настоящий путь ядра, а не запасной.
229
+ // --- RECOVERY-FILE PARSING -----------------------------------------------------------------------
230
+ // The parsing module is served to the browser, so this is a real core path, not a fallback.
224
231
  export async function sdkOpenRecovery(file, passphrase) {
225
232
  const mods = await sdkCoreModules();
226
233
  if (!mods) return null;
227
234
  return await mods.recovery.createRecovery().open(file, passphrase);
228
235
  }
229
236
 
230
- // --- ПОВЕРХНОСТИ ФАСАДА (котировки, состояние сделок, запуск сделки) -------------------------------
231
- // Ядро собрано странице бандлом, поэтому эти вызовы идут через НАСТОЯЩИЙ фасад. null они отвечают только
232
- // если сам бандл не загрузился - и тогда причина названа в консоли, а не спрятана.
237
+ // --- FACADE SURFACES (quotes, deal state, swap start) ---------------------------------------------
238
+ // The core is served to the page as a bundle, so these calls go through the REAL facade. They answer null only
239
+ // if the bundle itself did not load - and then the cause is named in the console, not hidden.
233
240
 
234
241
  export async function sdkWatchQuotes(request, onSnapshot) {
235
242
  const ctx = await instanceFor(request && request.chain);
@@ -242,37 +249,37 @@ export async function sdkQuotesSnapshot(chainSlug) {
242
249
  return ctx ? ctx.sdk.quotes.snapshot() : null;
243
250
  }
244
251
 
245
- // --- ИНДИКАТИВНАЯ КНИГА КОТИРОВОК НА ЯДРЕ ---------------------------------------------------------
246
- // КНИГА (предложения и отказы) ПРИХОДИТ ОТ ЯДРА: quotes.watch подписывается на опрос ядра, quotes.snapshot
247
- // отдаёт последний снимок без ожидания. Склейник здесь делает ровно ОДНУ работу - ПЕРЕВОД ФОРМЫ: имена
248
- // полей снимка ядра приводятся к тем, которые уже рисует экран. Ни одного правила отбора или приоритета
249
- // здесь нет: что предложение, что отказ, что лучший - решает ядро (sdk/src/quotes.mjs, readBook). Ядро
250
- // молчит или отказало - книга пуста, и прежняя (движковая) книга не подставляется: её больше нет.
252
+ // --- THE INDICATIVE QUOTE BOOK ON THE CORE ---------------------------------------------------------
253
+ // THE BOOK (offers and refusals) COMES FROM THE CORE: quotes.watch subscribes to the core poll, quotes.snapshot
254
+ // returns the last snapshot without waiting. The shim does exactly ONE job here - TRANSLATION OF SHAPE: the core
255
+ // snapshot's field names are mapped to those the screen already draws. There is no selection or priority rule
256
+ // here: offer, refusal or best is decided by the core (sdk/src/quotes.mjs, readBook). The core is silent or
257
+ // refused - the book is empty, and the former (engine) book is not substituted: it no longer exists.
251
258
  //
252
- // ПОДПИСКА ОДНА. Ядро опрашивает на время подписки; второй слушатель был бы вторым опросом, поэтому
253
- // прежняя подписка снимается ПЕРЕД новой. Последний снимок переносится через переподписку: читающий видит
254
- // прежнюю книгу, пока не придёт свежая, - а не пустоту на каждом такте.
255
- let bookStop = null; // отписка от текущей подписки ядра
256
- let bookKey = null; // под какой запрос (сеть|сторона|токен|размер) эта подписка поставлена
257
- let bookCore = null; // последний снимок ЯДРА (не перевода): перевод делается на чтение
258
- let bookResume = null; // ожидающий первого такта после подписки
259
- // ШОВ-ПОДСТАНОВКА (sdkBookUse): поверхность котировок ядра, принесённая снаружи. Нужна проверкам, которые
260
- // идут БЕЗ браузера: самого ядра в бандле там нет, а книгу проверить надо. В бою не задаётся.
259
+ // SUBSCRIPTION IS ONE. The core polls for the duration of the subscription; a second listener would be a second
260
+ // poll, so the previous subscription is removed BEFORE the new one. The last snapshot survives re-subscription:
261
+ // the reader sees the previous book until a fresh one arrives - not emptiness on every tick.
262
+ let bookStop = null; // unsubscribe from the core's current subscription
263
+ let bookKey = null; // which request (network|side|token|size) this subscription is for
264
+ let bookCore = null; // the last CORE snapshot (not the translation): the translation is done on read
265
+ let bookResume = null; // waiting for the first tick after subscribing
266
+ // A SEAM HOOK (sdkBookUse): a core quote surface brought from outside. Needed by checks that run WITHOUT a
267
+ // browser: the core itself is not in the bundle there, and the book must be checked. Not set in production.
261
268
  let bookQuotes = null;
262
269
 
263
- // ЯДРО ДЛЯ ПРОВЕРОК БЕЗ БРАУЗЕРА: кладём поверхность котировок (watch/snapshot) и работаем с ней.
270
+ // THE CORE FOR CHECKS WITHOUT A BROWSER: put in a quote surface (watch/snapshot) and work with it.
264
271
  export function sdkBookUse(quotesApi) {
265
272
  bookQuotes = quotesApi || null;
266
273
  bookCore = null;
267
274
  bookKey = null;
268
275
  bookResume = null;
269
- if (bookStop) { try { bookStop(); } catch { /* уже снята */ } }
276
+ if (bookStop) { try { bookStop(); } catch { /* already removed */ } }
270
277
  bookStop = null;
271
278
  }
272
279
 
273
- // ПЕРЕВОД СТРОКИ ЯДРА В СТРОКУ ЭКРАНА. Только имена: значения переносятся как есть. Возраст считается
274
- // ЗДЕСЬ (от собственной метки котировки): ядро посчитало его на своём такте, а показ спрашивает свежесть
275
- // сейчас - иначе срок жизни котировки «омолаживался» бы на такт опроса.
280
+ // TRANSLATING A CORE ROW INTO A SCREEN ROW. Names only: values are carried as is. The age is computed
281
+ // HERE (from the quote's own timestamp): the core computed it on its own tick, while display asks for freshness
282
+ // now - otherwise the quote's lifetime would be rejuvenated by a poll tick.
276
283
  function bookRow(o) {
277
284
  const at = Number.isFinite(Number(o.at)) ? Number(o.at) : null;
278
285
  return {
@@ -287,16 +294,16 @@ function bookRow(o) {
287
294
  };
288
295
  }
289
296
 
290
- // ВИД ОТКАЗА, КОТОРЫМ ЭКРАН НАЗЫВАЕТ ПРИЧИНУ. Код ядра `disabled` экран зовёт `off`; имена прочих кодов
291
- // совпадают, и переименовывать их незачем.
297
+ // THE REFUSAL KIND WITH WHICH THE SCREEN NAMES THE CAUSE. The core code `disabled` is called `off` by the
298
+ // screen; other code names match, and there is no reason to rename them.
292
299
  const REFUSAL_KIND = { disabled: "off" };
293
300
  function bookRefusal(r) {
294
301
  return { ...bookRow(r), code: r.code, kind: REFUSAL_KIND[r.code] || r.code, why: r.why };
295
302
  }
296
303
 
297
- // СНИМОК ЯДРА -> СНИМОК ЭКРАНА. Форма та же, что была у экрана: ok/error/data/at, а в data - references
298
- // (справочный курс) и best. Отказ связи не превращается в прежние числа: ok снимается, и экран показывает
299
- // честное состояние. Молчание ядра (снимка нет) - тоже пустая книга, а не прежняя.
304
+ // CORE SNAPSHOT -> SCREEN SNAPSHOT. The shape is the one the screen had: ok/error/data/at, and in data -
305
+ // references (the reference rate) and best. A connection failure does not turn into the former numbers: ok is
306
+ // dropped and the screen shows an honest state. Core silence (no snapshot) is also an empty book, not the former one.
300
307
  function bookOf(core) {
301
308
  if (!core) return { ok: false, error: null, data: null, at: 0, fetching: false };
302
309
  const offers = (Array.isArray(core.offers) ? core.offers : []).map(bookRow);
@@ -309,7 +316,7 @@ function bookOf(core) {
309
316
  fetching: false,
310
317
  data: {
311
318
  offers, refused, quotes: offers,
312
- // ЛУЧШЕЕ, КАК ЕГО НАЗВАЛ СЕРВИС: по нему экран подтверждает свою метку «лучшее» по паре и провайдеру.
319
+ // THE BEST, AS THE SERVICE NAMED IT: by it the screen confirms its "best" mark by pair and provider.
313
320
  best: core.best || null,
314
321
  seq: core.seq === undefined ? null : core.seq,
315
322
  references: Array.isArray(core.references) ? core.references : [],
@@ -318,31 +325,31 @@ function bookOf(core) {
318
325
  };
319
326
  }
320
327
 
321
- // ЗАПРОС КНИГИ У ЯДРА. Подписка ОДНА и переигрывается только когда СМЕНИЛСЯ ЗАПРОС: пока размер и сторона
322
- // те же, ядро опрашивает книгу своим тактом, и второго опроса не заводится. Когда запрос сменился,
323
- // вызывающий ждёт первый такт и получает СВЕЖИЙ снимок, а не прежний. Ядро не поднялось - пусто (и это
324
- // называет причина в консоли).
328
+ // ASKING THE CORE FOR THE BOOK. The subscription is ONE and is replayed only when the REQUEST CHANGED: while
329
+ // size and side are the same, the core polls the book on its own tick and no second poll is created. When the
330
+ // request changed, the caller waits for the first tick and gets a FRESH snapshot, not a previous one. The core
331
+ // did not come up - empty (and the console names why).
325
332
  export async function sdkBook({ chain, direction, token, size } = {}) {
326
333
  let quotes = bookQuotes;
327
- let instance = "injected"; // у подстановки (шва) сети нет: её экземпляр один
334
+ let instance = "injected"; // the hook (seam) has no network: its instance is one
328
335
  if (!quotes) {
329
336
  const ctx = await instanceFor(chain);
330
337
  if (!ctx) {
331
- if (bookStop) { try { bookStop(); } catch { /* уже снята */ } bookStop = null; }
338
+ if (bookStop) { try { bookStop(); } catch { /* already removed */ } bookStop = null; }
332
339
  bookKey = null;
333
340
  bookResume = null;
334
341
  bookCore = null;
335
342
  return null;
336
343
  }
337
344
  quotes = ctx.sdk.quotes;
338
- instance = ctx.chain.id; // книги разных сетей смешивать нельзя: сеть - часть ключа
345
+ instance = ctx.chain.id; // books of different networks must not be mixed: the network is part of the key
339
346
  }
340
347
  const dir = direction === "sell" ? "sell" : "buy";
341
348
  const key = instance + "|" + dir + "|" + String(token || "") + "|" + String(size === undefined || size === null ? "" : size);
342
- // ТОТ ЖЕ ЗАПРОС - ОПРОС УЖЕ ИДЁТ: возвращаем последний снимок, второй подписки не заводим. Иначе каждый
343
- // такт экрана сбрасывал бы состояние ядра и на мгновение отдавал бы пустую книгу читающему её напрямую.
349
+ // THE SAME REQUEST - A POLL IS ALREADY RUNNING: return the last snapshot, do not create a second subscription.
350
+ // Otherwise every screen tick would reset the core state and briefly serve an empty book to a direct reader.
344
351
  if (bookStop && bookKey === key) return bookCore;
345
- if (bookStop) { try { bookStop(); } catch { /* уже снята */ } bookStop = null; }
352
+ if (bookStop) { try { bookStop(); } catch { /* already removed */ } bookStop = null; }
346
353
  bookKey = key;
347
354
  bookResume = null;
348
355
  const wait = new Promise((resolve) => { bookResume = resolve; });
@@ -354,15 +361,15 @@ export async function sdkBook({ chain, direction, token, size } = {}) {
354
361
  } catch (error) {
355
362
  bookResume = null;
356
363
  bookCore = null;
357
- console.warn("[bridge] книга котировок ядру не отдалась (" + ((error && error.message) || error) +
358
- "): предложений не будет; см. www/js/sdk/bridge.js");
364
+ console.warn("[bridge] the quote book was not served to the core (" + ((error && error.message) || error) +
365
+ "): there will be no offers; see www/js/sdk/bridge.js");
359
366
  return null;
360
367
  }
361
368
  await wait;
362
369
  return bookCore;
363
370
  }
364
371
 
365
- // ПОСЛЕДНИЙ СНИМОК БЕЗ ОЖИДАНИЯ - в форме экрана. Ядро не опрошено ни разу - пусто (и это честно).
372
+ // THE LAST SNAPSHOT WITHOUT WAITING - in the screen's shape. The core has not been polled once - empty (and that is honest).
366
373
  export function sdkBookSnapshot() {
367
374
  return bookOf(bookCore);
368
375
  }
@@ -377,29 +384,29 @@ export async function sdkGetSwap(id, chainSlug) {
377
384
  return ctx ? ctx.sdk.swaps.get(id) : null;
378
385
  }
379
386
 
380
- // УСЛОВИЯ ОРДЕРА - ДАННЫЕ ДЛЯ ЯДРА, А НЕ ЕГО ПРАВИЛА.
381
- // Ядро требует эти восемь полей СНАРУЖИ (sdk/index.d.ts, StartRequest.order) и без них отказывает: к ним
382
- // привязаны и доказательство DLEQ, и подпись твёрдой котировки (atomic/orderContext.js, FIELDS). Здесь они не
383
- // выводятся заново, а берутся из того же, чем пользовалась страница: сеть и фабрика - из реестра сетей,
384
- // вносит - подключённый кошелёк, сумма - из котировки, окна - из политики финальности провайдера
385
- // (core/finality.js, orderWindows), соль - генератором движка (evm/funding.js, newSecret). Своих окон и
386
- // своих чисел здесь не появляется: правило о минимальных окнах живёт в core/finality.js и core/preSignGuards.js.
387
+ // ORDER TERMS - DATA FOR THE CORE, NOT ITS RULES.
388
+ // The core requires these eight fields FROM OUTSIDE (sdk/index.d.ts, StartRequest.order) and refuses without them:
389
+ // both the DLEQ proof and the firm-quote signature are bound to them (atomic/orderContext.js, FIELDS). Here they
390
+ // are not derived again but taken from the same sources the page used: network and factory from the network
391
+ // registry, the depositor - the connected wallet, the amount - from the quote, the windows - from the provider's
392
+ // finality policy (core/finality.js, orderWindows), the salt - from the engine's generator (evm/funding.js,
393
+ // newSecret). No windows or numbers of its own appear here: the minimum-window rule lives in core/finality.js and core/preSignGuards.js.
387
394
  export function sdkOrderTerms({ chain, amountWei, finality = null, salt = null, asset = "XMR" } = {}) {
388
395
  const windows = orderWindows(finality);
389
396
  const nowSec = Math.floor(Date.now() / 1000);
390
397
  const escrow = (chain && chain.escrow) || {};
391
- // КАССА КОМИССИИ - ЧАСТЬ УСЛОВИЙ ОРДЕРА (#103), как и фабрика: приходит из реестра сетей, а не из
392
- // контракта. Без неё ордер не собрать: условие обязательное и входит в termsHash.
393
- if (!escrow.cashier) throw new Error("в этой сети не задана касса комиссии (escrow.cashier) - ордер собрать нечем");
398
+ // THE FEE CASHIER IS PART OF THE ORDER TERMS, like the factory: it comes from the network registry, not from
399
+ // the contract. Without it the order cannot be assembled: the term is mandatory and enters termsHash.
400
+ if (!escrow.cashier) throw new Error("no fee cashier set on this network (escrow.cashier) - the order cannot be assembled");
394
401
  return {
395
402
  chainId: chain.chainId,
396
403
  factory: escrow.address,
397
404
  cashier: escrow.cashier,
398
405
  locker: evm.address(),
399
406
  amount: String(amountWei),
400
- // ЧТО ЧЕЛОВЕК ПОЛУЧАЕТ. В канонический контекст ордера поле не входит (это следствие пары и сети), но
401
- // провайдер его ждёт - по нему он понимает, что выплата в XMR, и кладёт в свою запись котировки.
402
- // Значение приходит данными от экрана; умолчание - XMR (экран покупки XMR за нативную монету).
407
+ // WHAT THE PERSON RECEIVES. The field is not part of the canonical order context (it is a consequence of the
408
+ // pair and network), but the provider expects it - by it he understands the payout is in XMR, and puts it in
409
+ // his quote record. The value comes as data from the screen; the default is XMR (the screen buying XMR for the native coin).
403
410
  asset: String(asset || "XMR"),
404
411
  readyBy: String(nowSec + windows.readyWindowSeconds),
405
412
  t1: String(nowSec + windows.claimWindowSeconds),
@@ -407,13 +414,13 @@ export function sdkOrderTerms({ chain, amountWei, finality = null, salt = null,
407
414
  };
408
415
  }
409
416
 
410
- // ТВЁРДАЯ КОТИРОВКА ПОД ОРДЕР - ДО ПОДПИСИ. Экран показывает ставку и получателя комиссии РАНЬШЕ, чем
411
- // человек подпишет (пункт 7 из #76), а ТУ ЖЕ САМУЮ котировку отдаёт в запуск сделки (sdkStartSwap,
412
- // поле orderQuote): подписывается ровно то, что показано, и второго запроса к ноде не делается.
417
+ // A FIRM QUOTE FOR THE ORDER - BEFORE SIGNING. The screen shows the fee rate and recipient BEFORE the person
418
+ // signs, and hands THE SAME quote to the swap start (sdkStartSwap, field orderQuote): exactly what is shown is
419
+ // signed, and no second request to the node is made.
413
420
  //
414
- // ПОЧЕМУ ЭТО МЕСТО, А НЕ ЭКРАН. Условия ордера (сеть, фабрика, вносивший, сумма, окна, соль) собираются
415
- // ОДНИМ местом - sdkOrderTerms; экран не должен собирать их второй редакцией. Здесь они собираются, по ним
416
- // запрашивается твёрдая котировка тем же путём ядра, и наружу уходят оба: `order` и `quote`.
421
+ // WHY THIS PLACE AND NOT THE SCREEN. The order terms (network, factory, depositor, amount, windows, salt) are
422
+ // assembled in ONE place - sdkOrderTerms; the screen must not assemble them in a second edition. Here they are
423
+ // assembled, a firm quote is requested by them through the core's path, and both go outward: `order` and `quote`.
417
424
  export async function sdkOrderQuote({ chain, providerId, amountWei, finality = null, salt = null, asset = "XMR" } = {}) {
418
425
  const ctx = await instanceFor(chain);
419
426
  if (!ctx) return null;
@@ -422,79 +429,82 @@ export async function sdkOrderQuote({ chain, providerId, amountWei, finality = n
422
429
  return { order, quote };
423
430
  }
424
431
 
425
- // ЗАПУСК СДЕЛКИ ЯДРОМ ЦЕЛИКОМ. Порядок шагов, сторона ордера, сроки в контракт, адрес эскроу ИЗ КВИТАНЦИИ,
426
- // отметка у нас и наблюдение за XMR - всё это держит ядро (sdk/src/swaps.mjs, swaps.start). Экран отдаёт
427
- // только данные: котировку, куда получить, пароль, кошелёк и обработчик подтверждения файла. Своей стороны
428
- // ордера, своих адресов и своих транзакций страница здесь не собирает.
432
+ // STARTING THE SWAP WHOLE, BY THE CORE. Step order, the order side, deadlines into the contract, the escrow
433
+ // address FROM THE RECEIPT, our readiness mark and watching the XMR - all of it is held by the core
434
+ // (sdk/src/swaps.mjs, swaps.start). The screen passes only data: the quote, where to receive, the password, the
435
+ // wallet and the file-confirmation handler. The page assembles no order side, no addresses and no transactions here.
429
436
  export async function sdkStartSwap(request) {
437
+ // THE CHECK SEAM (see useStartSwap above): a stand-in for the escrow funding while the asynchronous path is
438
+ // exercised. Absent in production - the real core path below is what runs.
439
+ if (startSwapOverride) return startSwapOverride({ ...(request || {}) });
430
440
  const ctx = await instanceFor(request && request.chain);
431
441
  if (!ctx) return null;
432
442
  const req = { ...(request || {}) };
433
- // КОШЕЛОК ДЛЯ ЗАМКА - АДАПТЕР КОШЕЛЬКА СТРАНИЦЫ, ЕСЛИ ВЫЗЫВАЮЩИЙ ЕГО НЕ ПРИНЁС. Подпись возможна только
434
- // в кошелёк-провайдере страницы, а своего провайдера у ядра нет: ядро получает готовый адаптер (send/receipt).
443
+ // THE WALLET FOR THE LOCK - THE PAGE WALLET ADAPTER, IF THE CALLER DID NOT BRING ONE. Signing is possible only
444
+ // in the page's wallet provider, and the core has no provider of its own: the core gets a ready adapter (send/receipt).
435
445
  if (!req.wallet) {
436
446
  const mods = await sdkCoreModules();
437
447
  const wallet = mods ? walletFor(mods) : null;
438
448
  if (wallet) req.wallet = wallet;
439
449
  }
440
- // КОШЕЛОК СТРАНИЦЫ ОТДАЁТСЯ ЯДРУ И КАК ЕГО КОШЕЛЁК: стражи сети (preflight) спрашивают сеть У КОШЕЛЬКА
441
- // ЯДРА, а не у того, что передан на замок, - иначе запуск отказывает кодом network { wallet-unknown }
442
- // (так делает и живой прогон из Node: sdk.wallet.use(...); await sdk.wallet.connect()). Подпись от этого
443
- // не меняется: и подключение, и отправка идут через тот же провайдер страницы.
450
+ // THE PAGE WALLET IS ALSO GIVEN TO THE CORE AS ITS WALLET: the network guards (preflight) ask the network OF
451
+ // THE CORE'S WALLET, not of the one passed for the lock - otherwise the start fails with the code network
452
+ // { wallet-unknown } (the live Node run does the same: sdk.wallet.use(...); await sdk.wallet.connect()). The
453
+ // signature does not change: both connecting and sending go through the same page provider.
444
454
  if (req.wallet) {
445
455
  try { ctx.sdk.wallet.use(req.wallet); await ctx.sdk.wallet.connect({ chainId: ctx.chain.chainId }); }
446
- catch { /* кошелёк не поднялся - отказ назовёт само ядро (network/wallet-unknown), тишины нет */ }
456
+ catch { /* the wallet did not come up - the core itself will name the refusal (network/wallet-unknown), no silence */ }
447
457
  }
448
- // Условий ордера нет - собираем их из данных (см. sdkOrderTerms). Контракт StartRequest допускает и
449
- // готовые: тогда вызывающий приносит свою сторону ордера со своими точками (инъекция записи).
458
+ // No order terms - assemble them from data (see sdkOrderTerms). The StartRequest contract also allows ready
459
+ // ones: then the caller brings its own order side with its own points (record injection).
450
460
  if (!req.order) {
451
461
  req.order = sdkOrderTerms({ chain: ctx.chain, amountWei: req.amountWei, finality: req.finality,
452
462
  salt: req.salt, asset: req.receiveAsset });
453
463
  }
454
- // ТВЁРДАЯ КОТИРОВКА ПОД ЭТОТ ОРДЕР - И ОНА ЖЕ УХОДИТ В ЗАМОК. Экран, показавший комиссию ДО подписи,
455
- // приносит котировку ГОТОВОЙ (sdkOrderQuote) - тогда второй раз к ноде не идём и подписывается ровно то,
456
- // что человек видел. Если не принёс, запрашиваем здесь тем же путём ядра (config.route("orderQuote")).
464
+ // A FIRM QUOTE FOR THIS ORDER - AND IT ALSO GOES INTO THE LOCK. A screen that showed the fee BEFORE signing
465
+ // brings the quote READY-MADE (sdkOrderQuote) - then we do not go to the node again and exactly what the person
466
+ // saw is signed. If it did not, we request it here through the core's path (config.route("orderQuote")).
457
467
  //
458
- // ЗАЧЕМ ЭТО ОТДЕЛЬНОЕ ПОЛЕ. Замок берёт котировку НЕ из req.quote: req.quote - индикативная котировка
459
- // книги для стражей (свежесть, сроки), а фабрике нужен ПОДПИСАННЫЙ НАБОР (комиссия, реестр, провайдер).
460
- // Ядро читает его как `request.orderQuote` (sdk/src/swaps.mjs). Пока мост его не клал, запуск доходил до
461
- // внесения и падал там: "нужна подписанная котировка: фабрика принимает котировку, а не голые условия ордера".
468
+ // WHY THIS SEPARATE FIELD. The lock takes the quote NOT from req.quote: req.quote is the book's indicative
469
+ // quote for the guards (freshness, deadlines), while the factory needs a SIGNED SET (fee, registry, provider).
470
+ // The core reads it as `request.orderQuote` (sdk/src/swaps.mjs). While the bridge did not put it there, the
471
+ // start reached the deposit and failed there: "a signed quote is required: the factory takes a quote, not bare order terms".
462
472
  if (!req.orderQuote && req.providerId) {
463
473
  req.orderQuote = await ctx.sdk.quotes.firm({ providerId: req.providerId, order: req.order });
464
474
  }
465
475
  if (req.orderQuote) {
466
- // ЗАБИРАЮЩЕГО НАЗЫВАЕТ ПРОВАЙДЕР, и он входит в контекст ордера: без него доказательство не сойдётся
467
- // (ядро так и откажет - context mismatch). Здесь только переносим его значение в условия.
476
+ // THE CLAIMER IS NAMED BY THE PROVIDER, and enters the order context: without it the proof would not agree
477
+ // (the core refuses - context mismatch). Here we only carry its value into the terms.
468
478
  if (req.orderQuote.claimer) req.order = { ...req.order, claimer: req.orderQuote.claimer };
469
- // ID КОТИРОВКИ ПОД ОРДЕР УХОДИТ В ЗАПИСЬ СДЕЛКИ: по нему наш сервис связывает эскроу с записью ноды
470
- // провайдера, и только после этого нода может забрать ETH. Экран это делал сам (swap.orderQuoteId) -
471
- // значит данные остаются теми же, и терять их при переводе нельзя.
479
+ // THE ORDER QUOTE ID GOES INTO THE DEAL RECORD: by it our service ties the escrow to the provider node's
480
+ // record, and only then can the node take the ETH. The screen did it itself (swap.orderQuoteId) - so the data
481
+ // stays the same, and it must not be lost in translation.
472
482
  if (req.orderQuote.id) req.quote = { ...(req.quote || {}), id: req.orderQuote.id };
473
- // СТОРОНА КОНТРАГЕНТА - ЭТА ЖЕ КОТИРОВКА: в ней половина, точка и доказательство провайдера, и её же
474
- // проверяет ядро (order.acceptCounterparty). Своей стороны контрагента мост не выдумывает.
483
+ // THE COUNTERPARTY SIDE IS THIS SAME QUOTE: it holds the provider's half, point and proof, and the core
484
+ // checks it too (order.acceptCounterparty). The bridge invents no counterparty side of its own.
475
485
  if (!req.counterparty) req.counterparty = req.orderQuote;
476
486
  }
477
- // СРОКИ ОРДЕРА УХОДЯТ И В КОТИРОВКУ: стражи сроков (preflight -> deadlineVerdict) читают readyBy/t1 ИЗ
478
- // КОТИРОВКИ, а не из условий. Значения ТЕ ЖЕ, что ушли в order - второго счёта и второго мнения не
479
- // появляется; иначе ядро честно отказывает кодом deadline { kind: "deadlines-unstated" }.
487
+ // THE ORDER DEADLINES ALSO GO INTO THE QUOTE: the deadline guards (preflight -> deadlineVerdict) read readyBy/t1
488
+ // FROM THE QUOTE, not from the terms. The values are THE SAME as those that went into order - no second count
489
+ // and no second opinion; otherwise the core honestly refuses with deadline { kind: "deadlines-unstated" }.
480
490
  req.quote = { ...(req.quote || {}), readyBy: Number(req.order.readyBy), t1: Number(req.order.t1) };
481
491
  return ctx.sdk.swaps.start(req);
482
492
  }
483
493
 
484
- // НАБЛЮДЕНИЕ ЗА ПРИХОДОМ XMR - ТОТ ЖЕ ШАГ ЯДРА, ЧТО И У ЗАПУСКА (GET /api/swaps/{id}). Отдельная функция
485
- // нужна экранам состояния: они ждут приход, а не начинают сделку. Ожидания ордера и кошелёк передаёт
486
- // вызывающий - без них ядро не подписывает отметку готовности, а лишь называет её следующий шаг.
494
+ // WATCHING THE XMR ARRIVAL - THE SAME CORE STEP AS THE START (GET /api/swaps/{id}). The separate function is
495
+ // needed by state screens: they wait for the arrival rather than starting a swap. The order expectations and the
496
+ // wallet are passed by the caller - without them the core does not sign the readiness mark, only names its next step.
487
497
  export async function sdkWatchSwap(request) {
488
498
  const ctx = await instanceFor(request && request.chain);
489
499
  if (!ctx) return null;
490
500
  return ctx.sdk.swaps.watch(request);
491
501
  }
492
502
 
493
- // --- СПИСОК СДЕЛОК ОТ ЯДРА (экран #/swaps) ---------------------------------------------------------
494
- // ЧТЕНИЕ, СЛИЯНИЕ И УБОРКА ИДУТ ЧЕРЕЗ ЯДРО, А НЕ ЧЕРЕЗ ДВИЖОК НА ЭКРАНЕ. sdkListSwaps выше отдаёт
495
- // состояния сделок (swaps.list); ниже - серверный список (swaps.sync: путь и правило слияния живут в ядре)
496
- // и уборка записи (swaps.forget: она убирает запись только здесь, на сервере и в контракте всё остаётся).
497
- // Ядро не поднялось - null, и причину называет консоль (см. sdkBundle), а не молчание.
503
+ // --- THE DEAL LIST FROM THE CORE (the #/swaps screen) ---------------------------------------------
504
+ // READING, MERGING AND DROPPING GO THROUGH THE CORE, NOT THE ENGINE ON THE PAGE. sdkListSwaps above returns
505
+ // deal states (swaps.list); below is the server list (swaps.sync: the path and merge rule live in the core) and
506
+ // record dropping (swaps.forget: it removes the record only here; the server and the contract keep everything).
507
+ // The core did not come up - null, and the console names the cause (see sdkBundle), not silence.
498
508
  export async function sdkSyncSwaps(chainSlug) {
499
509
  const ctx = await instanceFor(chainSlug);
500
510
  return ctx ? ctx.sdk.swaps.sync() : null;
@@ -505,46 +515,46 @@ export async function sdkForgetSwap(id, chainSlug) {
505
515
  return ctx ? ctx.sdk.swaps.forget(id) : null;
506
516
  }
507
517
 
508
- // СЛЕЖЕНИЕ И ЧТЕНИЯ СТРАНИЦЫ - ЧЕРЕЗ ТОТ ЖЕ ШОВ, ПОД ПРЕЖНИМИ ИМЕНАМИ. Экран объявляет фокус опроса и
509
- // читает ставку комиссии (справочник) ЗДЕСЬ, а не из core/ и evm/ напрямую. Имена оставлены ДОСЛОВНО теми
510
- // же, что были у движка (тот же приём, что у markReadyOrder/claimOrder/refundOrder выше): экран меняет
511
- // ИСТОЧНИК импорта, а не строку вызова. Состояние ордера (orderStatus/orderDeadlines) объявлено функциями
512
- // выше - это те же чтения, но теперь правила чтения живут в пакете, а не в www/js/core/swap-flow.js.
518
+ // WATCHING AND PAGE READS - THROUGH THE SAME SEAM, UNDER THE FORMER NAMES. The screen declares the poll focus and
519
+ // reads the fee rate (the reference panel) HERE, not from core/ and evm/ directly. The names are left VERBATIM as
520
+ // the engine's (the same technique as markReadyOrder/claimOrder/refundOrder above): the screen changes the import
521
+ // SOURCE, not the call expression. The order state (orderStatus/orderDeadlines) is declared as functions above -
522
+ // the same reads, but the reading rules now live in the package.
513
523
  export { setLiveFocus, cachedFeeRate, dealSwapIds, serverRestoreHeightFor, STEP_LABELS };
514
524
 
515
- // СОСТОЯНИЕ ОРДЕРА И ЕГО СРОКИ - ЧТЕНИЕ ЦЕПИ КОШЕЛЬКОМ СТРАНИЦЫ, НО ЧЕРЕЗ ЯДРО (#32, волна 3). Сами правила
516
- // чтения (status()/t1()/termsHash()/readyBy() и разбор ответов) переехали в пакет (sdk/src/swap-flow.mjs,
517
- // фасад order.status/order.deadlines). Склейник отдаёт их экрану ПОД ПРЕЖНИМИ ИМЕНАМИ, а кошелёк-читатель
518
- // подсовывает ядру тот же, что и у действий ордера (optionsFor -> evmCall), поэтому читает тот же кошелёк,
519
- // что и подпишет. Ядро не загрузилось - НАЗВАННЫЙ отказ, а не молчание и не пустое состояние.
525
+ // THE ORDER STATE AND ITS DEADLINES - CHAIN READS WITH THE PAGE'S WALLET, BUT THROUGH THE CORE. The reading rules
526
+ // themselves (status()/t1()/termsHash()/readyBy() and answer parsing) moved into the package
527
+ // (sdk/src/swap-flow.mjs, the order.status/order.deadlines facade). The shim exposes them to the screen UNDER THE
528
+ // FORMER NAMES, and the reader wallet it hands the core is the same as for the order actions (optionsFor ->
529
+ // evmCall), so it reads the same wallet that will sign. The core did not load - a NAMED refusal, not an empty state.
520
530
  export async function orderStatus(escrow) {
521
531
  const ctx = await instanceFor(undefined);
522
- if (!ctx) throw new Error("[bridge] состояние ордера не прочитать: ядро SDK странице не отдалось");
532
+ if (!ctx) throw new Error("[bridge] cannot read the order state: the SDK core was not served to the page");
523
533
  return ctx.sdk.order.status(escrow);
524
534
  }
525
535
 
526
536
  export async function orderDeadlines(escrow) {
527
537
  const ctx = await instanceFor(undefined);
528
- if (!ctx) throw new Error("[bridge] сроки ордера не прочитать: ядро SDK странице не отдалось");
538
+ if (!ctx) throw new Error("[bridge] cannot read the order deadlines: the SDK core was not served to the page");
529
539
  return ctx.sdk.order.deadlines(escrow);
530
540
  }
531
541
 
532
- // ВЫСОТА УЗЛА MONERO - ЧТЕНИЕ СТРАНИЦЫ. Своего нода у ядра нет (все чтения цепи идут через адаптер кошелька
533
- // страницы), поэтому высота для высоты скана берётся здесь, а не выдумывается. Имя сохранено (nodeHeight).
542
+ // THE MONERO NODE HEIGHT - A PAGE READ. The core has no node (all chain reads go through the page's wallet
543
+ // adapter), so the height for the scan height is taken here, not invented. The name is kept (nodeHeight).
534
544
  export async function nodeHeight() { return nodeHeightValue(); }
535
545
 
536
- // --- СОСТОЯНИЕ И ДЕЙСТВИЯ ДЕМОНСТРАЦИОННОЙ СДЕЛКИ (экран хода, progress.js) -------------------------
537
- // СОСТОЯНИЕ ОТДАЁТ ЯДРО: swaps.progress собирает снимок сделки (запись + вывод состояния + кошелёк из половин).
538
- // Прежней движковой дороги у экрана НЕТ (в отличие от действий ордера выше): ядро не поднялось - null, и экран
539
- // честно говорит, что состояния нет. Так же устроен и экран списка (sdkListSwaps).
546
+ // --- THE DEMO DEAL STATE AND ACTIONS (the progress screen, progress.js) ---------------------------
547
+ // THE STATE COMES FROM THE CORE: swaps.progress assembles a deal snapshot (record + state output + wallet from halves).
548
+ // The screen has NO former engine road (unlike the order actions above): the core did not come up - null, and the
549
+ // screen honestly says there is no state. The list screen is built the same way (sdkListSwaps).
540
550
  export async function sdkProgress(id, chainSlug) {
541
551
  const ctx = await instanceFor(chainSlug);
542
552
  return ctx ? ctx.sdk.swaps.progress(id) : null;
543
553
  }
544
554
 
545
- // ДЕЙСТВИЯ И ЗАПИСЬ ДЕМОНСТРАЦИОННОЙ СДЕЛКИ - через ядро, с прежним вызовом движка как запасным путём: это ТЕ ЖЕ
546
- // функции (sdk/src/swaps.mjs зовёт engine.swap.*). Имена - ядра (swaps.confirmReady/refundEth/sweepNow/
547
- // setDestination), а не строки вызова экрана.
555
+ // THE DEMO DEAL ACTIONS AND RECORD - through the core, with the former engine call as a fallback: they are THE SAME
556
+ // functions (sdk/src/swaps.mjs calls engine.swap.*). The names are the core's (swaps.confirmReady/refundEth/sweepNow/
557
+ // setDestination), not the screen's call strings.
548
558
  export async function sdkConfirmReady(id) {
549
559
  const ctx = await instanceFor(undefined);
550
560
  if (ctx) return ctx.sdk.swaps.confirmReady(id);
@@ -572,31 +582,31 @@ export async function sdkSetDestination(id, address) {
572
582
  return engineSaveSwap(swap);
573
583
  }
574
584
 
575
- // СЕТЬ АДРЕСА MONERO - ПОВЕРХНОСТЬ ЯДРА (оно владеет форматом адреса): склейник только отдаёт её экрану.
576
- // Запасной путь - тот же модуль, что зеркалит ядро (www/js/monero/address.js): второй таблицы префиксов нет.
585
+ // THE MONERO ADDRESS NETWORK - A CORE SURFACE (it owns the address format): the shim only exposes it to the screen.
586
+ // The fallback is the same module that mirrors the core (www/js/monero/address.js): there is no second prefix table.
577
587
  export async function sdkAddressNetwork(address) {
578
588
  const ctx = await instanceFor(undefined);
579
589
  if (ctx) return ctx.sdk.swaps.addressNetwork(address);
580
590
  try { return networkFromShape(address); } catch { return null; }
581
591
  }
582
592
 
583
- // --- ВИТРИНА И МЕХАНИЗМЫ СТРАНИЦЫ: ФОРМА, ПОДПИСЬ, ПРОДАЖА, ОСНОВАНИЕ -------------------------------
584
- // ЗДЕСЬ ЖИВЁТ ТО, ЧТО ЭКРАНЫ БРАЛИ У ДВИЖКА НАПРЯМУЮ, а теперь берут ТЕМ ЖЕ ШВОМ - под теми же именами.
585
- // Это ОСОЗНАННО оставшееся и названное, а не забытое:
586
- // * evm, swapCore, moneroNode - механизмы СТРАНИЦЫ: кошелёк (подпись возможна только в его провайдере),
587
- // свод сделок и такт опроса, чтения и опрос узла Monero (своего узла у ядра нет);
588
- // * цены, комиссии, газ и DEX-нога формы - ВИТРИНА: числа читаются у цепи модулями движка, а экран
589
- // получает их швом, а не второй дорогой в движок;
590
- // * networkFromShape/networkLabel - форма адреса Monero и её название: тот же модуль формата, что
591
- // зеркалит ядро (второй таблицы префиксов нет);
592
- // * createOrderWorker - воркер половин атомарного свопа: криптография живёт в одном месте и зовётся
593
- // воркером, чтобы главный поток не замирал;
594
- // * createSwap/createReverseSwap/activeSwaps - ЗАПИСЬ демонстрационной и обратной сделки (запись ведёт
595
- // движок; ядро отдаёт её СОСТОЯНИЕ через swaps.list/swaps.progress).
596
- // * createSwapWallet - КОШЕЛЁК СДЕЛКИ ФОРМЫ (состояние между экранами, ui/formState.js): кошелёк страницы
597
- // со своими ключами. Ядру он не нужен, поэтому шов отдаёт его тем же импортом, что и прочее.
598
- // ИМЯ БЕРЁТСЯ ИЗ ДВИЖКА ЗДЕСЬ ЖЕ (импортом) И ОТДАЁТСЯ НАРУЖУ (экспортом) - один шов, одна копия. Ни
599
- // одного нового правила здесь нет: экран меняет источник импорта, а не строку вызова.
593
+ // --- THE PAGE SHOWCASE AND MECHANISMS: FORM, SIGNING, SALE, FOUNDATION ----------------------------
594
+ // HERE LIVES WHAT SCREENS TOOK FROM THE ENGINE DIRECTLY and now take through THE SAME SEAM - under the same names.
595
+ // This is deliberately kept and named, not forgotten:
596
+ // * evm, swapCore, moneroNode - PAGE mechanisms: the wallet (signing is possible only in its provider),
597
+ // the deal digest and the poll tick, reads and polling of the Monero node (the core has no node);
598
+ // * prices, fees, gas and the form's DEX leg - SHOWCASE: the numbers are read from the chain by engine
599
+ // modules, and the screen gets them through the seam, not by a second road into the engine;
600
+ // * networkFromShape/networkLabel - the Monero address shape and its name: the same format module the core
601
+ // mirrors (there is no second prefix table);
602
+ // * createOrderWorker - the atomic-swap halves worker: the cryptography lives in one place and is called by a
603
+ // worker so the main thread does not freeze;
604
+ // * createSwap/createReverseSwap/activeSwaps - the RECORD of the demo and reverse deal (the record is kept by
605
+ // the engine; the core exposes its STATE via swaps.list/swaps.progress).
606
+ // * createSwapWallet - THE FORM'S DEAL WALLET (state between screens, ui/formState.js): the page wallet with
607
+ // its own keys. The core does not need it, so the seam exposes it with the same import as the rest.
608
+ // THE NAME IS TAKEN FROM THE ENGINE HERE (by import) AND EXPOSED OUTWARD (by export) - one seam, one copy. No
609
+ // new rule here: the screen changes the import source, not the call expression.
600
610
  import { networkLabel, probeMode, createSwapWallet } from "../monero/wallet.js";
601
611
  import { amountInputValue } from "../evm/amounts.js";
602
612
  import { refreshChainPrices, priceLabel } from "../evm/prices.js";