@ninsei-labs/sdk 0.39.2 → 0.40.0-develop.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +4 -1
  3. package/engine/atomic/dleq2.js +86 -111
  4. package/engine/atomic/halfenc.js +58 -72
  5. package/engine/atomic/halves.js +31 -53
  6. package/engine/atomic/order-client.js +15 -16
  7. package/engine/atomic/order.js +44 -62
  8. package/engine/atomic/orderContext.js +26 -50
  9. package/engine/atomic/orderHalfCommit.js +17 -18
  10. package/engine/core/chainSource.js +54 -69
  11. package/engine/core/clock.js +11 -12
  12. package/engine/core/config.js +554 -449
  13. package/engine/core/finality.js +34 -53
  14. package/engine/core/format.js +35 -36
  15. package/engine/core/preSignGuards.js +109 -117
  16. package/engine/core/rfqSource.js +57 -75
  17. package/engine/core/sellWindow.js +23 -24
  18. package/engine/core/store.js +20 -21
  19. package/engine/core/swap.js +242 -243
  20. package/engine/evm/amounts.js +50 -51
  21. package/engine/evm/asyncExec.js +577 -0
  22. package/engine/evm/asyncLegs.js +308 -0
  23. package/engine/evm/asyncRoute.js +179 -0
  24. package/engine/evm/auth.js +41 -48
  25. package/engine/evm/claimGas.js +28 -47
  26. package/engine/evm/depositor.js +39 -43
  27. package/engine/evm/dex.js +266 -267
  28. package/engine/evm/escrow.js +113 -129
  29. package/engine/evm/factory.js +71 -77
  30. package/engine/evm/fees.js +41 -42
  31. package/engine/evm/funding.js +160 -162
  32. package/engine/evm/gasReserve.js +67 -36
  33. package/engine/evm/index.js +46 -53
  34. package/engine/evm/injected.js +34 -42
  35. package/engine/evm/permit.js +129 -56
  36. package/engine/evm/prices.js +30 -45
  37. package/engine/evm/session.js +188 -189
  38. package/engine/mock/chain.js +17 -18
  39. package/engine/monero/address.js +33 -48
  40. package/engine/monero/halvesSweep.js +25 -43
  41. package/engine/monero/node.js +14 -15
  42. package/engine/monero/scan.js +82 -95
  43. package/engine/monero/swapKeys.js +30 -33
  44. package/engine/monero/unlock.js +17 -20
  45. package/engine/monero/wallet.js +32 -42
  46. package/engine/recovery/recoveryFile.js +154 -161
  47. package/engine/recovery/restore.js +50 -61
  48. package/engine/sdk/bridge.js +244 -234
  49. package/engine/ui/dom.js +4 -5
  50. package/engine/ui/walletModal.js +20 -31
  51. package/index.d.ts +26 -0
  52. package/package.json +1 -1
  53. package/src/adapters/evm-wallet.mjs +16 -0
  54. package/src/adapters/monero-wallet.mjs +1 -1
  55. package/src/adapters/monero.mjs +1 -1
  56. package/src/adapters/node-test-evm-wallet.mjs +5 -5
  57. package/src/codes.mjs +5 -0
  58. package/src/config.mjs +1 -1
  59. package/src/engine.mjs +4 -0
  60. package/src/index.mjs +16 -2
  61. package/src/legs/cow-spec.mjs +254 -0
  62. package/src/legs/cow.mjs +315 -0
  63. package/src/legs/evm.mjs +86 -2
  64. package/src/legs/kyber-spec.mjs +265 -0
  65. package/src/legs/kyber.mjs +425 -0
  66. package/src/legs/shape.mjs +35 -0
  67. package/src/legs/uniswaprelay-spec.mjs +541 -0
  68. package/src/legs/uniswaprelay.mjs +422 -0
  69. package/src/legs/uniswapx-spec.mjs +329 -0
  70. package/src/legs/uniswapx.mjs +362 -0
  71. package/src/quotes.mjs +23 -5
  72. package/src/swaps.mjs +18 -1
@@ -1,215 +1,346 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/core/config.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
3
- // tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
2
+ // Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
4
3
  // Everything below this header is identical to the source.
5
4
 
6
- // Конфигурация демки. Все числа, которые видит пользователь, живут здесь.
7
- // Значения взяты из LITEPAPER.md (разделы 5.0, 6, 7) и помечены в UI как демо-данные.
5
+ // Demo configuration. Every number a user sees lives here. Values come from the litepaper and are marked as demo data in the UI.
8
6
  //
9
- // Сети и токены описаны одной структурой CHAINS: у каждой сети свой нативный токен,
10
- // свой список принимаемых токенов и свои decimals. Это не украшательство: у USDT и USDC
11
- // decimals РАЗНЫЕ в разных сетях (6 на Ethereum/Arbitrum/Base и 18 на BSC), а "ETH"
12
- // в BSC вообще не существует - там нативный BNB. Поэтому идентификатор токена в
13
- // состоянии свапа составной: "<chainId>:<SYMBOL>", например "42161:USDC".
14
- // Адреса и decimals проверяются в сети: node tools/check-tokens.mjs (см. .hermes/docs).
7
+ // Networks and tokens are one CHAINS structure: each network has its own native token, its own accepted tokens and
8
+ // its own decimals. Not decoration: USDT and USDC have DIFFERENT decimals on different networks (6 on
9
+ // Ethereum/Arbitrum/Base, 18 on BSC), and "ETH" does not exist on BSC - the native is BNB. So a token id in the
10
+ // swap state is composite: "<chainId>:<SYMBOL>", e.g. "42161:USDC". Addresses and decimals are verified on chain by a check.
15
11
  //
16
- // ТРИ ПРИЗНАКА У ЗАПИСЕЙ, и ни один из них не означает "не поддерживается":
17
- // hidden (у СЕТИ) - сеть живая и выбирается по идентификатору, но её строки нет в списке сетей;
18
- // hidden (у ТОКЕНА) - токен живой и есть в списке на обмене, но его нет в витрине сети (в шапке);
19
- // peg: "native" - цена токена берётся от натива (WETH = ETH), своей демо-цены у него нет.
20
- // Смысл токенного hidden: витрина сети решает, что показать рядом с названием сети, а торговать
21
- // можно всем, что принимается. Так WETH есть в обмене, но не раздувает подпись сети в шапке.
12
+ // THREE MARKS ON RECORDS, none meaning "unsupported":
13
+ // hidden (network) - alive and selected by id, but not in the network list;
14
+ // hidden (token) - alive and in the swap list, but not in the network showcase (header);
15
+ // peg: "native" - the price comes from native (WETH = ETH); no demo price of its own.
16
+ // Token hidden means: the showcase decides what to show next to the network name, while all accepted can be traded.
17
+ // So WETH is in the swap list but does not bloat the network caption in the header.
22
18
  //
23
- // DEX-НОГА (Uniswap v3). У сети может быть блок uniswap: адреса QuoterV2 и UniswapV3Factory, адрес
24
- // обёрнутого нативного токена (WETH/WBNB) и ступени комиссии. Адреса НЕ выдуманы: они взяты из
25
- // официального списка развёртываний Uniswap (github.com/Uniswap/contracts, deployments/json/<chainId>.json)
26
- // и подтверждены ВЫЗОВОМ в сети - наличие кода, symbol()/decimals() у токена, ответ getPool и живая
27
- // котировка. Проверка и замер ликвидности: node tools/check-uniswap-quote.mjs. Сети без этого блока
28
- // означают "адреса не подтверждены вызовом", а не "DEX там не бывает": интерфейс обязан сказать словами.
19
+ // THE DEX LEG (Uniswap v3). A network may have a uniswap block: QuoterV2 and UniswapV3Factory addresses, the wrapped
20
+ // native (WETH/WBNB) and fee tiers. The addresses are NOT invented: they come from the official Uniswap deployment
21
+ // list (github.com/Uniswap/contracts, deployments/json/<chainId>.json) and are confirmed by a chain CALL - code
22
+ // present, symbol()/decimals(), getPool's answer and a live quote. A network without this block means "addresses not
23
+ // confirmed by a call", not "no DEX": the interface must say so in words.
24
+
25
+ // DEPLOYMENT ENDPOINTS. THE ONE PLACE a deployment names node addresses; nothing else in the code hard-codes a host,
26
+ // and no production domain is invented. ONE source, read by BOTH environments by the SAME names:
27
+ // * in the browser - window.NINSEI_DEPLOY = { moneroNode, evmRpc, sweepOrigin } set at deploy;
28
+ // * in Node (checks, tools) - the environment: NINSEI_MONERO_NODE, NINSEI_EVM_RPC, NINSEI_SWEEP_ORIGIN.
29
+ // With nothing set, the LOCAL CONTOUR is the honest default (our own Monero node, our own anvil, our own sweep
30
+ // page): checks then run in Node with no live stand, and no production address lives in the code. A deployment
31
+ // overrides all three.
32
+ const BROWSER_DEPLOY = (typeof window !== "undefined" && window.NINSEI_DEPLOY) ? window.NINSEI_DEPLOY : null;
33
+ const NODE_ENV = (typeof process !== "undefined" && process.env) ? process.env : null;
34
+ const LOCAL_ENDPOINTS = Object.freeze({
35
+ moneroNode: "http://127.0.0.1:38081",
36
+ evmRpc: "http://127.0.0.1:8545",
37
+ sweepOrigin: "http://localhost:5195",
38
+ });
39
+ export const ENDPOINTS = Object.freeze({
40
+ moneroNode: (BROWSER_DEPLOY && BROWSER_DEPLOY.moneroNode) || (NODE_ENV && NODE_ENV.NINSEI_MONERO_NODE) || LOCAL_ENDPOINTS.moneroNode,
41
+ evmRpc: (BROWSER_DEPLOY && BROWSER_DEPLOY.evmRpc) || (NODE_ENV && NODE_ENV.NINSEI_EVM_RPC) || LOCAL_ENDPOINTS.evmRpc,
42
+ sweepOrigin: (BROWSER_DEPLOY && BROWSER_DEPLOY.sweepOrigin) || (NODE_ENV && NODE_ENV.NINSEI_SWEEP_ORIGIN) || LOCAL_ENDPOINTS.sweepOrigin,
43
+ });
44
+
45
+ // THE PAGE'S OWN ADDRESS comes from the environment, not from a constant: WalletConnect compares the metadata url
46
+ // with the page's actual origin, so a hard-coded host breaks the session on any other host.
47
+ const PAGE_ORIGIN = typeof location !== "undefined" ? location.origin : "";
48
+
49
+ // THE EIP-712 SIGNING DOMAIN (name/version) for the depositor signature. It is NOT a brand literal in the signer: it
50
+ // belongs to the registry contract (it returns DOMAIN_SEPARATOR()), so it is taken from the config here. The defaults
51
+ // reproduce today's result byte for byte.
52
+ export const SIGNING_DOMAIN = Object.freeze({ name: "NinseiEscrowFactory", version: "1" });
29
53
 
30
54
  export const APP = {
31
- name: "ArrakisSwap",
55
+ name: "NinseiSwap",
32
56
  version: "0.1.0",
33
57
  demo: true,
34
- storageKey: "arrakis.demo.v1",
58
+ storageKey: "ninsei.demo.v1",
35
59
  };
36
60
 
37
61
  // ---------------------------------------------------------------------------
38
- // Сети. rpcUrl здесь нужен ТОЛЬКО как параметр для wallet_addEthereumChain (им
39
- // пользуется кошелёк пользователя, не наша страница), поэтому CSP и наш сервер
40
- // к этим адресам отношения не имеют. Балансы демка читает через провайдера кошелька.
62
+ // Networks. rpcUrl is needed ONLY as a wallet_addEthereumChain parameter (the user's wallet uses it, not our page),
63
+ // so CSP and our server have nothing to do with these addresses. Balances are read through the wallet provider.
41
64
  // ---------------------------------------------------------------------------
42
65
 
43
- // Поле escrow отвечает на один вопрос: можно ли в этой сети СЕТТЛИТЬСЯ.
66
+ // The escrow field answers one question: can one SETTLE on this network.
44
67
  //
45
- // Своп заканчивается там, где лежит контракт, поэтому сеть без контракта показываем в списке, но
46
- // выбрать не даём (в интерфейсе она серая с пометкой "coming soon"). Пока контракт не задеплоен
47
- // нигде, демка работает в режиме имитации: mode "simulated" - фондирование эскроу подделывается
48
- // (simulateEscrowFunding), реальных денег никто не двигает, и это честно написано в UI.
68
+ // A swap ends where the contract lies, so a network without a contract is listed but not selectable (grey, "coming
69
+ // soon"). While no contract is deployed anywhere the demo runs in simulation: mode "simulated" - escrow funding is
70
+ // faked (simulateEscrowFunding), no real money moves, and the UI says so.
49
71
  //
50
- // escrow: { address: null, mode: "simulated" } - демо-режим, выбрать можно
51
- // escrow: { address: "0x…", mode: "live" } - контракт задеплоен (сеть закрепляется за ним)
52
- // escrow: null - контракта нет и не планировался: "coming soon"
72
+ // escrow: { address: null, mode: "simulated" } - demo mode, selectable
73
+ // escrow: { address: "0x…", mode: "live" } - deployed; the network is tied to it
74
+ // escrow: null - no contract and none planned: "coming soon"
53
75
  //
54
- // СЕТИ, ГДЕ DEX-НОГА ВКЛЮЧЕНА И ИЗМЕРЯЕТСЯ (список DEX_CHAINS ниже). Он именно в ДАННЫХ, и это не
55
- // украшение: обходить весь реестр нельзя, потому что большинство сетей в статусе coming soon - по ним
56
- // работа не ведётся, пулы не ищутся и адреса не подтверждаются. Когда придёт время следующей сети, она
57
- // добавляется ОДНОЙ СТРОКОЙ в DEX_CHAINS и блоком uniswap у себя - и попадает и в интерфейс, и в замер
58
- // (tools/check-uniswap-quote.mjs берёт список сетей отсюда, а не обходом CHAINS).
76
+ // NETWORKS WHERE THE DEX LEG IS ON AND MEASURED (DEX_CHAINS below). It is in DATA, not decoration: the whole registry
77
+ // cannot be walked, as most networks are coming soon. The next network is added with ONE line in DEX_CHAINS and a
78
+ // uniswap block of its own - and lands both in the interface and in the measurement.
59
79
  //
60
- // ПОЧЕМУ ИМЕННО ЭТИ: arbitrum-sepolia (421614) - демо-сеть, где развёрнут наш эскроу и идут все прогоны;
61
- // arbitrum (42161) и mainnet (1) - боевые сети, где видно, что маршрут живой на настоящей ликвидности.
80
+ // WHY THESE: arbitrum-sepolia (421614) - the demo network with our escrow and all runs;
81
+ // arbitrum (42161) and mainnet (1) - live networks where the route is alive on real liquidity.
62
82
  export const DEX_CHAINS = ["arbitrum-sepolia", "arbitrum", "mainnet"];
63
83
 
64
- // СОСТАВ ОБМЕНА ЗАДАН ЖЁСТКО ЗДЕСЬ - ЭТО РЕШЕНИЕ, А НЕ РЕЗУЛЬТАТ ИЗМЕРЕНИЯ. Список читается как есть:
65
- // код ничего в него не добавляет и ничего из него не выкидывает, замер его не фильтрует. Если у токена в
66
- // сети нет пула, он ВСЁ РАВНО остаётся в списке, а факт отсутствия пула пишется в отчёт
67
- // (tools/check-uniswap-quote.mjs) - ликвидность оценивает человек, а не инструмент.
68
- // СЕТЬ МОЖЕТ НЕ ПРЕДЛАГАТЬ ЧАСТЬ СОСТАВА, и это ДРУГОЕ состояние, чем "маршрута нет": тогда актива нет
69
- // ни в tokens этой сети, ни в DEX_ROUTES у неё, и why-заглушки у него быть НЕ ДОЛЖНО (заглушка значила
70
- // бы "актив предлагается, но живого пула нет"). Пример - USDS и PyUSD на Arbitrum One (решение хозяина
71
- // 2026-09-23). Противоречия при этом краснеют: маршрут для актива, которого сеть не предлагает, и токен
72
- // в списке сети, которого нет в составе обмена.
84
+ // THE SWAP SET IS FIXED HERE - A DECISION, NOT A MEASUREMENT. Read as is: the code adds and drops nothing, the
85
+ // measurement does not filter it. A token without a pool STILL stays in the list, and the absence is reported -
86
+ // liquidity is a person's call, not a tool's.
87
+ // A NETWORK MAY NOT OFFER PART OF THE SET, which is ANOTHER state than "no route": the asset is then in neither
88
+ // the network's tokens nor its DEX_ROUTES, and it must NOT have a why-stub. Example - USDS and PyUSD on Arbitrum
89
+ // One (the owner's decision). Contradictions go red: a route for an asset the network does not offer, and a token
90
+ // in the network list that is not in the swap set.
73
91
  export const DEX_ASSETS = ["USDT", "USDC", "DAI", "USDS", "USDe", "PyUSD"];
