@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.
- package/CHANGELOG.md +63 -0
- package/README.md +4 -1
- package/engine/atomic/dleq2.js +86 -111
- package/engine/atomic/halfenc.js +58 -72
- package/engine/atomic/halves.js +31 -53
- package/engine/atomic/order-client.js +15 -16
- package/engine/atomic/order.js +44 -62
- package/engine/atomic/orderContext.js +26 -50
- package/engine/atomic/orderHalfCommit.js +17 -18
- package/engine/core/chainSource.js +54 -69
- package/engine/core/clock.js +11 -12
- package/engine/core/config.js +554 -449
- package/engine/core/finality.js +34 -53
- package/engine/core/format.js +35 -36
- package/engine/core/preSignGuards.js +109 -117
- package/engine/core/rfqSource.js +57 -75
- package/engine/core/sellWindow.js +23 -24
- package/engine/core/store.js +20 -21
- package/engine/core/swap.js +242 -243
- package/engine/evm/amounts.js +50 -51
- package/engine/evm/asyncExec.js +577 -0
- package/engine/evm/asyncLegs.js +308 -0
- package/engine/evm/asyncRoute.js +179 -0
- package/engine/evm/auth.js +41 -48
- package/engine/evm/claimGas.js +28 -47
- package/engine/evm/depositor.js +39 -43
- package/engine/evm/dex.js +266 -267
- package/engine/evm/escrow.js +113 -129
- package/engine/evm/factory.js +71 -77
- package/engine/evm/fees.js +41 -42
- package/engine/evm/funding.js +160 -162
- package/engine/evm/gasReserve.js +67 -36
- package/engine/evm/index.js +46 -53
- package/engine/evm/injected.js +34 -42
- package/engine/evm/permit.js +129 -56
- package/engine/evm/prices.js +30 -45
- package/engine/evm/session.js +188 -189
- package/engine/mock/chain.js +17 -18
- package/engine/monero/address.js +33 -48
- package/engine/monero/halvesSweep.js +25 -43
- package/engine/monero/node.js +14 -15
- package/engine/monero/scan.js +82 -95
- package/engine/monero/swapKeys.js +30 -33
- package/engine/monero/unlock.js +17 -20
- package/engine/monero/wallet.js +32 -42
- package/engine/recovery/recoveryFile.js +154 -161
- package/engine/recovery/restore.js +50 -61
- package/engine/sdk/bridge.js +244 -234
- package/engine/ui/dom.js +4 -5
- package/engine/ui/walletModal.js +20 -31
- package/index.d.ts +26 -0
- package/package.json +1 -1
- package/src/adapters/evm-wallet.mjs +16 -0
- package/src/adapters/monero-wallet.mjs +1 -1
- package/src/adapters/monero.mjs +1 -1
- package/src/adapters/node-test-evm-wallet.mjs +5 -5
- package/src/codes.mjs +5 -0
- package/src/config.mjs +1 -1
- package/src/engine.mjs +4 -0
- package/src/index.mjs +16 -2
- package/src/legs/cow-spec.mjs +254 -0
- package/src/legs/cow.mjs +315 -0
- package/src/legs/evm.mjs +86 -2
- package/src/legs/kyber-spec.mjs +265 -0
- package/src/legs/kyber.mjs +425 -0
- package/src/legs/shape.mjs +35 -0
- package/src/legs/uniswaprelay-spec.mjs +541 -0
- package/src/legs/uniswaprelay.mjs +422 -0
- package/src/legs/uniswapx-spec.mjs +329 -0
- package/src/legs/uniswapx.mjs +362 -0
- package/src/quotes.mjs +23 -5
- package/src/swaps.mjs +18 -1
package/engine/core/config.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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"
|
|
20
|
-
//
|
|
21
|
-
//
|
|
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
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
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: "
|
|
55
|
+
name: "NinseiSwap",
|
|
32
56
|
version: "0.1.0",
|
|
33
57
|
demo: true,
|
|
34
|
-
storageKey: "
|
|
58
|
+
storageKey: "ninsei.demo.v1",
|
|
35
59
|
};
|
|
36
60
|
|
|
37
61
|
// ---------------------------------------------------------------------------
|
|
38
|
-
//
|
|
39
|
-
//
|
|
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
|
-
//
|
|
66
|
+
// The escrow field answers one question: can one SETTLE on this network.
|
|
44
67
|
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
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 -
|
|
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
|
-
//
|
|
55
|
-
//
|
|
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
|
-
//
|
|
61
|
-
// arbitrum (42161)
|
|
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
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
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
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// minReadyLeadSec -
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
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 -
|
|
115
|
-
//
|
|
116
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
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%
|
|
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
|
-
//
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
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
|
-
//
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
193
|
-
//
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
rpcUrl:
|
|
226
|
-
//
|
|
227
|
-
//
|
|
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
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
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
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
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
|
-
//
|
|
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
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
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
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
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
|
-
//
|
|
295
|
-
// router - SwapRouter02.
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
// permit2 -
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// UniversalRouter (0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2)
|
|
306
|
-
//
|
|
307
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
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
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
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
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
357
|
-
//
|
|
358
|
-
// (keccak 0x9bffb62d50eef21bcfd4b59e1c3d6fb7cbb3e303257edb2f5ba9dc65b3e8719c -
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
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
|
|
381
|
-
//
|
|
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
|
-
//
|
|
392
|
-
//
|
|
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
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
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
|
|
409
|
-
// 2026-09-23,
|
|
410
|
-
//
|
|
411
|
-
// (
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
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:
|
|
422
|
-
//
|
|
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
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
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
|
-
//
|
|
447
|
-
//
|
|
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, //
|
|
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
|
|
460
|
-
// 2026-09-23,
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
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:
|
|
472
|
-
//
|
|
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
|
|
476
|
-
//
|
|
477
|
-
//
|
|
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
|
-
//
|
|
495
|
-
//
|
|
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
|
-
//
|
|
512
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
532
|
-
// DAI
|
|
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
|
|
537
|
-
//
|
|
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, //
|
|
545
|
-
caip2: "bip122:000000000019d6689c085ae165831e93", // bip122 +
|
|
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
|
-
//
|
|
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
|
-
//
|
|
663
|
+
// In Bitcoin there is one asset - BTC itself: the protocol has no contract tokens.
|
|
555
664
|
tokens: [],
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
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
|
|
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
|
-
//
|
|
577
|
-
//
|
|
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()
|
|
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, //
|
|
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
|
-
//
|
|
706
|
+
// NOT EVM: see the note at Tron.
|
|
598
707
|
escrow: null,
|
|
599
708
|
tokens: [
|
|
600
|
-
// SPL
|
|
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
|
-
//
|
|
610
|
-
//
|
|
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)
|
|
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
|
|
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
|
-
//
|
|
637
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
770
|
+
// THE ALLOW-LIST OF PROVIDERS - INTERFACE POLICY, NOT A CHAIN CHECK.
|
|
662
771
|
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
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`,
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
//
|
|
693
|
-
//
|
|
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
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
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
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
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
|
-
//
|
|
770
|
-
//
|
|
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, //
|
|
773
|
-
blockTimeSim: 120_000, // 2
|
|
774
|
-
blockTimeReal: 120_000, //
|
|
775
|
-
//
|
|
776
|
-
// (
|
|
777
|
-
//
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
|
|
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
|
-
//
|
|
901
|
+
// The backend (app/): swap sessions and watching the Monero leg.
|
|
791
902
|
//
|
|
792
|
-
// API
|
|
793
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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, //
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
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
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
//
|
|
816
|
-
//
|
|
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
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
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
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
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:
|
|
946
|
+
origin: ENDPOINTS.sweepOrigin,
|
|
840
947
|
localOrigin: "http://localhost:5195",
|
|
841
948
|
};
|
|
842
949
|
|
|
843
950
|
// ---------------------------------------------------------------------------
|
|
844
|
-
// EVM
|
|
845
|
-
//
|
|
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: "
|
|
957
|
+
name: "NinseiSwap",
|
|
852
958
|
description: "Non-custodial XMR <-> EVM atomic swap. Demo build: real wallet, real balances, no real funds move.",
|
|
853
|
-
url:
|
|
959
|
+
url: PAGE_ORIGIN,
|
|
854
960
|
icons: [],
|
|
855
961
|
},
|
|
856
962
|
};
|
|
857
963
|
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
//
|
|
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, //
|
|
979
|
+
quoteValid: 12_000, // quote lifetime (12 s in sim)
|
|
875
980
|
quoteRefresh: 5_000,
|
|
876
|
-
fundingMined: 6_000, // tx
|
|
877
|
-
makerLock: 30_000, //
|
|
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, //
|
|
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, //
|
|
883
|
-
t1Refund: 40 * 60_000, //
|
|
884
|
-
watchtowerGrace: 5 * 60_000, // watchtower
|
|
885
|
-
makerReclaim: 10 * 60_000, //
|
|
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
|
-
//
|
|
894
|
-
//
|
|
895
|
-
// mock -
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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,
|