74
92
 
75
- // СПРАВКА ДЛЯ ИНСТРУМЕНТА, А НЕ МЕХАНИЗМ ОБМЕНА: какие стейблы вообще годятся в посредники в этих сетях.
76
- // Когда обход нужен, он объявляется маршрутом в DEX_ROUTES ниже - человеком, а не перебором. DEX-нога
77
- // однонаправленная и доводит вход до НАТИВНОГО ТОКЕНА СЕТИ, потому что им фондируется эскроу; маршрутов,
78
- // заканчивающихся стейблом, не бывает по устройству ноги, и объявить такой нельзя (инструмент заметит).
93
+ // REFERENCE FOR THE TOOL, NOT A SWAP MECHANISM: which stables may serve as intermediaries. When a detour is needed
94
+ // it is declared as a route in DEX_ROUTES below - by a person, not by brute force. The DEX leg is one-way and
95
+ // brings the input to the NETWORK'S NATIVE token (the escrow is funded with it); routes ending in a stable do not
96
+ // exist by design and cannot be declared (the tool would notice).
79
97
  export const DEX_HUBS = ["USDC", "USDT", "DAI"];
80
98
 
81
- // ПОРОГИ ПРОВЕРКИ ЦЕНЫ - ЧИСЛА В ДАННЫХ, А НЕ В ЛОГИКЕ (www/js/evm/dex.js: priceGateVerdict). Проверка
82
- // отвечает на ОДИН вопрос: цена в пулах объявленного маршрута похожа на рыночную или сделка неверная.
83
- // Это не оценка ликвидности (её по-прежнему решает человек), а защита того же рода, что покрытие суммы:
84
- // неверная цена означает, что сделка не сойдётся, и такая сделка не должна начинаться (док 45).
85
- // maxDeviationPct - насколько неявная цена натива, ВЫВЕДЕННАЯ ИЗ КОТИРОВКИ последнего шага, может
86
- // разойтись с эталоном цены натива (Chainlink, evm/prices.js), прежде чем это будет
87
- // "не рынок". 2% - граница между обычным разбегом и неверной ценой;
88
- // stableBandPct - полоса "стейбл к стейблу" (шаги USDe→USDC, USDS→USDC, PyUSD→USDC): цена обязана
89
- // быть около 1:1, и это проверяется САМОЙ КОТИРОВКОЙ, без оракула. 1% покрывает
90
- // комиссию ступени (0.3%) и обычный разбег курса;
91
- // stables - какие активы считаются стейблами для этой полосы. Это ТЕ ЖЕ активы, что в составе
92
- // обмена (DEX_ASSETS): там одни стейблы по определению, и второго списка не заводим.
99
+ // THE PRICE-CHECK THRESHOLDS - NUMBERS IN DATA, NOT IN LOGIC (www/js/evm/dex.js: priceGateVerdict). The check
100
+ // answers ONE question: is the route's price like the market, or is the deal wrong. Not liquidity scoring (still a
101
+ // person's call) but a guard like amount coverage: a wrong price means the deal will not settle and must not start.
102
+ // maxDeviationPct - how far the implied native price FROM THE LAST STEP'S QUOTE may diverge from the native price
103
+ // reference (Chainlink, evm/prices.js) before "not market". 2% is the boundary between ordinary
104
+ // spread and a wrong price;
105
+ // stableBandPct - the "stable to stable" band (USDe→USDC, USDS→USDC, PyUSD→USDC): the price must be near 1:1,
106
+ // checked BY THE QUOTE ITSELF with no oracle. 1% covers the step fee (0.3%) and ordinary spread;
107
+ // stables - which assets count as stables for this band: THE SAME as in the swap set (DEX_ASSETS), all
108
+ // stables by definition, so no second list.
93
109
  export const DEX_PRICE_GATE = {
94
110
  maxDeviationPct: 2,
95
111
  stableBandPct: 1,
96
112
  stables: DEX_ASSETS,
97
113
  };
98
114
 
99
- // ПОРОГИ ЗАЩИТ ПЕРЕД ПОДПИСЬЮ - ТОЖЕ ЧИСЛА В ДАННЫХ, А НЕ В ЛОГИКЕ (www/js/core/preSignGuards.js).
100
- // Здесь лежит только то, что относится к СРОКАМ ордера; пороги цены и покрытия - выше, у DEX-ноги.
101
- // minReadyLeadSec - насколько срок отметки готовности обязан быть впереди "сейчас", прежде чем
102
- // ордер можно подписывать. Порог - МЯГКОЕ окно продажи (600 с, www/js/core/sellWindow.js):
103
- // столько провайдер обещает держать цену на экране. Раньше здесь стояло 1200 - ВЕСЬ срок
104
- // подтверждений, - и это была мина после issue #111: обратный билет нода выдаёт ровно с
105
- // контрактным окном 1200 с, а страница проверяет сроки на секунду-другую ПОЗЖЕ (см.
106
- // www/js/core/reverseOrder.js, deadlineVerdict), то есть заявка блокировала САМУ СЕБЯ.
107
- // Порог обязан быть строго меньше контрактного окна, иначе он ловит собственный билет.
108
- // minClaimWindowSec - насколько срок расчёта (t1) обязан отстоять от срока готовности: окно, в
109
- // которое забирающий успевает забрать ETH, не может быть "несколько секунд".
110
- // 10 минут против штатного часа.
115
+ // PRE-SIGNING GUARD THRESHOLDS - ALSO NUMBERS IN DATA, NOT IN LOGIC (www/js/core/preSignGuards.js).
116
+ // Only ORDER DEADLINES live here; the price and coverage thresholds are above, at the DEX leg.
117
+ // minReadyLeadSec - how far the readiness-mark deadline must be ahead of "now" before signing. The threshold is
118
+ // the SOFT sell window (600 s, www/js/core/sellWindow.js): how long the provider promises to
119
+ // hold the on-screen price. It used to be 1200 - the WHOLE confirmation window - which blocked
120
+ // the request ITSELF, because the node issues a reverse ticket with exactly the contract window
121
+ // of 1200 s while the page checks deadlines a second or two later. The threshold must be
122
+ // strictly below the contract window.
123
+ // minClaimWindowSec - how far the settlement deadline (t1) must stand from the readiness deadline: the window in
124
+ // which the claimer claims the ETH cannot be "a few seconds". 10 minutes against the usual hour.
111
125
  export const SIGNING_GUARDS = {
112
126
  minReadyLeadSec: 600,
113
127
  minClaimWindowSec: 600,
114
- // maxDeadlineSec - ПОТОЛОК срока (issue #88): `t1` не дальше этого от "сейчас". Иначе срок можно
115
- // поставить на год вперёд, и внесённые ETH нельзя вернуть ни до readyBy, ни до t1 - целый год.
116
- // 72 часа (3 суток) - решение владельца; штатные окна (4 ч + 1 ч при покупке, 2 ч + 1 ч при продаже)
117
- // укладываются в него с большим запасом.
128
+ // maxDeadlineSec - the deadline CEILING: `t1` is no farther than this from "now". Otherwise a deadline can be set
129
+ // a year ahead and the deposited ETH cannot be returned before readyBy or after t1 - a whole year. 72 hours
130
+ // (3 days) is the owner's decision; the usual windows (4 h + 1 h on a buy, 2 h + 1 h on a sell) fit with margin.
118
131
  maxDeadlineSec: 259200,
119
- // readyLeadWarnSec - за сколько секунд до границы отметки готовности сторона обязана предупредить человека.
120
- // У ПРЯМОГО режима это предупреждение на экране, у ОБРАТНОГО - тот же порог у сторожа ноды (у него своя
121
- // настройка RFQ_READY_LEAD_WARN_SEC с тем же значением по умолчанию, потому что нода запускается отдельно
122
- // и чужой конфиг не читает). Значение читается отсюда, а не повторяется числом в коде: два числа в двух
123
- // местах разошлись бы молча, и тревога приходила бы не тогда, когда обещано.
132
+ // readyLeadWarnSec - how many seconds before the readiness boundary a side must warn the person. In DIRECT mode
133
+ // an on-screen warning, in REVERSE the same threshold at the node's watchtower (its own RFQ_READY_LEAD_WARN_SEC
134
+ // with the same default, since the node runs separately and reads no other config). The value is read here, not
135
+ // repeated as a number in the code: two numbers in two places would diverge silently and the alarm would be late.
124
136
  readyLeadWarnSec: 600,
125
137
  };
126
138
 
127
- // ОБЪЯВЛЕННЫЕ МАРШРУТЫ DEX-НОГИ - РЕШЕНИЕ ХОЗЯИНА, А НЕ РЕЗУЛЬТАТ ПЕРЕБОРА В РАНТАЙМЕ (док 45).
128
- // Правило хозяина (2026-09-23): "обычно через USDT или USDC выбирать и всё, если нет прямого".
129
- // В обмене НЕТ ни поиска ступени, ни сравнения "напрямую / через хаб", ни выбора победителя по выходу:
130
- // страница котирует ТОЛЬКО тот путь, который объявлен здесь, и либо показывает его числа, либо честно
131
- // говорит, что по объявленному маршруту котировки нет (и почему). Маршрут выбирает человек.
132
- // Запись: steps - шаги по порядку, каждый { to, fee, pool, verified }. Первый токен пути - сам актив.
133
- // to - токен, в который ведёт шаг (символ из токенов этой сети либо обёрнутый натив этой сети);
134
- // fee - ступень комиссии ЭТОГО шага;
135
- // pool - адрес пула этого шага (прочитан у factory.getPool и подтверждён замером);
136
- // verified - дата и блок, на которых шаг подтверждён живой котировкой (tools/check-uniswap-quote.mjs).
137
- // Пустой steps + why - маршрут НЕ объявлен: это честное "маршрута нет", и по нему подпись блокируется.
138
- // Числа в комментариях - из замера 2026-09-23: выход на 100 единиц, импант по профилю 100->1000, газ.
139
+ // THE DECLARED DEX-LEG ROUTES - THE OWNER'S DECISION, NOT A RUNTIME SEARCH. Owner's rule: "usually choose via USDT
140
+ // or USDC if there is no direct one". There is NO fee-tier search, no "direct / via a hub" comparison, no winner
141
+ // picked by output: the page quotes ONLY the path declared here and either shows its numbers or honestly says there
142
+ // is no quote (and why). A person chooses the route.
143
+ // Record: steps in order, each { to, fee, pool, verified }. The first token of the path is the asset.
144
+ // to - the token a step leads to (a symbol from this network's tokens or its wrapped native);
145
+ // fee - THIS step's fee tier;
146
+ // pool - this step's pool address (read from factory.getPool, confirmed by measurement);
147
+ // verified - the date and block at which the step was confirmed by a live quote.
148
+ // Empty steps + why - the route is NOT declared: an honest "no route", and signing is blocked on it.
149
+ // The numbers in comments come from the 2026-09-23 measurement: output for 100 units, impact per the 100->1000 profile, gas.
139
150
  export const DEX_ROUTES = {
140
151
  "arbitrum-sepolia": {
141
- // Демо-сеть: объявлен единственный пул с внятной ликвидностью. ЦЕНА В НЁМ ИЗМЕРЕННО НЕГОДНАЯ
142
- // (+1738% к эталону Chainlink), и это оставлено наблюдением, а не приговором: решение - за хозяином.
152
+ // The demo network: one pool with sensible liquidity is declared. THE PRICE IN IT IS MEASURABLY WRONG
153
+ // (+1738% against the Chainlink reference) - left as an observation, not a verdict: the owner decides.
143
154
  USDC: {
144
155
  steps: [{ to: "WETH", fee: 3000, pool: "0x66eeab70ac52459dd74c6ad50d578ef76a441bbf", verified: { at: "2026-09-23", block: 311900041 } }],
145
156
  },
146
157
  },
147
158
  arbitrum: {
148
- // Объявлен хозяином 2026-09-23 по замеру того же дня (блок 508125577).
159
+ // Declared by the owner on 2026-09-23 from the same day's measurement (block 508125577).
149
160
  USDT: {
150
- steps: [{ to: "WETH", fee: 500, pool: "0x641c00a822e8b671738d32a431a4fb6074e5c79d", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036707 WETH / 100 USDT, импант -0.004%, газ 93202
161
+ steps: [{ to: "WETH", fee: 500, pool: "0x641c00a822e8b671738d32a431a4fb6074e5c79d", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036707 WETH / 100 USDT, impact -0.004%, gas 93202
151
162
  },
152
163
  USDC: {
153
- steps: [{ to: "WETH", fee: 100, pool: "0x6f38e884725a116c9c7fbf208e79fe8828a2595f", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036719 WETH / 100 USDC, импант -0.023%, газ 93921
164
+ steps: [{ to: "WETH", fee: 100, pool: "0x6f38e884725a116c9c7fbf208e79fe8828a2595f", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036719 WETH / 100 USDC, impact -0.023%, gas 93921
154
165
  },
155
166
  DAI: {
156
- steps: [{ to: "WETH", fee: 3000, pool: "0xa961f0473da4864c5ed28e00fcc53a3aab056c1b", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036601 WETH / 100 DAI, импант -0.373%, газ 95653
167
+ steps: [{ to: "WETH", fee: 3000, pool: "0xa961f0473da4864c5ed28e00fcc53a3aab056c1b", verified: { at: "2026-09-23", block: 508125577 } }], // 0.036601 WETH / 100 DAI, impact -0.373%, gas 95653
157
168
  },
158
- // USDe против WETH пула не имеет вовсе - объявлен обход через USDC (замер: 99.931621 USDC / 100 USDe).
169
+ // USDe has no pool against WETH at all - a detour via USDC is declared (measurement: 99.931621 USDC / 100 USDe).
159
170
  USDe: {
160
171
  steps: [
161
172
  { to: "USDC", fee: 100, pool: "0x3223859e24d5413947761cb79d3094f21be69bfa", verified: { at: "2026-09-23", block: 508125577 } },
162
173
  { to: "WETH", fee: 100, pool: "0x6f38e884725a116c9c7fbf208e79fe8828a2595f", verified: { at: "2026-09-23", block: 508125577 } },
163
174
  ],
164
175
  },
165
- // USDS И PyUSD ЗДЕСЬ НЕ ОБЪЯВЛЕНЫ ВООБЩЕ (решение хозяина 2026-09-23): на Arbitrum One они больше
166
- // НЕ ПРЕДЛАГАЮТСЯ - это не "маршрута нет", а "этого актива на этой сети нет". Поэтому записей у
167
- // них нет ни одной, в том числе why-заглушки: заглушка значила бы "актив предлагается, пула нет".
168
- // Их нет и в tokens этой сети (см. выше), а на Ethereum оба остаются и работают.
169
- // WETH - не своп, а unwrap у WETH9: 1:1. Газ зависит от держателя и суммы: замер 2026-09-23 дал 52793
170
- // (держатель 0x278d…f8d2, блоки 508121467..508121667) и 52598 на другом держателе - поэтому в
171
- // интерфейсе газ unwrap не показывается числом (см. evm/dex.js: unwrapLeg).
176
+ // USDS AND PyUSD ARE NOT DECLARED HERE AT ALL (the owner's decision): on Arbitrum One they are no longer
177
+ // OFFERED - not "no route" but "this asset is not on this network", so no records and no why-stub. They are
178
+ // absent from this network's tokens too; on Ethereum both stay and work.
179
+ // WETH is not a swap but an unwrap at WETH9: 1:1. Gas depends on holder and amount: the 2026-09-23 measurement
180
+ // gave 52793 (holder 0x278d…f8d2, blocks 508121467..508121667) and 52598 on another holder, so no gas figure is
181
+ // shown in the interface (see evm/dex.js: unwrapLeg).
172
182
  WETH: { steps: [], unwrap: true },
173
183
  },
174
184
  mainnet: {
175
- // Объявлен хозяином 2026-09-23 по замеру того же дня (блок 26040283).
185
+ // Declared by the owner on 2026-09-23 from the same day's measurement (block 26040283).
176
186
  USDT: {
177
- steps: [{ to: "WETH", fee: 500, pool: "0x11b815efb8f581194ae79006d24e0d814b7697f6", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036714 WETH / 100 USDT, импант -0.002%, газ 81801
187
+ steps: [{ to: "WETH", fee: 500, pool: "0x11b815efb8f581194ae79006d24e0d814b7697f6", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036714 WETH / 100 USDT, impact -0.002%, gas 81801
178
188
  },
179
189
  USDC: {
180
- steps: [{ to: "WETH", fee: 100, pool: "0xe0554a476a092703abdb3ef35c80e0d76d32939f", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036717 WETH / 100 USDC, импант -0.002%, газ 87061
190
+ steps: [{ to: "WETH", fee: 100, pool: "0xe0554a476a092703abdb3ef35c80e0d76d32939f", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036717 WETH / 100 USDC, impact -0.002%, gas 87061
181
191
  },
182
192
  DAI: {
183
- steps: [{ to: "WETH", fee: 500, pool: "0x60594a405d53811d3bc4766596efd80fd545a270", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036707 WETH / 100 DAI, импант -0.006%, газ 79908
193
+ steps: [{ to: "WETH", fee: 500, pool: "0x60594a405d53811d3bc4766596efd80fd545a270", verified: { at: "2026-09-23", block: 26040283 } }], // 0.036707 WETH / 100 DAI, impact -0.006%, gas 79908
184
194
  },
185
- // USDe: прямого пула против WETH нет - объявлен обход через USDC (замер: 99.975800 USDC / 100 USDe).
195
+ // USDe: no direct pool against WETH - a detour via USDC is declared (measurement: 99.975800 USDC / 100 USDe).
186
196
  USDe: {
187
197
  steps: [
188
198
  { to: "USDC", fee: 100, pool: "0xe6d7ebb9f1a9519dc06d557e03c522d53520e76a", verified: { at: "2026-09-23", block: 26040283 } },
189
199
  { to: "WETH", fee: 100, pool: "0xe0554a476a092703abdb3ef35c80e0d76d32939f", verified: { at: "2026-09-23", block: 26040283 } },
190
200
  ],
191
201
  },
192
- // USDS: прямой пул против WETH существует, но он заведомо непригоден (0.000867 WETH на 100 USDS),
193
- // поэтому объявлен обход через USDC (замер: 99.640880 USDC / 100 USDS на ступени 0.3%).
202
+ // USDS: a direct pool against WETH exists but is plainly unusable (0.000867 WETH for 100 USDS), so a detour
203
+ // via USDC is declared (measurement: 99.640880 USDC / 100 USDS at the 0.3% tier).
194
204
  USDS: {
195
205
  steps: [
196
206
  { to: "USDC", fee: 3000, pool: "0xa66a2770bc0e0c65b63b5a3bb4560e90f95d6146", verified: { at: "2026-09-23", block: 26040283 } },
197
207
  { to: "WETH", fee: 100, pool: "0xe0554a476a092703abdb3ef35c80e0d76d32939f", verified: { at: "2026-09-23", block: 26040283 } },
198
208
  ],
199
209
  },
200
- // PyUSD: прямой пул против WETH заведомо непригоден - объявлен обход через USDC (99.989571 USDC / 100 PyUSD).
210
+ // PyUSD: a direct pool against WETH is plainly unusable - a detour via USDC is declared (99.989571 USDC / 100 PyUSD).
201
211
  PyUSD: {
202
212
  steps: [
203
213
  { to: "USDC", fee: 100, pool: "0x13394005c1012e708fce1eb974f1130fdc73a5ce", verified: { at: "2026-09-23", block: 26040283 } },
204
214
  { to: "WETH", fee: 100, pool: "0xe0554a476a092703abdb3ef35c80e0d76d32939f", verified: { at: "2026-09-23", block: 26040283 } },
205
215
  ],
206
216
  },
207
- // WETH - не своп, а unwrap у WETH9: 1:1. Газ зависит от держателя: замер 2026-09-23 дал 36335 и 36277
208
- // у двух разных держателей, поэтому числом в интерфейсе он не показывается.
217
+ // WETH is not a swap but an unwrap at WETH9: 1:1. Gas depends on the holder: the 2026-09-23 measurement gave
218
+ // 36335 and 36277 on two holders, so no number is shown in the interface.
209
219
  WETH: { steps: [], unwrap: true },
210
220
  },
211
221
  };
212
222
 
223
+ // ASYNCHRONOUS ROUTE PROVIDERS - A NETWORK IS SERVED OR IT IS NOT, AND THAT IS DATA, NOT A GUESS.
224
+ //
225
+ // The declared routes above are ROUTER swaps: the swap runs INSIDE the person's own transaction (shape "sync").
226
+ // A person coming from USDC who holds NO native coin of their own cannot pay for that transaction at all - there
227
+ // is nothing to pay the gas with. So a second kind of route is declared here: an INTENT AUCTION, where the person
228
+ // SIGNS an intent and the settlement arrives LATER as a separate transaction sent by someone else (shape "async";
229
+ // CoWSwap is the first such provider, see the step-1 package layer sdk/src/legs/cow.mjs). The wallet's own
230
+ // balance then decides the path (www/js/evm/permit.js: usdcWithoutEthVerdict) and the asynchronous one is taken
231
+ // when the wallet cannot cover the gas - the swap settles first, and the escrow deposit (and its gas) comes after.
232
+ // STEP 4 ADDS A SECOND ONE (kyberswap, below): an intent auction is a KIND, not a single venue, so the table is keyed
233
+ // by provider id and each provider declares its own networks and book. CoW is unchanged.
234
+ // STEP 5 ADDS WHAT A PROVIDER SETTLES (`settles`): the escrow is funded with NATIVE coin, so the choice must only
235
+ // pick a provider able to carry the route ALL THE WAY there. CoW settles native; KyberSwap settles the WRAPPED
236
+ // native, so it is a LIQUIDITY leg and is usable only inside a composition (see www/js/evm/asyncRoute.js:
237
+ // asyncRouteChoice) - never as the sole provider for a native need.
238
+ //
239
+ // KEYED BY EVM chainId. A network ABSENT here is NOT served asynchronously: the path choice must refuse BY NAME
240
+ // (www/js/evm/asyncRoute.js: asyncRouteVerdict, reason "no-async-provider") rather than pretend. The order book
241
+ // base URLs are the provider's own public endpoints (the CoWSwap order book OpenAPI `servers` list), the same
242
+ // ones pinned in sdk/src/legs/cow.mjs; tools/check-buy-async-route.mjs cross-checks the two tables so the engine
243
+ // and the package cannot drift apart silently.
244
+ export const ASYNC_ROUTE_PROVIDERS = {
245
+ cowswap: {
246
+ venue: "CoWSwap",
247
+ shape: "async",
248
+ // WHAT IT SETTLES: native coin. A CoW order can buy native directly (buyToken = the BUY_ETH_ADDRESS marker,
249
+ // sdk/src/legs/cow-spec.mjs), so this provider can carry the route ALL THE WAY to the escrow, which is funded
250
+ // with native coin. tools/check-buy-async-route.mjs cross-checks this against cowProvider.settles.
251
+ settles: "native",
252
+ networks: {
253
+ 1: { slug: "mainnet", orderbook: "https://api.cow.fi/mainnet" },
254
+ 100: { slug: "xdai", orderbook: "https://api.cow.fi/xdai" },
255
+ 42161: { slug: "arbitrum_one", orderbook: "https://api.cow.fi/arbitrum_one" },
256
+ 8453: { slug: "base", orderbook: "https://api.cow.fi/base" },
257
+ 56: { slug: "bnb", orderbook: "https://api.cow.fi/bnb" },
258
+ 11155111: { slug: "sepolia", orderbook: "https://api.cow.fi/sepolia" },
259
+ },
260
+ },
261
+ // KYBERSWAP LIMIT ORDER - THE SECOND SUCH PROVIDER (step 4). Same shape as CoW (an intent auction: the person signs
262
+ // an off-chain order, a taker settles it on chain later), so it is declared HERE the same way. ONE order book base
263
+ // URL serves every chain (docs.kyberswap.com "Base URL: https://limit-order.kyberswap.com"), and the settlement
264
+ // contract is the same DSLOProtocol 0xcab2... on each chain (docs.kyberswap.com, "Contracts & Addresses"); there is
265
+ // no per-chain slug, so the record carries `contract` instead of `slug`. The networks are the chains KyberSwap
266
+ // Limit Order serves (Ethereum, Optimism, BSC, Polygon, Fantom, zkSync, Mantle, Base, Arbitrum, Avalanche, Linea,
267
+ // Scroll, Blast). SCOPE: this provider settles WETH, not native coin - it is the LIQUIDITY leg, see the scope note
268
+ // in sdk/src/legs/kyber.mjs; reaching native is a second order on a native-capable provider (CoW above). The guard
269
+ // tools/check-buy-async-route.mjs cross-checks this table against sdk/src/legs/kyber.mjs so the two cannot drift.
270
+ kyberswap: {
271
+ venue: "KyberSwap Limit Order",
272
+ shape: "async",
273
+ // WHAT IT SETTLES: the WRAPPED native (WETH), not native coin - its orders trade ERC20 against ERC20 and its
274
+ // API refuses native as an order asset (error 4004). So it is a LIQUIDITY leg: it can never be the sole provider
275
+ // for a native need; reaching native is a SECOND order on a native-capable provider (CoW above). See the scope
276
+ // note in sdk/src/legs/kyber.mjs.
277
+ settles: "wrapped",
278
+ networks: {
279
+ 1: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
280
+ 10: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
281
+ 56: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
282
+ 137: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
283
+ 250: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
284
+ 324: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
285
+ 5000: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
286
+ 8453: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
287
+ 42161: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
288
+ 43114: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
289
+ 59144: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
290
+ 534352: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
291
+ 81457: { orderbook: "https://limit-order.kyberswap.com", contract: "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C" },
292
+ },
293
+ },
294
+ // UNISWAPX - THE THIRD SUCH PROVIDER (step 6). Same shape as CoW (an intent auction: the swapper signs an EIP-712
295
+ // order and fillers settle it on chain LATER), and the SAME native capability: an UniswapX output token may be the
296
+ // protocol's native sentinel address(0) (sdk/src/legs/uniswapx-spec.mjs), so this provider can carry a route ALL
297
+ // THE WAY to native - a SECOND native leg beside CoW. Declared for the chains whose reactor takes the V2 Dutch
298
+ // order the package builds (Ethereum mainnet, V2DutchOrderReactor); other chains run the Dutch V3 reactor, whose
299
+ // order is a different type the package does not build. The order service base URL is the UniswapX orders API
300
+ // (shared by every chain); there is no per-chain slug, so the record carries `reactor` instead.
301
+ // tools/check-buy-async-route.mjs cross-checks this table against sdk/src/legs/uniswapx.mjs so the two cannot drift.
302
+ // `submitter` IS OUR OWN ENDPOINT, NOT A THIRD PARTY. Publishing a signed order needs the Uniswap Trading API key,
303
+ // and the key lives ONLY in the backend's environment (app/, UNISWAPX_API_KEY). The page posts the signed order to
304
+ // this SAME-ORIGIN path - so no CSP change, and no key anywhere in the browser - and the backend attaches the API
305
+ // key server-side. The provider takes the submitter from configuration and refuses by name
306
+ // ("uniswapx-no-submitter") when it is absent. tools/check-uniswapx-submit.mjs holds this entry against the package.
307
+ uniswapx: {
308
+ venue: "UniswapX",
309
+ shape: "async",
310
+ // WHAT IT SETTLES: native coin - an output may be the NATIVE sentinel address(0), so it can be a native leg or,
311
+ // on its own, carry a native need all the way. See the note in sdk/src/legs/uniswapx.mjs (publishing needs the
312
+ // Uniswap Trading API and an API key, and the order is cosigned by Uniswap Labs).
313
+ settles: "native",
314
+ networks: {
315
+ 1: { orderbook: "https://api.uniswap.org/v2", reactor: "0x00000011F84B9aa48e5f8aA8B9897600006289Be", submitter: "/api/uniswapx/order" },
316
+ },
317
+ },
318
+ // UNISWAP RELAY - the fourth such provider (step 7), and the one that needs no key. These are Uniswap's own
319
+ // contracts (Uniswap/relayer): RelayOrderReactor takes a signed order and relays one call into the
320
+ // UniversalRouter; the relayer pays the gas and is repaid by the order's fee in the same input token.
321
+ // There is no order book at all: the order is a self-contained Permit2 instruction, and anyone holding the
322
+ // signature may call the reactor. WHAT THIS MEANS IN PRACTICE: Uniswap deploys this reactor on Ethereum
323
+ // mainnet ONLY (Uniswap/relayer README, "Deployment Addresses": 0x0000000000A4e21E2597DCac987455c48b12edBF
324
+ // together with UniversalRouter 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD); no Uniswap source places it on
325
+ // Arbitrum; and HOW a signed order reaches a relayer is NOT DESCRIBED in Uniswap's repository (the
326
+ // "Integrating as a filler" section is empty and the UniswapX order service has no relay order type). The
327
+ // entry therefore carries the reactor and the router instead of a book, and the delivery path is refused in
328
+ // the package by name (uniswap-relay-no-discovery) rather than by an invented address.
329
+ // tools/check-buy-async-route.mjs and tools/check-async-route-relay.mjs cross-check this table against
330
+ // sdk/src/legs/uniswaprelay.mjs (reactor and router), so a repeated truth cannot drift silently.
331
+ uniswaprelay: {
332
+ venue: "Uniswap Relay",
333
+ shape: "async",
334
+ // WHAT IT SETTLES: the native coin - a relayed swap unwraps the wrapped native to the recipient, and the
335
+ // escrow deposit is paid in native, so this provider carries a native need to the END (the native leg of a
336
+ // composition, or a single route). See the scope note in sdk/src/legs/uniswaprelay.mjs.
337
+ settles: "native",
338
+ networks: {
339
+ 1: { reactor: "0x0000000000A4e21E2597DCac987455c48b12edBF", universalRouter: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD" },
340
+ },
341
+ },
342
+ };
343
+
213
344
  export const CHAINS = [
214
345
  {
215
346
  id: "arbitrum-sepolia",
@@ -219,123 +350,111 @@ export const CHAINS = [
219
350
  caip2: "eip155:421614",
220
351
  testnet: true,
221
352
  native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
222
- // Свой прокси, а не публичный узел напрямую: адрес эскроу (predict) приходит по этому пути, и
223
- // подменённый или совравший посредник означал бы блокировку ETH не на тот адрес. Подробности,
224
- // включая проверку сети и CORS, - в tools/check-evm-proxy.mjs и .hermes/deploy/arrakis-evm-*.conf
225
- rpcUrl: "https://rpc.arrakisswap.trade",
226
- // Фид Chainlink для цены нейтива. Адреса проверены ВЫЗОВОМ в сети (description() совпало
227
- // с парой, значение свежее и в разумном коридоре) - см. tools/check-prices.mjs. Если фида
228
- // нет, цены нет: интерфейс обязан сказать «демо», а не показывать вчерашнюю константу.
353
+ // Our own proxy rather than a public node directly: the escrow address (predict) comes along this path, and a
354
+ // substituted or lying intermediary would mean locking the ETH against the wrong address. Details, including a
355
+ // network check and CORS, are in the deploy notes.
356
+ rpcUrl: ENDPOINTS.evmRpc,
357
+ // The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
358
+ // the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
359
+ // say "demo", not show yesterday's constant.
229
360
  priceFeed: { address: "0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
230
361
  explorerTx: "https://sepolia.arbiscan.io/tx/",
231
362
  explorerAddr: "https://sepolia.arbiscan.io/address/",
232
363
  explorerName: "arbiscan (sepolia)",
233
364
  gasUsd: 0.01,
234
365
  routeType: "atomic",
235
- // ОСНОВНАЯ сеть демки: сюда поехал контракт эскроу, и здесь РАЗВЁРНУТ РОУТЕР КОМИССИИ - он же
236
- // фабрика (порождает эскроу, хранит ставку, держит кассу комиссии). Адрес ниже - факт, а не
237
- // намерение: 0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8 (высота 314323871,
238
- // транзакция 0x7a88c0249867b76b11c6cb678e6092fdc19404b51540a64c5fd03f5a32f74826, код 8223 байта,
239
- // keccak256 0x90f26f641b48a2a630463195d4429b0818d29187563e09711a40811fcf08a0ba, развёрнут 30.09.2026): добавлен РАННИЙ ВОЗВРАТ в мёртвой зоне - если отметки нет,
240
- // refund открыт сразу после readyBy, и он же публикует половину внёсшего. Код совпадает с текущей
241
- // сборкой репозитория байт в байт, то есть ранний возврат В СЕТИ (проверено
366
+ // THE DEMO'S MAIN NETWORK: the escrow went here, and the FEE ROUTER IS DEPLOYED here - also the factory (it
367
+ // spawns escrows, keeps the rate, holds the fee cashier). The address is a FACT, not an intention:
368
+ // 0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8 (height 314323871, transaction
369
+ // 0x7a88c0249867b76b11c6cb678e6092fdc19404b51540a64c5fd03f5a32f74826, code 8223 bytes, keccak256
370
+ // 0x90f26f641b48a2a630463195d4429b0818d29187563e09711a40811fcf08a0ba); it added EARLY REFUND in the dead
371
+ // zone - with no mark, refund is open right after readyBy and it publishes the depositor's half. The code
372
+ // matches the current repo build byte for byte.
242
373
  // `npm run check:deployed -- --address=0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8`).
243
- // ПРЕЖНЯЯ (потолок 3%, раннего возврата ещё нет): 0xEa619bDC78989627093B7830990367B2ECE52CE5
244
- // (высота 314309038, транзакция 0x07ddb118c16639785d0055cfb5e28457f33f0c1e8323ea37d421c8abdfcec767, код 8143 байта).
245
- // ПРЕЖНЯЯ боевая (роутер с потолком 10%): 0x9f856cd9c14fd9493961473b804039b09a4012ef (высота
246
- // 313597131, код 8723 байта, keccak 0xe35b9efa1466fc9e57ffc61fd6243019c55990ea4a04de03bbfa3ec477e49427).
247
- // ПРЕЖНЯЯ до неё (фабрика без комиссии): 0xcb54af9ec11344d448c7900ccb08530a23783259 (высота
248
- // 312222006, код 6035 байт); ещё ПРЕЖНИЕ: 0x0ec0be02…4222e (высота 312180386, 5989 байт),
249
- // 0x13556d78…aa8b8 и 0x4E7270…dbe1b0, см. .hermes/deploy.
250
- // mode: "live" - форма вызывает НАСТОЯЩИЕ транзакции: создание ордера ВМЕСТЕ С ДЕНЬГАМИ одной
251
- // транзакцией (createOrderAndFund; отдельной функции внесения у эскроу нет вовсе - ордер рождается
252
- // зафондированным, см. FACTORY_METHODS в www/js/evm/factory.js и ESCROW_METHODS в escrow.js).
253
- // Прежних lock(sealedHalf) и hashlock в контракте нет: половина не посылается, а раскрывается
254
- // предъявлением.
255
- // Возвращается в "simulated" одной правкой, если нужно показать демо без движения средств.
256
- // cashier (#103): касса комиссии ИНТЕРФЕЙСА, которую он называет в котировках. Контракты в сети ещё
257
- // ПРЕЖНИЕ (док 55), поэтому кассы там нет: поле заполняется тем же заходом, что и адрес фабрики.
374
+ // PREVIOUS (ceiling 3%, no early refund): 0xEa619bDC78989627093B7830990367B2ECE52CE5 (height 314309038,
375
+ // code 8143 bytes). PREVIOUS live (router with a 10% ceiling): 0x9f856cd9c14fd9493961473b804039b09a4012ef
376
+ // (height 313597131, code 8723 bytes). PREVIOUS before it (factory without a fee):
377
+ // 0xcb54af9ec11344d448c7900ccb08530a23783259 (height 312222006, code 6035 bytes), and earlier ones still.
378
+ // mode: "live" - the form sends REAL transactions: order creation TOGETHER WITH THE MONEY in one transaction
379
+ // (createOrderAndFund; the escrow has no separate funding function - an order is born funded, see FACTORY_METHODS
380
+ // in www/js/evm/factory.js and ESCROW_METHODS in escrow.js). The old lock(sealedHalf) and hashlock are gone: the
381
+ // half is not sent but revealed by presentation. Switching back to "simulated" is one edit, to show a demo with
382
+ // no money movement. cashier: the INTERFACE's fee cashier, named in quotes. The contracts on chain are still the
383
+ // previous ones, so there is no cashier there: the field is filled in the same pass as the factory address.
258
384
  escrow: { address: "0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8", mode: "live", cashier: null },
259
385
  tokens: [
260
- // Тестовый USDC (Circle): decimals 6 и symbol "USDC" проверены в сети этой же нодой.
386
+ // Test USDC (Circle): decimals 6 and symbol "USDC" verified on chain by the same node.
261
387
  { symbol: "USDC", address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", decimals: 6, usd: 1, cls: "ar-token-usdc" },
262
- // WETH - ЭТО ТОТ ЖЕ ETH, ТОЛЬКО ОБЁРНУТЫЙ, поэтому пула для него не ищем: WETH -> ETH это
263
- // withdraw у самого WETH9, один к одному и без свопа (см. DEX-ногу ниже и tools/check-uniswap-quote.mjs).
264
- // hidden: true - "в витрине сети (в шапке) не показываем", а НЕ "не поддерживается": в списке
265
- // токенов на обмене он есть (список на обмене берёт tokensOf, а шапка фильтрует по hidden).
266
- // Адрес определён ИЗМЕРЕНИЕМ, а не по памяти: token0/token1 пула USDC/WETH дали этот адрес, а
267
- // symbol()/decimals()/name() ответили "WETH"/18/"Wrapped Ether" (проверка 2026-09-23).
268
- // peg: "native" - своей демо-цены у WETH нет: его долларовая цена - это цена ETH.
388
+ // WETH IS THE SAME ETH, JUST WRAPPED, so no pool is searched for it: WETH -> ETH is a withdraw at WETH9, one
389
+ // to one and without a swap (see the DEX leg below). hidden: true means "do not show in the network showcase
390
+ // (header)", NOT "unsupported": it is in the swap token list (that uses tokensOf; the header filters by hidden).
391
+ // The address is DETERMINED BY MEASUREMENT, not from memory: token0/token1 of the USDC/WETH pool gave it, and
392
+ // symbol()/decimals()/name() answered "WETH"/18/"Wrapped Ether" (checked 2026-09-23).
393
+ // peg: "native" - WETH has no demo price of its own: its dollar price is the ETH price.
269
394
  { symbol: "WETH", address: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
270
395
  ],
271
- // DEX-НОГА ЭТОЙ СЕТИ (Uniswap v3, только чтение: eth_call к QuoterV2).
272
- // ФАБРИКИ В ОФИЦИАЛЬНОМ СПИСКЕ ДЛЯ ARBITRUM SEPOLIA НЕТ, поэтому адрес ПОЛУЧЕН ИЗМЕРЕНИЕМ:
273
- // у пулов, названных в .hermes/docs/43-uniswap-same-chain-ramp.md, прочитан factory(), а затем
274
- // сверено, что getPool(USDC, WETH, fee) у него возвращает те же адреса (2026-09-23).
275
- // ВАЖНО ПРО ЭТУ СЕТЬ: пулы в ней ЕСТЬ, но ликвидность негодная. Ступень 0.01% НЕ ОБСЛУЖИВАЕТ НИ
276
- // ОДНОГО размера ("pool cannot serve this size" - вокруг цены ликвидности нет), у 0.05% выход НОЛЬ
277
- // на любом размере, а 0.3% отвечает, но ценой, которая с эталоном Chainlink расходится.
278
- // ОДНО ЧИСЛО, А НЕ ТРИ РАЗНЫХ. До 24 сентября 2026 здесь стояли "в 30 раз" и "в 18 раз", а в
279
- // докладе 43 - 94.54%: это замеры НА РАЗНЫХ РАЗМЕРАХ В РАЗНЫЕ ДНИ, и без названного размера и
280
- // способа каждое из них - просто другое число. Замер 2026-09-24, блок 312238363, головной размер
281
- // 100 USDC: котировка дала 0.116508 WETH, то есть неявная цена натива $858.31 против $2650.42 по
282
- // фиду Chainlink - расхождение 67.6% при пороге 2%, пул дешевле эталона в 3.09 раза.
283
- // СПОСОБ ЗАМЕРА - ТОТ ЖЕ КОД, ЧТО В ИНТЕРФЕЙСЕ (www/js/evm/dex.js: priceGateVerdict, эталон из
284
- // www/js/evm/prices.js), и он перезапускаем одной командой:
285
- // node tools/check-uniswap-quote.mjs --chain=arbitrum-sepolia
286
- // ЧИСЛО ЗАВИСИТ ОТ РАЗМЕРА (тот же блок, та же шкала): 1 USDC = 77.5%, 10 USDC = 76.6%,
287
- // 100 USDC = 67.6%, 1000 USDC = -16.0%. Поэтому здесь стоит ОДНО число С НАЗВАННЫМ РАЗМЕРОМ.
288
- // Инструмент печатает ещё и наблюдение в шкале "во сколько раз дешевле" (+208.6% для 100 USDC,
289
- // то есть те же 3.09 раза) - это ПЕРЕСЧЁТ той же величины, а не второй замер.
396
+ // THIS NETWORK'S DEX LEG (Uniswap v3, read only: eth_call to QuoterV2). THE FACTORY IS NOT IN THE OFFICIAL LIST
397
+ // FOR ARBITRUM SEPOLIA, so the address was OBTAINED BY MEASUREMENT: factory() was read from the pools named in
398
+ // the same-chain ramp notes, and getPool(USDC, WETH, fee) was then checked to return the same addresses
399
+ // (2026-09-23). IMPORTANT ABOUT THIS NETWORK: pools exist but liquidity is unusable. The 0.01% tier serves NO
400
+ // size ("pool cannot serve this size"), 0.05% gives ZERO output at any size, and 0.3% answers at a price that
401
+ // diverges from the Chainlink reference.
402
+ // ONE NUMBER, NOT THREE. It used to read "30x" and "18x" here while the report said 94.54%: measurements AT
403
+ // DIFFERENT SIZES ON DIFFERENT DAYS, each meaningless without a named size and method. Measurement 2026-09-24,
404
+ // block 312238363, head size 100 USDC: the quote gave 0.116508 WETH, i.e. an implied native price of $858.31
405
+ // against $2650.42 by the Chainlink feed - a 67.6% divergence against a 2% threshold, the pool 3.09x cheaper
406
+ // than the reference. The method is THE SAME CODE AS IN THE INTERFACE (www/js/evm/dex.js: priceGateVerdict,
407
+ // reference from www/js/evm/prices.js) and is rerunnable in one command.
408
+ // (rerun the DEX quote check against arbitrum-sepolia)
409
+ // THE NUMBER DEPENDS ON SIZE (same block, same scale): 1 USDC = 77.5%, 10 USDC = 76.6%, 100 USDC = 67.6%,
410
+ // 1000 USDC = -16.0%. So ONE number stands here WITH A NAMED SIZE. The tool also prints a "how many times
411
+ // cheaper" observation (+208.6% for 100 USDC, i.e. the same 3.09x) - a RECOUNT of the same figure, not a second measurement.
290
412
  uniswap: {
291
413
  factory: "0x248ab79bbb9bc29bb72f7cd42f17e054fc40188e",
292
414
  quoter: "0x2779a0CC1c3e0E44D2542EC3e79e3864Ae93Ef0B",
293
- // ИСПОЛНЕНИЕ (в отличие от котировки строкой выше): адреса ЗАМЕРЕНЫ, а не взяты из соглашения о
294
- // развёртывании, и замер перепроверяется прогоном tools/check-dex-exec.mjs.
295
- // router - SwapRouter02. Три НЕЗАВИСИМЫХ признака происхождения, все из цепи: (1) код по адресу
296
- // есть, 24497 байт; (2) его factory() РАВЕН фабрике Uniswap строкой выше; (3) его WETH9()
297
- // РАВЕН wrapped ниже. Плюс исходник верифицирован у обозревателя: Arbiscan API V2 (chainid
298
- // 421614) отдал ABI из 41 записи, имя контракта SwapRouter02, компилятор v0.7.6+commit.7338295f.
299
- // Угадать адрес, который вернёт ИМЕННО эти factory() и WETH9(), нельзя - это тот же контракт.
300
- // ВАЖНО: permit2() у ЭТОГО роутера ОТКАТЫВАЕТ (проверяется прогоном): он не потребляет
301
- // Permit2, и разрешение даётся ему самому обычным approve.
302
- // permit2 - канонический Permit2 Uniswap: код 9152 байта, и его DOMAIN_SEPARATOR() совпал с
303
- // посчитанным С НУЛЯ для chainId 421614 и этого адреса (имя домена "Permit2"), а исходник
304
- // верифицирован у обозревателя. Нужен пути ФИНАНСИРОВАНИЯ ордера, а НЕ этому роутеру.
305
- // UniversalRouter (0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2) СЮДА НЕ ВНЕСЁН - и это решение, а не
306
- // забывчивость: его происхождение по этой сети НЕ подтверждается (getabi и getsourcecode у
307
- // обозревателя отвечают NOTOK, то есть исходник не верифицирован), а "тот же адрес, что в докладе
308
- // 43" доказательством не является. Появится нужда - сначала доказательство, потом запись.
415
+ // EXECUTION (unlike the quote above): the addresses are MEASURED, not taken from a deployment agreement, and
416
+ // the measurement is rechecked by a run.
417
+ // router - SwapRouter02. Three INDEPENDENT signs of origin, all from the chain: (1) code at the address,
418
+ // 24497 bytes; (2) its factory() EQUALS the Uniswap factory above; (3) its WETH9() EQUALS wrapped below.
419
+ // Plus the source is verified at the explorer: Arbiscan API V2 (chainid 421614) returned an ABI of 41
420
+ // entries, contract name SwapRouter02, compiler v0.7.6+commit.7338295f. An address returning EXACTLY these
421
+ // factory() and WETH9() cannot be guessed - it is the same contract.
422
+ // IMPORTANT: permit2() on THIS router REVERTS (checked by a run): it does not consume Permit2, and the
423
+ // allowance is given to it by an ordinary approve.
424
+ // permit2 - the canonical Uniswap Permit2: code 9152 bytes, its DOMAIN_SEPARATOR() matched one computed FROM
425
+ // SCRATCH for chainId 421614 and this address (domain name "Permit2"), and the source is verified at the
426
+ // explorer. Needed by the order's FUNDING path, NOT by this router.
427
+ // UniversalRouter (0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2) is NOT included, on purpose: its origin on
428
+ // this network is NOT confirmed (getabi and getsourcecode at the explorer answer NOTOK, i.e. the source is
429
+ // unverified), and "the same address as in the report" is not proof. When needed - proof first, then the record.
309
430
  router: "0x101F443B4d1b059569D643917553c771E1b9663E",
310
431
  permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
311
- // Обёрнутый нативный токен: адрес совпадает с WETH в tokens выше - это проверяется (check-front).
432
+ // The wrapped native token: the address matches WETH in tokens above - that is checked (check-front).
312
433
  wrapped: { symbol: "WETH", name: "Wrapped Ether", address: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", decimals: 18 },
313
434
  feeTiers: [100, 500, 3000, 10000],
314
435
  },
315
- // ЦЕНА ЭТОЙ СЕТИ НЕГОДНАЯ ПО ПРИРОДЕ, И ЭТО ПРИЗНАК СЕТИ (по образцу hidden/unsupported), а не
316
- // ослабление проверки: тот же замер, что в блоке uniswap выше (блок 312238363, 100 USDC: неявная
317
- // цена натива $858.31 против эталона $2650.42 - расхождение 67.6% при пороге 2%). На боевой сети такая цена
318
- // ОСТАНАВЛИВАЕТ подпись, здесь - только помечается словами в интерфейсе: на тестнете проверяется
319
- // ПОТОК, а не рынок, иначе демо-прогоны встали бы на цене, которую мы не выбирали.
320
- // Признака нет у боевых сетей - там проверка цены блокирует, и это умолчание (DEX_PRICE_GATE).
321
- // Песочница local-anvil в проверку не попадает вовсе: у неё нет ни блока uniswap, ни токенов, то
322
- // есть DEX-ноги там не бывает - помечать нечего.
436
+ // THIS NETWORK'S PRICE IS WRONG BY NATURE, AND THAT IS A NETWORK MARK (like hidden/unsupported), not a
437
+ // weakening of the check: the same measurement as in the uniswap block above (block 312238363, 100 USDC: implied
438
+ // native price $858.31 against a $2650.42 reference - a 67.6% divergence against a 2% threshold). On a live
439
+ // network such a price STOPS signing; here it is only marked in words: a testnet tests the FLOW, not the market,
440
+ // else demo runs would stall on a price we did not choose. Live networks have no mark - there the price check
441
+ // blocks, and that is the default (DEX_PRICE_GATE). The local-anvil sandbox is not covered at all: it has no
442
+ // uniswap block and no tokens, so there is no DEX leg there - nothing to mark.
323
443
  dexPriceGate: "tolerated",
324
444
  },
325
445
  {
326
- // ЛОКАЛЬНАЯ СЕТЬ ДЛЯ ВНУТРЕННИХ ТЕСТОВ. Это anvil на этой же машине: настоящая цепочка, настоящие
327
- // транзакции, но песочница. В демо она не для показа - она для прогонов, где нужен полный контроль.
328
- // СВОЙ chainId (31337), а не как у публичной сети. С совпадающим идентификатором фронт не мог однозначно
329
- // выбрать песочницу: при сверке с кошельком он находил ПЕРВУЮ сеть с таким id и возвращался к публичной.
330
- // 31337 - обычный идентификатор локальных цепочек, ни с одной настоящей сетью не совпадает.
446
+ // A LOCAL NETWORK FOR INTERNAL TESTS. This is anvil on this machine: a real chain with real transactions, but a
447
+ // sandbox. In the demo it is not for showing - it is for runs needing full control. Its OWN chainId (31337), not
448
+ // one of a public network: with a matching id the front end could not uniquely pick the sandbox - it found the
449
+ // FIRST network with that id and fell back to the public one. 31337 is the usual local-chain id, matching no real network.
331
450
  id: "local-anvil",
332
451
  name: "Local (anvil)",
333
- // СКРЫТА ИЗ СПИСКА СЕТЕЙ, НО НЕ ВЫКЛЮЧЕНА. hidden - это только про состав строк, которые рисует
334
- // список (см. shownChains в www/js/ui/chainPicker.js): в демо песочницу показывать нечего, а для
335
- // внутренних прогонов она нужна. Признак НЕ означает "не поддерживается" (это unsupported у
336
- // Monad/Hyperliquid - те остаются в списке, просто серые и подписаны тем же "coming soon", что и остальные неживые сети):
337
- // escrowState, isSelectable и settlementChain читают сам реестр и про hidden не знают, поэтому
338
- // песочница остаётся живой сетью с живым эскроу и выбирается по идентификатору "local-anvil".
452
+ // HIDDEN FROM THE NETWORK LIST BUT NOT OFF. hidden concerns only the rows the list draws
453
+ // (see shownChains in www/js/ui/chainPicker.js): the sandbox is not for showing, but internal runs need it. The
454
+ // mark does NOT mean "unsupported" (that is unsupported on Monad/Hyperliquid - those stay listed, grey, marked
455
+ // "coming soon" like the other non-live networks): escrowState, isSelectable and settlementChain read the
456
+ // registry itself and do not know about hidden, so the sandbox stays a live network with a live escrow, selected
457
+ // by the id "local-anvil".
339
458
  hidden: true,
340
459
  vm: "evm",
341
460
  chainId: 31337,
@@ -343,42 +462,39 @@ export const CHAINS = [
343
462
  testnet: true,
344
463
  native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
345
464
  rpcUrl: "http://127.0.0.1:8545",
346
- // Фида Chainlink в песочнице нет - и это ШТАТНЫЙ случай: интерфейс обязан сказать «демо»,
347
- // а не показать вчерашнюю константу.
465
+ // There is no Chainlink feed in the sandbox - a NORMAL case: the interface must say "demo", not show
466
+ // yesterday's constant.
348
467
  priceFeed: null,
349
468
  explorerTx: "",
350
469
  explorerAddr: "",
351
470
  explorerName: "",
352
471
  gasUsd: 0.0001,
353
472
  routeType: "atomic",
354
- // ФАБРИКА, развёрнутая в локальную цепочку. Адрес НЕ ВЫБИРАЕТСЯ, А СЛЕДУЕТ ИЗ ИСТОРИИ ОТПРАВИТЕЛЯ:
355
- // CREATE строит его из счёта и номера его транзакции, поэтому на СВЕЖЕЙ цепочке он один и тот же, а на
356
- // давно поднятой - другой: у счёта уже есть история. Здесь адрес ИЗМЕРЕН на цепи (чтение кода + keccak)
357
- // после передеплоя под формат котировки с xmrAmount: счёт 0 anvil, nonce 60, блок 74, 13188 байт кода
358
- // (keccak 0x9bffb62d50eef21bcfd4b59e1c3d6fb7cbb3e303257edb2f5ba9dc65b3e8719c - совпал со сборкой дерева).
359
- // Команда - из mvp/contracts: forge script script/Deploy.s.sol:Deploy --rpc-url http://127.0.0.1:8545
360
- // --broadcast --private-key <ключ счёта 0 anvil> - той же формой, что в tools/stack.mjs.
361
- // cashier - второй CREATE того же захода (сначала фабрика, затем касса, затем реестр): 573 байта,
362
- // keccak 0xcca1d8bc53d1d1c98901570800e093d0933f84637c6cdc727153b3ef8d8c3c36 - ровно тот хеш, которым
363
- // эскроу сверяет кассу (#122). Реестр - третий CREATE (DEPLOY_REGISTRY=true), 4557 байт.
364
- // ПРЕЖНЯЯ запись: фабрика 0x5fbdb2315678afecb367f032d93f642f64180aa3 (13077 байт, знала только прежний
365
- // селектор predict 0x3d81fc01) и касса 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 (573 байта, но ДРУГОЙ
366
- // сборки - keccak 0x5c32bb9c…): её код не совпадал с канонической кассой, и эскроу отверг бы такой ордер
367
- // по #122 - поэтому касса развёрнута заново тем же заходом.
368
- // ХЕШ КОДА РЕАЛИЗАЦИИ ЭСКРОУ (#114): то, что исполняет клон каждого ордера и что интерфейс обязан
369
- // сверить сам, а не только довериться фабрике. Значение - keccak256 runtime-кода NinseiEscrow, взято
370
- // из СВОЕЙ сборки: forge inspect NinseiEscrow deployedBytecode --root mvp/contracts и keccak (совпало
371
- // с keccak256(type(NinseiEscrow).runtimeCode), которое записывает фабрика, и с артефактом out/).
372
- // У реализации НЕТ immutables, поэтому это КОНСТАНТА ПРОТОКОЛА - один и тот же хеш в любой сети со
373
- // сборкой #114. Лежит при сети, а не одним общим числом, потому что политика может отсутствовать: сеть
374
- // со СТАРОЙ фабрикой (без implementation()) обязана остаться БЕЗ политики, иначе чтение пары откажет и
375
- // живые котировки этой сети встанут. Так и есть у arbitrum-sepolia: развёрнутая там фабрика сборки #114
376
- // не знает (оба геттера revert-ят), поэтому у неё поля НЕТ; включается тем же заходом, что и новый деплой.
473
+ // THE FACTORY DEPLOYED INTO THE LOCAL CHAIN. Its address is NOT CHOSEN BUT FOLLOWS FROM THE SENDER'S HISTORY:
474
+ // CREATE builds it from the account and its transaction number, so on a FRESH chain it is always the same while
475
+ // on a long-running one it differs - the account already has history. Here the address was MEASURED on chain
476
+ // (code read + keccak) after a redeploy for the quote format with xmrAmount: account 0 of anvil, nonce 60, block
477
+ // 74, 13188 bytes of code (keccak 0x9bffb62d50eef21bcfd4b59e1c3d6fb7cbb3e303257edb2f5ba9dc65b3e8719c -
478
+ // matched the tree build). The deploy command comes from mvp/contracts, the same form the stack tool uses.
479
+ // cashier is the second CREATE of the same pass (factory, then cashier, then registry): 573 bytes, keccak
480
+ // 0xcca1d8bc53d1d1c98901570800e093d0933f84637c6cdc727153b3ef8d8c3c36 - exactly the hash the escrow checks
481
+ // the cashier with. The registry is the third CREATE (DEPLOY_REGISTRY=true), 4557 bytes. PREVIOUS record:
482
+ // factory 0x5fbdb2315678afecb367f032d93f642f64180aa3 and cashier 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512,
483
+ // whose code did not match the canonical cashier - the escrow would have rejected such an order.
484
+ // THE ESCROW IMPLEMENTATION CODE HASH: what executes each order's clone, which the interface must check itself
485
+ // rather than trust the factory. The value is keccak256 of NinseiEscrow's runtime code from OUR OWN build (forge
486
+ // inspect NinseiEscrow deployedBytecode --root mvp/contracts and keccak - matched keccak256(type(NinseiEscrow)
487
+ // .runtimeCode) that the factory writes, and the out/ artifact). The implementation has NO immutables, so this is
488
+ // a PROTOCOL CONSTANT - the same hash on any network with that build. It sits per network, not as one global
489
+ // number, because the policy may be absent: a network with an OLD factory (no implementation()) must stay
490
+ // WITHOUT a policy, else reading the pair fails and live quotes there stall. That is the case on
491
+ // arbitrum-sepolia: its factory does not know the build (both getters revert), so it has NO field; it is enabled
492
+ // in the same pass as the new deploy.
377
493
  escrow: { address: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", mode: "live", cashier: "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", implementationCodeHash: "0xfd1ef12c1d96d3ee7060bd5901e06108723783403fad00c9630dcdeb6193674b" },
378
- // Токенов в песочнице нет: расплата идёт нейтивом. Пустой список - это факт о песочнице, а не забывчивость.
494
+ // There are no tokens in the sandbox: payment is native. An empty list is a fact about the sandbox, not an oversight.
379
495
  tokens: [],
380
- // Uniswap в песочнице не развёрнут и не нужен: тут своя цепочка для сквозных прогонов, а DEX-нога
381
- // проверяется в публичных сетях (см. DEX_CHAINS у начала файла). Блока uniswap у сети нет.
496
+ // Uniswap is not deployed in the sandbox and is not needed: this is a chain of its own for end-to-end runs, while
497
+ // the DEX leg is checked on public networks (see DEX_CHAINS at the top). The network has no uniswap block.
382
498
  },
383
499
  {
384
500
  id: "arbitrum",
@@ -388,46 +504,42 @@ export const CHAINS = [
388
504
  caip2: "eip155:42161",
389
505
  native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
390
506
  rpcUrl: "https://arb1.arbitrum.io/rpc",
391
- // Фид Chainlink для цены нейтива. Адреса проверены ВЫЗОВОМ в сети (description() совпало
392
- // с парой, значение свежее и в разумном коридоре) - см. tools/check-prices.mjs. Если фида
393
- // нет, цены нет: интерфейс обязан сказать «демо», а не показывать вчерашнюю константу.
507
+ // The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
508
+ // the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
509
+ // say "demo", not show yesterday's constant.
394
510
  priceFeed: { address: "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
395
511
  explorerTx: "https://arbiscan.io/tx/",
396
512
  explorerAddr: "https://arbiscan.io/address/",
397
513
  explorerName: "arbiscan",
398
- gasUsd: 0.21, // демо-оценка стоимости газа, как и остальные числа демки
514
+ gasUsd: 0.21, // a demo estimate of gas cost, like the rest of the demo numbers
399
515
  routeType: "atomic",
400
- // Контракта эскроу нет ни на одной боевой сети, поэтому демка имитирует фондирование.
516
+ // No escrow contract on any live network, so the demo simulates funding.
401
517
  escrow: { address: null, mode: "simulated" },
402
518
  tokens: [
403
519
  { symbol: "USDC", address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals: 6, usd: 1, cls: "ar-token-usdc" },
404
- // Адрес тот же, что был у USDT на Arbitrum, но контракт теперь отвечает symbol="USD₮0":
405
- // Tether передал контракт USDT0 (миграция прошла без смены адреса, 1:1 к USDT на Ethereum).
406
- // Идентификатор оставляем USDT - пользователь меняет «USDT», а не «USD₮0».
520
+ // The address is the one USDT had on Arbitrum, but the contract now answers symbol="USD₮0": Tether moved it
521
+ // to USDT0 (the migration kept the address, 1:1 to USDT on Ethereum). The id stays USDT - a user switches on
522
+ // "USDT", not "USD₮0".
407
523
  { symbol: "USDT", onChainSymbol: "USD₮0", address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", decimals: 6, usd: 1, cls: "ar-token-usdt" },
408
- // DAI и USDe: адреса - РЕШЕНИЕ ХОЗЯИНА (стояли и раньше). Каждый подтверждён чтением в сети
409
- // 2026-09-23, блок 508109137: код по адресу есть, symbol()/decimals()/name() прочитаны. Замер по
410
- // фабрике (getPool(токен, WETH, fee), ступени 100/500/3000/10000) на тот же блок: у USDe пулы 500
411
- // (0xaa9aea1896130089a8fc33acd7028dbaeba421d8) и 3000 (0x9511ee80843cdf8ecfa0337c73621458a8ca1c73)
412
- // есть, запасы НУЛЕВЫЕ. Это СПРАВКА для человека, а не отбор: список обмена жёсткий (DEX_ASSETS),
413
- // инструмент его не фильтрует.
414
- // USDS И PyUSD В ЭТОЙ СЕТИ НЕ ПРЕДЛАГАЮТСЯ (решение хозяина 2026-09-23). Прежние адреса (USDS
415
- // 0x6491c05A82219b8D1479057361ff1654749b876b, PyUSD 0x46850aD61C2B7d64d08c9C754F45254596696984) убраны
416
- // отсюда вместе с маршрутами: актив, которого сеть не предлагает, не должен ни рисоваться в строке
417
- // активов сети (ui/chainPicker.js), ни попадать в список на обмен (ui/views/swapForm.js). На
418
- // Ethereum оба остаются.
524
+ // DAI and USDe: the addresses are the OWNER'S DECISION (they stood before). Each is confirmed by a chain read
525
+ // on 2026-09-23, block 508109137: code present, symbol()/decimals()/name() read. The pool measurement (getPool
526
+ // on tiers 100/500/3000/10000): USDe has pools at 500 and 3000 with ZERO reserves. This is REFERENCE for a
527
+ // person, not a filter: the swap list is fixed (DEX_ASSETS) and the tool does not filter it.
528
+ // USDS AND PyUSD ARE NOT OFFERED ON THIS NETWORK (the owner's decision). Their previous addresses were removed
529
+ // here together with the routes: an asset the network does not offer must neither be drawn in the network's
530
+ // asset row (ui/chainPicker.js) nor enter the swap list (ui/views/swapForm.js). On Ethereum both stay.
419
531
  { symbol: "DAI", address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", decimals: 18, usd: 1, cls: "ar-token-dai" },
420
532
  { symbol: "USDe", address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", decimals: 18, usd: 1, cls: "ar-token-usde" },
421
- // WETH: как в arbitrum-sepolia (см. комментарий там) - пула не ищем, WETH->ETH это unwrap у WETH9.
422
- // Адрес определён измерением: token0/token1 пулов USDC/WETH, symbol()="WETH", decimals=18, name()="Wrapped Ether".
533
+ // WETH: as in arbitrum-sepolia (see the comment there) - no pool is searched, WETH->ETH is an unwrap at
534
+ // WETH9. The address is measured: token0/token1 of the USDC/WETH pools, symbol()="WETH", decimals=18, name()="Wrapped Ether".
423
535
  { symbol: "WETH", address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
424
536
  ],
425
- // DEX-НОГА: адреса из официального списка развёртываний Uniswap (deployments/json/42161.json) и
426
- // подтверждены вызовами в сети 2026-09-23: код QuoterV2 8273 байта, код фабрики 24535 байт,
427
- // getPool(USDC|USDT|DAI|USDe, WETH, 100|500|3000|10000) отвечает, котировки живые.
428
- // ЧЕГО В ЭТОЙ СЕТИ НЕТ: у USDe пул существует, но ликвидности в нём нет (liquidity()=0), а ступень
429
- // 10000 у DAI мизерная - котировка на 100000 DAI даёт вчетверо меньше, чем на 1000. Это видно в
430
- // замере, и это ровно тот случай, ради которого нога считается через пул, а не берётся из мока.
537
+ // DEX LEG: the addresses come from the official Uniswap deployment list (deployments/json/42161.json) and are
538
+ // confirmed by chain calls on 2026-09-23: QuoterV2 code 8273 bytes, factory code 24535 bytes, getPool(
539
+ // USDC|USDT|DAI|USDe, WETH, 100|500|3000|10000) answers, quotes are live. WHAT THIS NETWORK LACKS: USDe has a
540
+ // pool but no liquidity (liquidity()=0), and the 10000 tier for DAI is tiny - a quote for 100000 DAI gives a
541
+ // quarter of one for 1000. That is visible in the measurement, and it is exactly why the leg goes through a pool
542
+ // rather than a mock.
431
543
  uniswap: {
432
544
  factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
433
545
  quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
@@ -443,39 +555,36 @@ export const CHAINS = [
443
555
  caip2: "eip155:1",
444
556
  native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
445
557
  rpcUrl: "https://ethereum-rpc.publicnode.com",
446
- // Фид Chainlink для цены нейтива. Адреса проверены ВЫЗОВОМ в сети (description() совпало
447
- // с парой, значение свежее и в разумном коридоре) - см. tools/check-prices.mjs. Если фида
448
- // нет, цены нет: интерфейс обязан сказать «демо», а не показывать вчерашнюю константу.
558
+ // The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
559
+ // the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
560
+ // say "demo", not show yesterday's constant.
449
561
  priceFeed: { address: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
450
562
  explorerTx: "https://etherscan.io/tx/",
451
563
  explorerAddr: "https://etherscan.io/address/",
452
564
  explorerName: "etherscan",
453
- gasUsd: 3.4, // на L1 газ заметно дороже - в демо это видно при выборе сети
565
+ gasUsd: 3.4, // L1 gas is noticeably dearer - visible in the demo when choosing a network
454
566
  routeType: "atomic",
455
567
  escrow: { address: null, mode: "simulated" },
456
568
  tokens: [
457
569
  { symbol: "USDC", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6, usd: 1, cls: "ar-token-usdc" },
458
570
  { symbol: "USDT", address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6, usd: 1, cls: "ar-token-usdt" },
459
- // DAI, USDe, USDS, PyUSD: USDS и PyUSD - АДРЕСА ХОЗЯИНА, каждый подтверждён чтением в сети
460
- // 2026-09-23, блок 26039940: код по адресу есть, symbol()/decimals()/name() прочитаны
461
- // (USDS: "USDS"/18/"USDS Stablecoin"; PyUSD: "PYUSD"/6/"PayPal USD"). USDS и USDe отличаются одной
462
- // буквой, поэтому подтверждались отдельно. Замер по фабрике (getPool(токен, WETH, fee)) на тот же
463
- // блок: у USDS пулы только на 500 (0xe547c6d5039d1902db58102e7f52410dbc5b0708, ~2.1 USDS) и 3000
464
- // (0x691c9c856eeec77531750c465450b5e05a74c047, пусто); у PyUSD - на 500 (0xa8b1a6acc942b8266f987c4b0cdf625e3479a26b, пусто),
465
- // 3000 (0xf313d711d71eb9a607b4a61a827a9e32a7846621, ~33.8 PyUSD) и 10000 (0x643ea0885875a77bf8dc3e0f3ead866a125276b1, ~265.8 PyUSD);
466
- // у USDe пула с WETH НЕТ ни на одной ступени. Это СПРАВКА, а не отбор: список жёсткий (DEX_ASSETS).
571
+ // DAI, USDe, USDS, PyUSD: USDS and PyUSD are the OWNER'S ADDRESSES, each confirmed by a chain read on
572
+ // 2026-09-23, block 26039940: code present, symbol()/decimals()/name() read (USDS: "USDS"/18/"USDS Stablecoin";
573
+ // PyUSD: "PYUSD"/6/"PayPal USD"). USDS and USDe differ by one letter, so they were confirmed separately. The
574
+ // factory measurement (getPool(token, WETH, fee)) on the same block: USDS pools only at 500 (~2.1 USDS) and 3000
575
+ // (empty); PyUSD at 500 (empty), 3000 (~33.8 PyUSD) and 10000 (~265.8 PyUSD); USDe has NO pool with WETH at any
576
+ // tier. This is REFERENCE, not selection: the list is fixed (DEX_ASSETS).
467
577
  { symbol: "DAI", address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", decimals: 18, usd: 1, cls: "ar-token-dai" },
468
578
  { symbol: "USDe", address: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", decimals: 18, usd: 1, cls: "ar-token-usde" },
469
579
  { symbol: "USDS", address: "0xdC035D45d973E3EC169d2276DDab16f1e407384F", decimals: 18, usd: 1, cls: "ar-token-usds" },
470
580
  { symbol: "PyUSD", address: "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8", decimals: 6, usd: 1, cls: "ar-token-pyusd" },
471
- // WETH: пула не ищем - WETH->ETH это unwrap у WETH9 (1:1). hidden: true = не показывать в витрине
472
- // сети, но оставить в списке на обмене (см. arbitrum-sepolia). Адрес подтверждён измерением.
581
+ // WETH: no pool searched - WETH->ETH is an unwrap at WETH9 (1:1). hidden: true = do not show in the network
582
+ // showcase but keep in the swap list (see arbitrum-sepolia). The address is confirmed by measurement.
473
583
  { symbol: "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
474
584
  ],
475
- // DEX-НОГА: адреса из официального списка Uniswap (deployments/json/1.json), подтверждены вызовами:
476
- // код QuoterV2 8273 байта, фабрики 24535 байт, пулы USDC|USDT|DAI к WETH есть по всем четырём
477
- // ступеням, котировки живые. У USDe в этой сети пула НЕТ ни по одной ступени - так и должно быть
478
- // сказано словами, а не показано нулём.
585
+ // DEX LEG: the addresses come from the official Uniswap deployment list (deployments/json/1.json), confirmed by
586
+ // calls: QuoterV2 code 8273 bytes, factory 24535 bytes, USDC|USDT|DAI to WETH pools exist at all four tiers and
587
+ // quotes are live. USDe has NO pool on any tier here - and that must be said in words, not shown as a zero.
479
588
  uniswap: {
480
589
  factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
481
590
  quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
@@ -491,9 +600,9 @@ export const CHAINS = [
491
600
  caip2: "eip155:8453",
492
601
  native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
493
602
  rpcUrl: "https://mainnet.base.org",
494
- // Фид Chainlink для цены нейтива. Адреса проверены ВЫЗОВОМ в сети (description() совпало
495
- // с парой, значение свежее и в разумном коридоре) - см. tools/check-prices.mjs. Если фида
496
- // нет, цены нет: интерфейс обязан сказать «демо», а не показывать вчерашнюю константу.
603
+ // The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
604
+ // the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
605
+ // say "demo", not show yesterday's constant.
497
606
  priceFeed: { address: "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
498
607
  explorerTx: "https://basescan.org/tx/",
499
608
  explorerAddr: "https://basescan.org/address/",
@@ -507,9 +616,9 @@ export const CHAINS = [
507
616
  { symbol: "DAI", address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", decimals: 18, usd: 1, cls: "ar-token-dai" },
508
617
  { symbol: "USDe", address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", decimals: 18, usd: 1, cls: "ar-token-usde" },
509
618
  ],
510
- // DEX-НОГИ В ЭТОЙ СЕТИ НЕТ, и это НЕ про "мы не проверяли", а про состояние продукта: сеть в
511
- // статусе coming soon, блока uniswap у неё нет и пулы здесь не ищутся. Что это значит для экрана -
512
- // он обязан сказать словами (см. mock/market.js: dexRoute), а не показать пул-пустышку.
619
+ // THERE IS NO DEX LEG ON THIS NETWORK, and that is NOT "we did not check" but the product state: the network is
620
+ // coming soon, it has no uniswap block and no pools are searched here. What that means for the screen: it must
621
+ // say so in words (see mock/market.js: dexRoute), not show a dummy pool.
513
622
  },
514
623
  {
515
624
  id: "bsc",
@@ -519,8 +628,8 @@ export const CHAINS = [
519
628
  caip2: "eip155:56",
520
629
  native: { symbol: "BNB", decimals: 18, usd: 612.5 },
521
630
  rpcUrl: "https://bsc-dataseed.binance.org",
522
- // Цена BNB: фид Chainlink не подтверждён вызовом - пока сеть показывает «демо»,
523
- // а не выдуманное число. Добавляется сюда, когда адрес проверен так же, как остальные.
631
+ // The BNB price: the Chainlink feed is not confirmed by a call - so far the network shows "demo", not an
632
+ // invented number. It is added here once the address is verified like the others.
524
633
  explorerTx: "https://bscscan.com/tx/",
525
634
  explorerAddr: "https://bscscan.com/address/",
526
635
  explorerName: "bscscan",
@@ -528,35 +637,35 @@ export const CHAINS = [
528
637
  routeType: "atomic",
529
638
  escrow: { address: null, mode: "simulated" },
530
639
  tokens: [
531
- // Внимание: на BSC у USDT и USDC 18 decimals (не 6, как на Ethereum L1 и L2).
532
- // DAI и USDe на BNB Chain не добавляем: в демке их там нет.
640
+ // Note: on BSC USDT and USDC have 18 decimals (not 6, as on Ethereum L1 and L2).
641
+ // DAI and USDe are not added on BNB Chain: the demo does not have them there.
533
642
  { symbol: "USDT", address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18, usd: 1, cls: "ar-token-usdt" },
534
643
  { symbol: "USDC", address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", decimals: 18, usd: 1, cls: "ar-token-usdc" },
535
644
  ],
536
- // DEX-НОГИ В ЭТОЙ СЕТИ НЕТ: сеть в статусе coming soon, блок uniswap у неё отсутствует, и пулы
537
- // здесь не ищутся. Обёрнутый нативный токен этой сети - WBNB (нативный тут BNB, не ETH), но пока
538
- // сеть не включена, в реестре нет ни одного её адреса: незачем подтверждать то, чем не пользуемся.
645
+ // THERE IS NO DEX LEG ON THIS NETWORK: it is coming soon, has no uniswap block, and no pools are searched here.
646
+ // This network's wrapped native is WBNB (its native is BNB, not ETH), but while the network is off the registry
647
+ // has none of its addresses: no point confirming what we do not use.
539
648
  },
540
649
  {
541
650
  id: "bitcoin",
542
651
  name: "Bitcoin",
543
652
  vm: "bitcoin",
544
- chainId: 0, // у Bitcoin нет EVM chainId; 0 - это SLIP-44 coin type, и он не сталкивается с EVM-сетями
545
- caip2: "bip122:000000000019d6689c085ae165831e93", // bip122 + первые 32 символа хеша нулевого блока
653
+ chainId: 0, // Bitcoin has no EVM chainId; 0 is the SLIP-44 coin type and does not clash with EVM networks
654
+ caip2: "bip122:000000000019d6689c085ae165831e93", // bip122 + the first 32 chars of the zero block hash
546
655
  native: { symbol: "BTC", decimals: 8, usd: 96000 },
547
- // У Bitcoin нет JSON-RPC: это REST-API блокчейн-обозревателя. Демка в него не ходит.
656
+ // Bitcoin has no JSON-RPC: this is a block explorer's REST API. The demo does not call it.
548
657
  rpcUrl: "https://blockstream.info/api",
549
658
  explorerTx: "https://mempool.space/tx/",
550
659
  explorerAddr: "https://mempool.space/address/",
551
660
  explorerName: "mempool.space",
552
661
  gasUsd: 1.2,
553
662
  routeType: "atomic",
554
- // В Bitcoin актив один - сам BTC: контрактных токенов в протоколе нет.
663
+ // In Bitcoin there is one asset - BTC itself: the protocol has no contract tokens.
555
664
  tokens: [],
556
- // Эскроу-контракта здесь быть не может, и это не пробел: у Bitcoin вместо контракта работает
557
- // native atomic swap (HTLC/адапторные подписи в скрипте). Поэтому говорим прямо, чем сеттлится,
558
- // и не выдаём отсутствие контракта за "coming soon". Демка Bitcoin пока не умеет (подключается
559
- // EVM-кошелёк), поэтому escrow: null - сеть в списке, выбрать её нельзя.
665
+ // An escrow contract cannot exist here, and that is not a gap: Bitcoin uses a native atomic swap (HTLC/adaptor
666
+ // signatures in the script) instead of a contract. So we say plainly what it settles with and do not pass the
667
+ // absence of a contract off as "coming soon". The demo cannot do Bitcoin yet (it connects an EVM wallet), so
668
+ // escrow: null - the network is listed but not selectable.
560
669
  escrow: null,
561
670
  settlement: "native-atomic-swap",
562
671
  },
@@ -564,7 +673,7 @@ export const CHAINS = [
564
673
  id: "tron",
565
674
  name: "Tron",
566
675
  vm: "tron",
567
- chainId: 728126428, // network id: eth_chainId на TronGrid отвечает 0x2b6653dc
676
+ chainId: 728126428, // network id: eth_chainId on TronGrid answers 0x2b6653dc
568
677
  caip2: "tron:0x2b6653dc",
569
678
  native: { symbol: "TRX", decimals: 6, usd: 0.28 },
570
679
  rpcUrl: "https://api.trongrid.io",
@@ -573,11 +682,11 @@ export const CHAINS = [
573
682
  explorerName: "tronscan",
574
683
  gasUsd: 0.35,
575
684
  routeType: "atomic",
576
- // НЕ EVM: демка сегодня подключает только EVM-кошелёк (EIP-1193) и не умеет здесь ни читать
577
- // балансы, ни подписывать. Поэтому сеть видна в списке, но не выбирается - escrow: null.
685
+ // NOT EVM: today the demo connects only an EVM wallet (EIP-1193) and can neither read balances here nor sign.
686
+ // So the network is listed but not selectable - escrow: null.
578
687
  escrow: null,
579
688
  tokens: [
580
- // TRC-20: symbol()/decimals() проверены через TronGrid triggerconstantcontract (USDT, 6).
689
+ // TRC-20: symbol()/decimals() verified through TronGrid triggerconstantcontract (USDT, 6).
581
690
  { symbol: "USDT", address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6, usd: 1, cls: "ar-token-usdt" },
582
691
  ],
583
692
  },
@@ -585,7 +694,7 @@ export const CHAINS = [
585
694
  id: "solana",
586
695
  name: "Solana",
587
696
  vm: "solana",
588
- chainId: 101, // идентификатор Solana mainnet-beta; ниже CAIP-2 с genesis hash
697
+ chainId: 101, // Solana mainnet-beta id; below is the CAIP-2 with the genesis hash
589
698
  caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
590
699
  native: { symbol: "SOL", decimals: 9, usd: 198.4 },
591
700
  rpcUrl: "https://api.mainnet-beta.solana.com",
@@ -594,10 +703,10 @@ export const CHAINS = [
594
703
  explorerName: "solscan",
595
704
  gasUsd: 0.002,
596
705
  routeType: "atomic",
597
- // НЕ EVM: см. пояснение у Tron.
706
+ // NOT EVM: see the note at Tron.
598
707
  escrow: null,
599
708
  tokens: [
600
- // SPL-mint: проверено getAccountInfo (owner Tokenkeg...) и getTokenSupply (decimals 6).
709
+ // SPL mint: verified by getAccountInfo (owner Tokenkeg...) and getTokenSupply (decimals 6).
601
710
  { symbol: "USDC", address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", decimals: 6, usd: 1, cls: "ar-token-usdc" },
602
711
  { symbol: "USDT", address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", decimals: 6, usd: 1, cls: "ar-token-usdt" },
603
712
  ],
@@ -606,9 +715,9 @@ export const CHAINS = [
606
715
  id: "monad",
607
716
  name: "Monad",
608
717
  vm: "evm",
609
- // Демка эту сеть пока не умеет: в UI ведёт себя как Solana - серая и не выбирается,
610
- // а в строке честно написано "not supported yet". План сети не отменён - снимем признак,
611
- // когда появится контракт и токены.
718
+ // The demo cannot do this network yet: in the UI it behaves like Solana - grey and not selectable, with
719
+ // "not supported yet" written honestly in the row. The network's plan is not cancelled - the mark comes off
720
+ // when a contract and tokens appear.
612
721
  unsupported: true,
613
722
  chainId: 143,
614
723
  caip2: "eip155:143",
@@ -621,21 +730,21 @@ export const CHAINS = [
621
730
  routeType: "atomic",
622
731
  escrow: { address: null, mode: "simulated" },
623
732
  tokens: [
624
- // USDC (Circle) и USDT0: symbol()/decimals() проверены вызовом в сети Monad (chainId 143).
733
+ // USDC (Circle) and USDT0: symbol()/decimals() verified by a call on the Monad network (chainId 143).
625
734
  { symbol: "USDC", address: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603", decimals: 6, usd: 1, cls: "ar-token-usdc" },
626
735
  { symbol: "USDT", onChainSymbol: "USDT0", address: "0xe7cd86e13AC4309349F30B3435a9d337750fC82D", decimals: 6, usd: 1, cls: "ar-token-usdt" },
627
736
  ],
628
- // DEX-НОГИ ЗДЕСЬ НЕТ: сеть в статусе coming soon, пулы не ищутся и адреса в реестр не пишутся.
629
- // Незачем держать в интерфейсе адрес, которым нельзя пользоваться: то, что мы измерили раньше,
630
- // после такого решения просто не проверяется, и оставлять это как "данные" было бы обманом.
737
+ // NO DEX LEG HERE: the network is coming soon, pools are not searched and addresses are not written into the
738
+ // registry. No point keeping an address in the interface that cannot be used: what we measured earlier simply
739
+ // stops being checked, and keeping it as "data" would be a lie.
631
740
  },
632
741
  {
633
742
  id: "hyperevm",
634
743
  name: "Hyperliquid EVM",
635
744
  vm: "evm",
636
- // Демка эту сеть пока не умеет: в UI ведёт себя как Solana - серая и не выбирается,
637
- // а в строке честно написано "not supported yet". План сети не отменён - снимем признак,
638
- // когда появится контракт и токены.
745
+ // The demo cannot do this network yet: in the UI it behaves like Solana - grey and not selectable, with
746
+ // "not supported yet" written honestly in the row. The network's plan is not cancelled - the mark comes off
747
+ // when a contract and tokens appear.
639
748
  unsupported: true,
640
749
  chainId: 999,
641
750
  caip2: "eip155:999",
@@ -648,37 +757,37 @@ export const CHAINS = [
648
757
  routeType: "atomic",
649
758
  escrow: { address: null, mode: "simulated" },
650
759
  tokens: [
651
- // USDC и USDT0 (на контракте symbol = "USD₮0"): проверено вызовами в самой сети (chainId 999).
760
+ // USDC and USDT0 (the contract's symbol = "USD₮0"): verified by calls on the network itself (chainId 999).
652
761
  { symbol: "USDC", address: "0xb88339CB7199b77E23DB6E890353E22632Ba630f", decimals: 6, usd: 1, cls: "ar-token-usdc" },
653
762
  { symbol: "USDT", onChainSymbol: "USD₮0", address: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", decimals: 6, usd: 1, cls: "ar-token-usdt" },
654
763
  ],
655
- // DEX-НОГИ ЗДЕСЬ НЕТ: сеть в статусе coming soon, пулы не ищутся и адреса в реестр не пишутся.
764
+ // NO DEX LEG HERE: the network is coming soon, pools are not searched and addresses are not written into the registry.
656
765
  },
657
766
  ];
658
767
 
659
768
  export const DEFAULT_CHAIN = "arbitrum-sepolia";
660
769
 
661
- // СПИСОК ДОПУЩЕННЫХ ПРОВАЙДЕРОВ - ПОЛИТИКА ИНТЕРФЕЙСА, А НЕ ПРОВЕРКА ЦЕПИ (#76/#77).
770
+ // THE ALLOW-LIST OF PROVIDERS - INTERFACE POLICY, NOT A CHAIN CHECK.
662
771
  //
663
- // Цепь отвечает на ДРУГОЙ вопрос: чей ключ подписал котировку (реестр мейкеров, providerOf - его читает
664
- // ядро, sdk/src/quotes.mjs). ЭТОТ список - решение интерфейса о том, с кем он вообще торгует. Провайдер,
665
- // чей адрес подтверждён цепью, но не назван здесь, не проходит (код `quote-provider-not-allowed`), и это
666
- // видно ЧЕЛОВЕКУ до подписи: комиссию показывает котировка, а её выдача не состоится.
772
+ // The chain answers a DIFFERENT question: whose key signed the quote (the maker registry, providerOf - the core
773
+ // reads it, sdk/src/quotes.mjs). THIS list is the interface's decision about whom it trades with at all. A provider
774
+ // whose address the chain confirms but who is not named here is refused (the code `quote-provider-not-allowed`), and
775
+ // the person sees that BEFORE signing: the quote shows the fee, but its issuance will not happen.
667
776
  //
668
- // ПОЧЕМУ ОТДЕЛЬНЫМ СПИСКОМ, А НЕ ЧТЕНИЕМ ИЗ БЕКЕНДА. Отбор - дело интерфейса: он меняется здесь, без
669
- // передеплоя контрактов и без правок у чужого узла. Такой же список есть и у бекенда
670
- // (`ARRAKIS_ALLOWED_PROVIDERS`, app/app.env) - это ДВЕ независимые ступени одного решения, а не копия:
671
- // страница защищает себя сама, даже если сервис настроен иначе.
777
+ // WHY A SEPARATE LIST AND NOT A READ FROM THE BACKEND. Selection is the interface's business: it changes here,
778
+ // without redeploying contracts or editing someone else's node. The backend has the same kind of list
779
+ // (`ARRAKIS_ALLOWED_PROVIDERS`, in the backend env) - TWO independent steps of one decision, not a copy: the page
780
+ // guards itself even if the service is configured differently.
672
781
  //
673
- // АДРЕСА - в нижнем регистре, как их отдаёт котировка (поле `provider`). Сеть без записи здесь означает
674
- // "список не задан" (политика не применяется), а НЕ "никого не пускать": иначе сеть без настройки встала бы
675
- // целиком. Заполнять список для сети нужно тогда, когда там зарегистрирован мейкер.
782
+ // THE ADDRESSES ARE LOWERCASE, as the quote gives them (field `provider`). A network with no entry here means
783
+ // "no list set" (the policy does not apply), NOT "let nobody in": otherwise a network without configuration would
784
+ // stall entirely. A network's list needs filling once a maker is registered there.
676
785
  export const ALLOWED_PROVIDERS = {
677
786
  "local-anvil": ["0x60af2ad4963c738aa6ac16d6cb0af1982508ba21"],
678
787
  };
679
788
 
680
- // Список допущенных для сети - или null, если он не задан (политики нет). Ядро принимает и массив, и
681
- // предикат; здесь отдаётся массив в нижнем регистре: список - это то, что видно и меняется в одном месте.
789
+ // The allow-list for a network - or null if it is not set (no policy). The core accepts both an array and a
790
+ // predicate; here an array is returned in lowercase: the list is what is visible and changed in one place.
682
791
  export function allowedProvidersFor(chainOrId) {
683
792
  const id = typeof chainOrId === "string" ? chainOrId : chainOrId && chainOrId.id;
684
793
  const list = ALLOWED_PROVIDERS[id];
@@ -686,12 +795,11 @@ export function allowedProvidersFor(chainOrId) {
686
795
  return list.map((a) => String(a).toLowerCase());
687
796
  }
688
797
 
689
- // СПИСОК ИЗВЕСТНЫХ СБОРОК ФАБРИКИ - ПОЛИТИКА ИНТЕРФЕЙСА, РЯДОМ С allowedProviders (#103).
690
- // Значение - extcodehash (keccak256 кода) фабрики, которую интерфейс готов принять: фабрика приходит из
691
- // ЗАПИСИ РЕЕСТРА провайдера, а её код сверяется с этим списком. Пусто для сети = политики нет (фабрика всё
692
- // равно обязана быть названа записью и совпасть с подписанной котировкой). Заполняется при развёртывании:
693
- // у фабрики нет immutable-полей, поэтому одна и та же сборка даёт один и тот же хеш у любого, кто её
694
- // развернул, - своей фабрикой провайдер попадает в тот же список.
798
+ // THE LIST OF KNOWN FACTORY BUILDS - INTERFACE POLICY, next to allowedProviders. The value is the extcodehash
799
+ // (keccak256 of the code) of a factory the interface will accept: the factory comes from the PROVIDER REGISTRY
800
+ // record, and its code is compared against this list. Empty for a network = no policy (the factory must still be
801
+ // named by the record and match the signed quote). Filled at deploy: the factory has no immutables, so one build
802
+ // gives the same hash to anyone who deployed it - a provider's own factory lands in the same list.
695
803
  export const KNOWN_FACTORY_CODES = {};
696
804
 
697
805
  export function knownFactoryCodesFor(chainOrId) {
@@ -701,10 +809,10 @@ export function knownFactoryCodesFor(chainOrId) {
701
809
  return list.map((h) => String(h).toLowerCase());
702
810
  }
703
811
 
704
- // СПИСОК ИЗВЕСТНЫХ СБОРОК РЕАЛИЗАЦИИ ЭСКРОУ - ПОЛИТИКА ИНТЕРФЕЙСА (#114), при самой сети, где живёт адрес
705
- // фабрики (escrow.address). Хеш её кода - КОНСТАНТА ПРОТОКОЛА (у реализации нет immutables), но политика
706
- // может быть не задана: у сети без поля implementationCodeHash её НЕТ (старая фабрика пары не отдаёт).
707
- // Пусто = политики нет; непусто = интерфейс торгует только через фабрики, ведущие на эту сборку.
812
+ // THE LIST OF KNOWN ESCROW-IMPLEMENTATION BUILDS - INTERFACE POLICY, at the network where the factory address
813
+ // (escrow.address) lives. Its code hash is a PROTOCOL CONSTANT (the implementation has no immutables), but the
814
+ // policy may be unset: a network without an implementationCodeHash field has NONE (an old factory does not give the
815
+ // pair). Empty = no policy; non-empty = the interface trades only through factories leading to this build.
708
816
  export function knownImplementationCodesFor(chainOrId) {
709
817
  const id = typeof chainOrId === "string" ? chainOrId : chainOrId && chainOrId.id;
710
818
  const chain = CHAINS.find((c) => c.id === id) || chainById(id);
@@ -721,7 +829,7 @@ export function chainByChainId(chainId) {
721
829
  return CHAINS.find((c) => c.chainId === Number(chainId)) || null;
722
830
  }
723
831
 
724
- // Принимаемые токены сети: нативный первым, затем контрактные.
832
+ // A network's accepted tokens: native first, then contract ones.
725
833
  export function tokensOf(chainId) {
726
834
  const c = chainById(chainId);
727
835
  return [nativeOf(chainId), ...c.tokens];
@@ -740,7 +848,15 @@ export function nativeOf(chainId) {
740
848
  };
741
849
  }
742
850
 
743
- // Токен по составному идентификатору "<chainId>:<SYMBOL>" либо по паре аргументов.
851
+ // A network's WRAPPED native token (WETH/WBNB...), or null. It is what the liquidity leg settles (KyberSwap Limit
852
+ // Order trades ERC20 against ERC20 and cannot deliver native) and what the native leg sells to buy native coin: the
853
+ // engine's composed route (www/js/evm/asyncExec.js) turns the token into this wrapped token first, then into native.
854
+ export function wrappedNativeOf(chainId) {
855
+ const c = chainById(chainId);
856
+ return (c.tokens || []).find((t) => t && t.peg === "native") || null;
857
+ }
858
+
859
+ // A token by its composite id "<chainId>:<SYMBOL>" or by two arguments.
744
860
  export function tokenById(idOrChainId, symbolMaybe) {
745
861
  let chainId = idOrChainId;
746
862
  let symbol = symbolMaybe;
@@ -752,137 +868,126 @@ export function tokenById(idOrChainId, symbolMaybe) {
752
868
  }
753
869
 
754
870
 
755
- // Monero-сеть демки.
756
- // networkType=mainnet - потому что monero-js (вендоренная библиотека мнемоник/ключей/адресов)
757
- // поддерживает только mainnet-префиксы адресов (см. www/assets/vendors/monero-js/README.md).
758
- // Режим monero-ts (WebAssembly wallet2) умеет и stagenet.
759
- // Получатель ордера (claimer) для ТЕСТОВ на тестовых сетях.
871
+ // The demo's Monero network. networkType=mainnet because monero-js (the vendored mnemonic/key/address library)
872
+ // supports only mainnet address prefixes (see the vendored readme). The monero-ts mode (WebAssembly wallet2) also
873
+ // handles stagenet.
874
+ // The order recipient (claimer) for TESTS on test networks.
760
875
  //
761
- // ВАЖНО: контракт требует, чтобы locker и claimer различались, поэтому адрес оплаты здесь указывать
762
- // нельзя - транзакция будет отклонена. Платит подключённый кошелёк (кто угодно), а забирает средства тот,
763
- // кто записан здесь, предъявив секрет ордера.
764
- // Это ЗАГЛУШКА для испытаний: в бою адрес получателя приходит от RFQ-узла мейкера вместе с котировкой.
876
+ // IMPORTANT: the contract requires locker and claimer to differ, so a payment address must not be given here -
877
+ // the transaction would be rejected. The connected wallet pays (anyone), while the funds are claimed by whoever is
878
+ // recorded here, presenting the order secret.
879
+ // This is a TEST stub: in production the recipient address comes from the maker's RFQ node with the quote.
765
880
  export const TEST_CLAIMER = "0xc14C7841C7d6350859474dD6DF8dC9672047A6A0";
766
881
 
767
882
  export const MONERO = {
768
- // Сеть, в которой демка генерирует свой демо-кошелёк и адрес получателя. Обязана совпадать с
769
- // XMR_NETWORKS (ниже) и с той цепью, к которой подключена нода: адрес чужой сети в поле
770
- // получателя означает отправленные в никуда XMR. Совпадение проверяет npm test.
883
+ // The network in which the demo generates its demo wallet and recipient address. It must match XMR_NETWORKS
884
+ // (below) and the chain the node is connected to: an address of another network in the recipient field means XMR
885
+ // sent nowhere. A check verifies the match.
771
886
  networkType: "stagenet",
772
- confirmTarget: 10, // подтверждений до claim (пример из litepaper §5.0)
773
- blockTimeSim: 120_000, // 2 минуты на блок, в sim-времени
774
- blockTimeReal: 120_000, // реальный target Monero mainnet
775
- // Нода Monero. Адрес задан прямо здесь и больше нигде: параметров у демки нет
776
- // (ни ?rpc=, ни localStorage) и переключений RPC не предполагается - меняется строкой в коде.
777
- // сегодня в публичную, дальше в свою - для демки разницы нет.
778
- // сегодня в публичную, дальше в свою - для демки разницы нет.
779
- // Нода нужна только для статуса сети и режима live-подтверждений; ключи, балансы и выводы
780
- // через неё не запрашиваются.
781
- // АДРЕС НОДЫ. В бою он один и виден в коде (.hermes/deploy/arrakis-xmr-rpc.conf). НА ЛОКАЛЬНОМ СТЕНДЕ
782
- // страница обязана ходить на НАШУ ноду: боевой прокси может быть недоступен (502), а высота сети - один из
783
- // источников высоты скана для файла восстановления, то есть без ноды файл уходит владельцу без высоты.
784
- // Подстановку делает только стенд через окно; в бою window.ARRAKIS_XMR_NODE никто не ставит.
785
- node: (typeof window !== "undefined" && window.ARRAKIS_XMR_NODE) || "https://xmr.arrakisswap.trade",
887
+ confirmTarget: 10, // confirmations before claim (the litepaper example)
888
+ blockTimeSim: 120_000, // 2 minutes per block, in sim time
889
+ blockTimeReal: 120_000, // the real Monero mainnet target
890
+ // The Monero node. Its address is set in ONE named place (see ENDPOINTS) and nowhere else: the demo has no
891
+ // parameters (no ?rpc=, no localStorage) and no RPC switching is planned. The node is needed only for the network
892
+ // status and live-confirmation mode; keys, balances and withdrawals are not requested through it.
893
+ // A DEPLOYMENT sets the node address in ENDPOINTS; on the local stand the page must reach OUR node - the
894
+ // production proxy may be unavailable (502), and the network height is one source of the recovery file's scan
895
+ // height, i.e. without a node the file reaches its owner without a height.
896
+ node: ENDPOINTS.moneroNode,
786
897
  nodePollMs: 30_000,
787
898
  };
788
899
 
789
900
  // ---------------------------------------------------------------------------
790
- // Бэкенд (app/): сессии свопов и наблюдение за Monero-ногой.
901
+ // The backend (app/): swap sessions and watching the Monero leg.
791
902
  //
792
- // API живёт на том же домене по пути /api/ (Angie проксирует на 127.0.0.1:8787) - поэтому CSP
793
- // править не нужно, его покрывает connect-src 'self'. Локально тот же путь отдаёт serve.mjs
794
- // с флагом --api=http://127.0.0.1:8787 (см. tools/serve.mjs) - тоже без правки политики.
903
+ // The API lives on the same domain at /api/ (Angie proxies to 127.0.0.1:8787), so CSP needs no edit - connect-src
904
+ // 'self' covers it. Locally the same path is served with an --api flag - also without touching the policy.
795
905
  // ---------------------------------------------------------------------------
796
906
  export const API = {
797
907
  base: "/api",
798
- // Источник Monero-ноги: 'app' - настоящий бэкенд по HTTP, 'mock' - симулятор демки без сети.
908
+ // The Monero-leg source: 'app' - the real backend over HTTP, 'mock' - the demo simulator with no network.
799
909
  xmrSource: "app",
800
- pollMs: 5_000, // как часто фронт опрашивает сессию свопа
801
- // СКОЛЬКО ПРОВАЙДЕРОВ ПОКАЗЫВАТЬ в блоке цены. Список из десятков строк не читается, а сравнить больше
802
- // пяти цен человек всё равно не сможет - он выбирает лучшую. Остальные не скрываются молча: об их
803
- // существовании говорит строка-итог ("и ещё N").
910
+ pollMs: 5_000, // how often the front end polls a swap session
911
+ // HOW MANY PROVIDERS TO SHOW in the price block. A list of dozens of rows is unreadable, and a person cannot
912
+ // compare more than five prices anyway - he picks the best. The rest are not silenced: a summary line ("and N
913
+ // more") tells of their existence.
804
914
  maxProviders: 5,
805
- timeoutMs: 60_000, // Таймаут одного запроса: зависший бэкенд не должен подвешивать экран. 60 с, а не 10: создание
806
- // кошелька сделки - единственный ДОЛГИЙ путь, он сканирует цепочку от высоты восстановления, и
807
- // прежние 10 с обрывали РАБОТАЮЩИЙ запрос. Снаружи это выглядело как «Swap wallet unavailable».
808
- // Срок поднимали дважды (8 -> 10 -> 60): здесь короткий таймаут не защита, а ложный отказ.
915
+ timeoutMs: 60_000, // One request's timeout: a hung backend must not hang the screen. 60 s, not 10: creating the
916
+ // deal wallet is the only LONG path - it scans the chain from the restore height - and the old 10 s cut off a
917
+ // WORKING request. From outside it looked like "Swap wallet unavailable". The deadline was raised twice
918
+ // (8 -> 10 -> 60): a short timeout here is not a guard but a false refusal.
809
919
  };
810
920
 
811
- // Сеть Monero для каждого источника. Бэкенд на сервере работает со stagenet-кошельком
812
- // (monero-wallet-rpc поднят на stagenet-ноде), поэтому в режиме 'app' адрес свопа обязан быть
813
- // stagenet-адресом: иначе wallet-rpc не сможет создать по нему watch-only кошелёк.
814
- // Мок-режим демки исторически mainnet - адреса из демо-примеров.
815
- // Одна цепь на всю демку. Раньше мок-режим притворялся mainnet (демка была исторически mainnet),
816
- // но это только вредило: адреса в мок-режиме выглядели как mainnet (префикс 4), а живая нода -
817
- // stagenet (префикс 5), и в одном UI соседствовали две цепи. Смешение цепей в свопе означает
818
- // потерянные деньги (см. monero/address.js), поэтому теперь и мок, и живой режим - stagenet.
921
+ // The Monero network for each source. The backend on the server works with a stagenet wallet (monero-wallet-rpc is
922
+ // up on a stagenet node), so in 'app' mode the swap address must be a stagenet one: otherwise wallet-rpc cannot
923
+ // create a watch-only wallet for it. The demo's mock mode was historically mainnet - addresses from the demo examples.
924
+ // ONE CHAIN FOR THE WHOLE DEMO. The mock mode used to pretend to be mainnet, which only hurt: mock addresses looked
925
+ // like mainnet (prefix 4) while the live node is stagenet (prefix 5), and two chains sat side by side in one UI.
926
+ // Mixing chains in a swap means lost money (see monero/address.js), so now both mock and live are stagenet.
819
927
  //
820
- // ПЛЮС ЗНАКОМОЕ ИМЯ REGTEST. Ключи app/mock отвечают на вопрос «в какой сети кошелёк этого источника».
821
- // Третья запись источника не имеет: fakechain - официальное имя regtest-режима monerod, которым нода
822
- // называет себя на контуре CI. Она нужна, чтобы имя сети ПРИНИМАЛОСЬ: ядро сверяет xmrNetwork со списком
823
- // сетей движка (sdk/src/config.mjs, STANDARD_XMR_NETWORKS), а адаптер кошелька ядра берёт список сетей
824
- // из ЭТОГО реестра (sdk/src/adapters/monero-wallet.mjs). Кошелёк демки при этом остаётся stagenet.
928
+ // PLUS THE FAMILIAR NAME REGTEST. The app/mock keys answer "which network this source's wallet is on". A third
929
+ // source entry has none: fakechain is the official name of monerod's regtest mode, which the CI contour node calls
930
+ // itself. It is needed so the network name is ACCEPTED: the core compares xmrNetwork against the engine's network
931
+ // list (sdk/src/config.mjs, STANDARD_XMR_NETWORKS), and the core's wallet adapter takes its network list FROM THIS
932
+ // registry (sdk/src/adapters/monero-wallet.mjs). The demo's wallet stays stagenet.
825
933
  export const XMR_NETWORKS = { app: "stagenet", mock: "stagenet", fakechain: "fakechain" };
826
934
 
827
- // Страница sweep: отдельный домен со своей (более строгой) политикой и WASM-кошельком.
828
- // Вывод XMR делает сам пользователь там, по своему recovery-файлу - демка за него не подписывает
829
- // (см. .hermes/docs/09-sweep-origin.md).
830
- // Куда ведёт ссылка на страницу вывода. В бою - боевой домен, на локальном стенде - его собственный адрес
831
- // (там своя нода и свой бэкенд). Отдельная функция, чтобы правило было одним: ссылка и то, где страница
832
- // действительно поднята, не должны расходиться.
935
+ // The sweep page: a separate domain with its own (stricter) policy and a WASM wallet. The user makes the XMR
936
+ // withdrawal there, from his recovery file - the demo does not sign for him.
937
+ // Where the link to the withdrawal page leads. In production - the production domain, on the local stand - its own
938
+ // address (its own node and backend there). A separate function so the rule is one: the link and where the page is
939
+ // actually served must not diverge.
833
940
  export function sweepOrigin() {
834
941
  const h = typeof location !== "undefined" ? location.hostname : "";
835
942
  return /^(localhost|127\.0\.0\.1|\[::1\])$/.test(h) || /\.local$/.test(h) ? SWEEP.localOrigin : SWEEP.origin;
836
943
  }
837
944
 
838
945
  export const SWEEP = {
839
- origin: "https://sweep.arrakisswap.trade",
946
+ origin: ENDPOINTS.sweepOrigin,
840
947
  localOrigin: "http://localhost:5195",
841
948
  };
842
949
 
843
950
  // ---------------------------------------------------------------------------
844
- // EVM-кошелёк: WalletConnect (Reown).
845
- // projectId - публичный идентификатор приложения, его видит любой сайт с WalletConnect
846
- // (это не секрет: он уходит в QR и в relay). Метаданные показываются пользователю в кошельке.
951
+ // The EVM wallet: WalletConnect (Reown). projectId is the app's public identifier, visible to any site with
952
+ // WalletConnect (not a secret: it goes into the QR and the relay). The metadata is shown to the user in the wallet.
847
953
  // ---------------------------------------------------------------------------
848
954
  export const EVM = {
849
955
  projectId: "18c13c05b6c0ab011ffff7cee0700e2d",
850
956
  metadata: {
851
- name: "ArrakisSwap",
957
+ name: "NinseiSwap",
852
958
  description: "Non-custodial XMR <-> EVM atomic swap. Demo build: real wallet, real balances, no real funds move.",
853
- url: "https://arrakisswap.trade",
959
+ url: PAGE_ORIGIN,
854
960
  icons: [],
855
961
  },
856
962
  };
857
963
 
858
- // СТАВКИ КОМИССИИ ЗДЕСЬ НЕТ - И НЕ ДОЛЖНО БЫТЬ. Единственный источник правды: ПОДПИСАННАЯ КОТИРОВКА.
859
- // Ставка, величина и получатель комиссии - часть условий ордера (решение #76); функций feeBps()/feeFor() у
860
- // фабрики больше нет (роутер удалён), и узел не сверяет свою настройку с цепью - он её ПОДПИСЫВАЕТ, а
861
- // величина приходит в котировке и сверяется зеркалом формулы (www/js/evm/fees.js, rfq/fundEscrow.mjs).
862
- // Копия в конфиге разошлась бы с подписанным набором - и выяснилось бы это не здесь, а на подписи.
863
- // Запись со ставкой (0.003) удалена 28 сентября 2026; переход на котировку - этап #76 (комиссия в ордере).
864
-
865
- // Мок-мейкеры. Спред - то, что мейкер забирает себе; комиссия Arrakis считается отдельно.
866
- // СПИСОК ВЫДУМАННЫХ МЕЙКЕРОВ УДАЛЁН. Раньше здесь лежали Maker A/B/C с надбавкой, аптаймом, запасом и
867
- // задержкой - и всё это уходило в интерфейс как факты. Аптайм чужого сервиса мы не измеряем, запас не
868
- // знаем, а надбавку провайдер формирует сам и показывает её только в своей цене. Теперь котировки берутся
869
- // у провайдера через бекенд (/api/rate), и выдумывать рядом с ними нечего.
870
-
871
- // Тайминги. Все в sim-миллисекундах (см. js/core/clock.js): при speed=60 одна
872
- // реальная секунда равна одной sim-минуте.
964
+ // THERE IS NO FEE RATE HERE - AND THERE MUST NOT BE. The only source of truth is the SIGNED QUOTE. The rate, amount
965
+ // and recipient of the fee are part of the order terms; the factory no longer has feeBps()/feeFor() (the router was
966
+ // removed), and the node does not compare its setting with the chain - it SIGNS it, while the amount comes in the
967
+ // quote and is checked by the formula mirror (www/js/evm/fees.js, rfq/fundEscrow.mjs). A copy in the config would
968
+ // diverge from the signed set - and it would surface at signing, not here. The record with a rate (0.003) was
969
+ // removed on 28 September 2026; the move to the quote was the fee-in-order stage.
970
+
971
+ // Mock makers. The spread is what a maker keeps; the Ninsei fee is counted separately.
972
+ // THE LIST OF INVENTED MAKERS IS REMOVED. It used to hold Makers A/B/C with a markup, uptime, reserve and delay -
973
+ // all of it reaching the interface as facts. We do not measure someone else's uptime, we do not know their reserve,
974
+ // and a provider forms its own markup and shows it only in its price. Now quotes come from a provider through the
975
+ // backend (/api/rate), and there is nothing to invent next to them.
976
+
977
+ // Timings. All in sim milliseconds (see js/core/clock.js): at speed=60 one real second equals one sim minute.
873
978
  export const TIMING = {
874
- quoteValid: 12_000, // срок жизни котировки (12 сек в sim)
979
+ quoteValid: 12_000, // quote lifetime (12 s in sim)
875
980
  quoteRefresh: 5_000,
876
- fundingMined: 6_000, // tx фондирования попала в блок
877
- makerLock: 30_000, // мейкер лочит XMR
981
+ fundingMined: 6_000, // the funding tx landed in a block
982
+ makerLock: 30_000, // the maker locks the XMR
878
983
  xmrConfirm: MONERO.blockTimeSim,
879
- readyWindow: 60 * 60_000, // сколько у пользователя есть на ready после лока
984
+ readyWindow: 60 * 60_000, // how long the user has to mark ready after the lock
880
985
  makerClaim: 45_000,
881
986
  sweep: 30_000,
882
- t0Refund: 20 * 60_000, // после этого можно вернуть ETH, если мейкер не залочил XMR
883
- t1Refund: 40 * 60_000, // после этого можно вернуть ETH, если мейкер не сделал claim
884
- watchtowerGrace: 5 * 60_000, // watchtower возвращает деньги, если пользователь ушёл
885
- makerReclaim: 10 * 60_000, // мейкер забирает XMR, если пользователь не сделал ready
987
+ t0Refund: 20 * 60_000, // after this the ETH can be refunded if the maker did not lock the XMR
988
+ t1Refund: 40 * 60_000, // after this the ETH can be refunded if the maker did not claim
989
+ watchtowerGrace: 5 * 60_000, // the watchtower refunds if the user left
990
+ makerReclaim: 10 * 60_000, // the maker takes the XMR back if the user did not mark ready
886
991
  };
887
992
 
888
993
  export const DEMO = {
@@ -890,15 +995,15 @@ export const DEMO = {
890
995
  defaultSpeed: 60,
891
996
  scenarios: ["happy", "maker_no_lock", "user_no_ready", "maker_stall"],
892
997
  defaultScenario: "happy",
893
- // Режима кошелька больше нет: ключи в демке считает monero-js, и это единственный путь
894
- // (WASM-кошелёк monero-ts переехал на отдельный домен sweep - своя политика и COOP/COEP).
895
- // mock - подтверждения по sim-таймеру; live - по реальной высоте Monero mainnet
998
+ // There is no wallet mode any more: keys in the demo are computed by monero-js, the only path (the WASM wallet
999
+ // monero-ts moved to the separate sweep domain - its own policy and COOP/COEP).
1000
+ // mock - confirmations by a sim timer; live - by the real Monero mainnet height
896
1001
  chainModes: ["mock", "live"],
897
- // ЖИВАЯ ВЫСОТА ПО УМОЛЧАНИЮ. "mock" означает подтверждения по таймеру песочницы - это демонстрационный
898
- // режим, и он больше не подставляется сам: сделка обязана считаться по настоящей высоте цепи, иначе
899
- // «подтверждено» на экране ничего не значит. Демонстрационный режим остался выбором, а не умолчанием.
1002
+ // LIVE HEIGHT BY DEFAULT. "mock" means confirmations by a sandbox timer - a demo mode, and it is no longer
1003
+ // substituted by itself: a deal must be counted by the real chain height, else "confirmed" on screen means
1004
+ // nothing. Demo mode remains a choice, not a default.
900
1005
  defaultChainMode: "live",
901
- // Источник Monero-ноги: 'app' - настоящий бэкенд (см. API.xmrSource), 'mock' - симулятор.
1006
+ // The Monero-leg source: 'app' - the real backend (see API.xmrSource), 'mock' - the simulator.
902
1007
  xmrSources: ["app", "mock"],
903
1008
  defaultXmrSource: "app",
904
1009
  defaultChain: DEFAULT_CHAIN,