@ninsei-labs/sdk 0.39.3 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- 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 +561 -451
- 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 +8 -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 +20 -8
- package/src/config.mjs +1 -1
- package/src/engine.mjs +4 -0
- 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 +1 -1
- package/src/swaps.mjs +1 -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,116 @@ 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
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// cashier
|
|
257
|
-
//
|
|
258
|
-
escrow: { address: "
|
|
366
|
+
// THE DEMO'S MAIN NETWORK: the Ninsei escrow factory, the interface's fee cashier and the maker registry live
|
|
367
|
+
// here; the ArrakisFeeRouter is gone. The factory is a FACT, not an intention:
|
|
368
|
+
// 0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2 (height 318033203, transaction
|
|
369
|
+
// 0x0f82c556f056f2dcaba5c12da75a749c257dfb1ccbf0eb6962a4e7f0119d166d, code 4226 bytes, keccak256
|
|
370
|
+
// 0x4b515f600c641bffde054b3a98f4e26bd43d2dd2b2d8e60e8cfe36ee03686dd9); every order is an EIP-1167 clone and
|
|
371
|
+
// the factory carries the pair (implementation(), implementationCodeHash()). The fee cashier is the second CREATE
|
|
372
|
+
// of the same pass - 0x7E84035B4f42219F3fCE22855c6Dd7D36942B019 (height 318033206, code 573 bytes, keccak256
|
|
373
|
+
// 0xcca1d8bc53d1d1c98901570800e093d0933f84637c6cdc727153b3ef8d8c3c36); the maker registry is the third -
|
|
374
|
+
// 0x416d548B497d3AE808807172a5Bc3549512a64E5 (height 318033209, code 4557 bytes). The code matches the current
|
|
375
|
+
// repo build byte for byte.
|
|
376
|
+
// `npm run check:deployed -- --address=0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2`).
|
|
377
|
+
// PREVIOUS (the ArrakisFeeRouter of the previous rules, now removed): 0xaEB1c0455C76551dcEb2E6B1245B65F0458b0aE8
|
|
378
|
+
// (height 314323871, code 8223 bytes, keccak256 0x90f26f641b48a2a630463195d4429b0818d29187563e09711a40811fcf08a0ba).
|
|
379
|
+
// PREVIOUS (ceiling 3%, no early refund): 0xEa619bDC78989627093B7830990367B2ECE52CE5 (height 314309038,
|
|
380
|
+
// code 8143 bytes). PREVIOUS live (router with a 10% ceiling): 0x9f856cd9c14fd9493961473b804039b09a4012ef
|
|
381
|
+
// (height 313597131, code 8723 bytes). PREVIOUS before it (factory without a fee):
|
|
382
|
+
// 0xcb54af9ec11344d448c7900ccb08530a23783259 (height 312222006, code 6035 bytes), and earlier ones still.
|
|
383
|
+
// mode: "live" - the form sends REAL transactions: order creation TOGETHER WITH THE MONEY in one transaction
|
|
384
|
+
// (createOrderAndFund; the escrow has no separate funding function - an order is born funded, see FACTORY_METHODS
|
|
385
|
+
// in www/js/evm/factory.js and ESCROW_METHODS in escrow.js). The old lock(sealedHalf) and hashlock are gone: the
|
|
386
|
+
// half is not sent but revealed by presentation. Switching back to "simulated" is one edit, to show a demo with
|
|
387
|
+
// no money movement. cashier: the INTERFACE's fee cashier, named in quotes and now deployed (the second CREATE of
|
|
388
|
+
// the same pass). implementationCodeHash: the escrow implementation the factory carries, checked against the chain.
|
|
389
|
+
escrow: { address: "0x1029D6bC53f6b9919F8ec6Ac922327F7e5A3DdF2", mode: "live", cashier: "0x7E84035B4f42219F3fCE22855c6Dd7D36942B019", implementationCodeHash: "0xfd1ef12c1d96d3ee7060bd5901e06108723783403fad00c9630dcdeb6193674b" },
|
|
259
390
|
tokens: [
|
|
260
|
-
//
|
|
391
|
+
// Test USDC (Circle): decimals 6 and symbol "USDC" verified on chain by the same node.
|
|
261
392
|
{ symbol: "USDC", address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
262
|
-
// WETH
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
// peg: "native" - своей демо-цены у WETH нет: его долларовая цена - это цена ETH.
|
|
393
|
+
// WETH IS THE SAME ETH, JUST WRAPPED, so no pool is searched for it: WETH -> ETH is a withdraw at WETH9, one
|
|
394
|
+
// to one and without a swap (see the DEX leg below). hidden: true means "do not show in the network showcase
|
|
395
|
+
// (header)", NOT "unsupported": it is in the swap token list (that uses tokensOf; the header filters by hidden).
|
|
396
|
+
// The address is DETERMINED BY MEASUREMENT, not from memory: token0/token1 of the USDC/WETH pool gave it, and
|
|
397
|
+
// symbol()/decimals()/name() answered "WETH"/18/"Wrapped Ether" (checked 2026-09-23).
|
|
398
|
+
// peg: "native" - WETH has no demo price of its own: its dollar price is the ETH price.
|
|
269
399
|
{ symbol: "WETH", address: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
|
|
270
400
|
],
|
|
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 раза) - это ПЕРЕСЧЁТ той же величины, а не второй замер.
|
|
401
|
+
// THIS NETWORK'S DEX LEG (Uniswap v3, read only: eth_call to QuoterV2). THE FACTORY IS NOT IN THE OFFICIAL LIST
|
|
402
|
+
// FOR ARBITRUM SEPOLIA, so the address was OBTAINED BY MEASUREMENT: factory() was read from the pools named in
|
|
403
|
+
// the same-chain ramp notes, and getPool(USDC, WETH, fee) was then checked to return the same addresses
|
|
404
|
+
// (2026-09-23). IMPORTANT ABOUT THIS NETWORK: pools exist but liquidity is unusable. The 0.01% tier serves NO
|
|
405
|
+
// size ("pool cannot serve this size"), 0.05% gives ZERO output at any size, and 0.3% answers at a price that
|
|
406
|
+
// diverges from the Chainlink reference.
|
|
407
|
+
// ONE NUMBER, NOT THREE. It used to read "30x" and "18x" here while the report said 94.54%: measurements AT
|
|
408
|
+
// DIFFERENT SIZES ON DIFFERENT DAYS, each meaningless without a named size and method. Measurement 2026-09-24,
|
|
409
|
+
// block 312238363, head size 100 USDC: the quote gave 0.116508 WETH, i.e. an implied native price of $858.31
|
|
410
|
+
// against $2650.42 by the Chainlink feed - a 67.6% divergence against a 2% threshold, the pool 3.09x cheaper
|
|
411
|
+
// than the reference. The method is THE SAME CODE AS IN THE INTERFACE (www/js/evm/dex.js: priceGateVerdict,
|
|
412
|
+
// reference from www/js/evm/prices.js) and is rerunnable in one command.
|
|
413
|
+
// (rerun the DEX quote check against arbitrum-sepolia)
|
|
414
|
+
// THE NUMBER DEPENDS ON SIZE (same block, same scale): 1 USDC = 77.5%, 10 USDC = 76.6%, 100 USDC = 67.6%,
|
|
415
|
+
// 1000 USDC = -16.0%. So ONE number stands here WITH A NAMED SIZE. The tool also prints a "how many times
|
|
416
|
+
// cheaper" observation (+208.6% for 100 USDC, i.e. the same 3.09x) - a RECOUNT of the same figure, not a second measurement.
|
|
290
417
|
uniswap: {
|
|
291
418
|
factory: "0x248ab79bbb9bc29bb72f7cd42f17e054fc40188e",
|
|
292
419
|
quoter: "0x2779a0CC1c3e0E44D2542EC3e79e3864Ae93Ef0B",
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
// router - SwapRouter02.
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
// permit2 -
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// UniversalRouter (0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2)
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
// 43" доказательством не является. Появится нужда - сначала доказательство, потом запись.
|
|
420
|
+
// EXECUTION (unlike the quote above): the addresses are MEASURED, not taken from a deployment agreement, and
|
|
421
|
+
// the measurement is rechecked by a run.
|
|
422
|
+
// router - SwapRouter02. Three INDEPENDENT signs of origin, all from the chain: (1) code at the address,
|
|
423
|
+
// 24497 bytes; (2) its factory() EQUALS the Uniswap factory above; (3) its WETH9() EQUALS wrapped below.
|
|
424
|
+
// Plus the source is verified at the explorer: Arbiscan API V2 (chainid 421614) returned an ABI of 41
|
|
425
|
+
// entries, contract name SwapRouter02, compiler v0.7.6+commit.7338295f. An address returning EXACTLY these
|
|
426
|
+
// factory() and WETH9() cannot be guessed - it is the same contract.
|
|
427
|
+
// IMPORTANT: permit2() on THIS router REVERTS (checked by a run): it does not consume Permit2, and the
|
|
428
|
+
// allowance is given to it by an ordinary approve.
|
|
429
|
+
// permit2 - the canonical Uniswap Permit2: code 9152 bytes, its DOMAIN_SEPARATOR() matched one computed FROM
|
|
430
|
+
// SCRATCH for chainId 421614 and this address (domain name "Permit2"), and the source is verified at the
|
|
431
|
+
// explorer. Needed by the order's FUNDING path, NOT by this router.
|
|
432
|
+
// UniversalRouter (0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2) is NOT included, on purpose: its origin on
|
|
433
|
+
// this network is NOT confirmed (getabi and getsourcecode at the explorer answer NOTOK, i.e. the source is
|
|
434
|
+
// unverified), and "the same address as in the report" is not proof. When needed - proof first, then the record.
|
|
309
435
|
router: "0x101F443B4d1b059569D643917553c771E1b9663E",
|
|
310
436
|
permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
|
|
311
|
-
//
|
|
437
|
+
// The wrapped native token: the address matches WETH in tokens above - that is checked (check-front).
|
|
312
438
|
wrapped: { symbol: "WETH", name: "Wrapped Ether", address: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73", decimals: 18 },
|
|
313
439
|
feeTiers: [100, 500, 3000, 10000],
|
|
314
440
|
},
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
// есть DEX-ноги там не бывает - помечать нечего.
|
|
441
|
+
// THIS NETWORK'S PRICE IS WRONG BY NATURE, AND THAT IS A NETWORK MARK (like hidden/unsupported), not a
|
|
442
|
+
// weakening of the check: the same measurement as in the uniswap block above (block 312238363, 100 USDC: implied
|
|
443
|
+
// native price $858.31 against a $2650.42 reference - a 67.6% divergence against a 2% threshold). On a live
|
|
444
|
+
// network such a price STOPS signing; here it is only marked in words: a testnet tests the FLOW, not the market,
|
|
445
|
+
// else demo runs would stall on a price we did not choose. Live networks have no mark - there the price check
|
|
446
|
+
// blocks, and that is the default (DEX_PRICE_GATE). The local-anvil sandbox is not covered at all: it has no
|
|
447
|
+
// uniswap block and no tokens, so there is no DEX leg there - nothing to mark.
|
|
323
448
|
dexPriceGate: "tolerated",
|
|
324
449
|
},
|
|
325
450
|
{
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
// 31337 - обычный идентификатор локальных цепочек, ни с одной настоящей сетью не совпадает.
|
|
451
|
+
// A LOCAL NETWORK FOR INTERNAL TESTS. This is anvil on this machine: a real chain with real transactions, but a
|
|
452
|
+
// sandbox. In the demo it is not for showing - it is for runs needing full control. Its OWN chainId (31337), not
|
|
453
|
+
// one of a public network: with a matching id the front end could not uniquely pick the sandbox - it found the
|
|
454
|
+
// FIRST network with that id and fell back to the public one. 31337 is the usual local-chain id, matching no real network.
|
|
331
455
|
id: "local-anvil",
|
|
332
456
|
name: "Local (anvil)",
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
457
|
+
// HIDDEN FROM THE NETWORK LIST BUT NOT OFF. hidden concerns only the rows the list draws
|
|
458
|
+
// (see shownChains in www/js/ui/chainPicker.js): the sandbox is not for showing, but internal runs need it. The
|
|
459
|
+
// mark does NOT mean "unsupported" (that is unsupported on Monad/Hyperliquid - those stay listed, grey, marked
|
|
460
|
+
// "coming soon" like the other non-live networks): escrowState, isSelectable and settlementChain read the
|
|
461
|
+
// registry itself and do not know about hidden, so the sandbox stays a live network with a live escrow, selected
|
|
462
|
+
// by the id "local-anvil".
|
|
339
463
|
hidden: true,
|
|
340
464
|
vm: "evm",
|
|
341
465
|
chainId: 31337,
|
|
@@ -343,42 +467,39 @@ export const CHAINS = [
|
|
|
343
467
|
testnet: true,
|
|
344
468
|
native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
|
|
345
469
|
rpcUrl: "http://127.0.0.1:8545",
|
|
346
|
-
//
|
|
347
|
-
//
|
|
470
|
+
// There is no Chainlink feed in the sandbox - a NORMAL case: the interface must say "demo", not show
|
|
471
|
+
// yesterday's constant.
|
|
348
472
|
priceFeed: null,
|
|
349
473
|
explorerTx: "",
|
|
350
474
|
explorerAddr: "",
|
|
351
475
|
explorerName: "",
|
|
352
476
|
gasUsd: 0.0001,
|
|
353
477
|
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-ят), поэтому у неё поля НЕТ; включается тем же заходом, что и новый деплой.
|
|
478
|
+
// THE FACTORY DEPLOYED INTO THE LOCAL CHAIN. Its address is NOT CHOSEN BUT FOLLOWS FROM THE SENDER'S HISTORY:
|
|
479
|
+
// CREATE builds it from the account and its transaction number, so on a FRESH chain it is always the same while
|
|
480
|
+
// on a long-running one it differs - the account already has history. Here the address was MEASURED on chain
|
|
481
|
+
// (code read + keccak) after a redeploy for the quote format with xmrAmount: account 0 of anvil, nonce 60, block
|
|
482
|
+
// 74, 13188 bytes of code (keccak 0x9bffb62d50eef21bcfd4b59e1c3d6fb7cbb3e303257edb2f5ba9dc65b3e8719c -
|
|
483
|
+
// matched the tree build). The deploy command comes from mvp/contracts, the same form the stack tool uses.
|
|
484
|
+
// cashier is the second CREATE of the same pass (factory, then cashier, then registry): 573 bytes, keccak
|
|
485
|
+
// 0xcca1d8bc53d1d1c98901570800e093d0933f84637c6cdc727153b3ef8d8c3c36 - exactly the hash the escrow checks
|
|
486
|
+
// the cashier with. The registry is the third CREATE (DEPLOY_REGISTRY=true), 4557 bytes. PREVIOUS record:
|
|
487
|
+
// factory 0x5fbdb2315678afecb367f032d93f642f64180aa3 and cashier 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512,
|
|
488
|
+
// whose code did not match the canonical cashier - the escrow would have rejected such an order.
|
|
489
|
+
// THE ESCROW IMPLEMENTATION CODE HASH: what executes each order's clone, which the interface must check itself
|
|
490
|
+
// rather than trust the factory. The value is keccak256 of NinseiEscrow's runtime code from OUR OWN build (forge
|
|
491
|
+
// inspect NinseiEscrow deployedBytecode --root mvp/contracts and keccak - matched keccak256(type(NinseiEscrow)
|
|
492
|
+
// .runtimeCode) that the factory writes, and the out/ artifact). The implementation has NO immutables, so this is
|
|
493
|
+
// a PROTOCOL CONSTANT - the same hash on any network with that build. It sits per network, not as one global
|
|
494
|
+
// number, because the policy may be absent: a network with an OLD factory (no implementation()) must stay
|
|
495
|
+
// WITHOUT a policy, else reading the pair fails and live quotes there stall. That is the case on
|
|
496
|
+
// arbitrum-sepolia: its factory does not know the build (both getters revert), so it has NO field; it is enabled
|
|
497
|
+
// in the same pass as the new deploy.
|
|
377
498
|
escrow: { address: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", mode: "live", cashier: "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", implementationCodeHash: "0xfd1ef12c1d96d3ee7060bd5901e06108723783403fad00c9630dcdeb6193674b" },
|
|
378
|
-
//
|
|
499
|
+
// There are no tokens in the sandbox: payment is native. An empty list is a fact about the sandbox, not an oversight.
|
|
379
500
|
tokens: [],
|
|
380
|
-
// Uniswap
|
|
381
|
-
//
|
|
501
|
+
// Uniswap is not deployed in the sandbox and is not needed: this is a chain of its own for end-to-end runs, while
|
|
502
|
+
// the DEX leg is checked on public networks (see DEX_CHAINS at the top). The network has no uniswap block.
|
|
382
503
|
},
|
|
383
504
|
{
|
|
384
505
|
id: "arbitrum",
|
|
@@ -388,46 +509,42 @@ export const CHAINS = [
|
|
|
388
509
|
caip2: "eip155:42161",
|
|
389
510
|
native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
|
|
390
511
|
rpcUrl: "https://arb1.arbitrum.io/rpc",
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
512
|
+
// The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
|
|
513
|
+
// the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
|
|
514
|
+
// say "demo", not show yesterday's constant.
|
|
394
515
|
priceFeed: { address: "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
|
|
395
516
|
explorerTx: "https://arbiscan.io/tx/",
|
|
396
517
|
explorerAddr: "https://arbiscan.io/address/",
|
|
397
518
|
explorerName: "arbiscan",
|
|
398
|
-
gasUsd: 0.21, //
|
|
519
|
+
gasUsd: 0.21, // a demo estimate of gas cost, like the rest of the demo numbers
|
|
399
520
|
routeType: "atomic",
|
|
400
|
-
//
|
|
521
|
+
// No escrow contract on any live network, so the demo simulates funding.
|
|
401
522
|
escrow: { address: null, mode: "simulated" },
|
|
402
523
|
tokens: [
|
|
403
524
|
{ symbol: "USDC", address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
525
|
+
// The address is the one USDT had on Arbitrum, but the contract now answers symbol="USD₮0": Tether moved it
|
|
526
|
+
// to USDT0 (the migration kept the address, 1:1 to USDT on Ethereum). The id stays USDT - a user switches on
|
|
527
|
+
// "USDT", not "USD₮0".
|
|
407
528
|
{ 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 оба остаются.
|
|
529
|
+
// DAI and USDe: the addresses are the OWNER'S DECISION (they stood before). Each is confirmed by a chain read
|
|
530
|
+
// on 2026-09-23, block 508109137: code present, symbol()/decimals()/name() read. The pool measurement (getPool
|
|
531
|
+
// on tiers 100/500/3000/10000): USDe has pools at 500 and 3000 with ZERO reserves. This is REFERENCE for a
|
|
532
|
+
// person, not a filter: the swap list is fixed (DEX_ASSETS) and the tool does not filter it.
|
|
533
|
+
// USDS AND PyUSD ARE NOT OFFERED ON THIS NETWORK (the owner's decision). Their previous addresses were removed
|
|
534
|
+
// here together with the routes: an asset the network does not offer must neither be drawn in the network's
|
|
535
|
+
// asset row (ui/chainPicker.js) nor enter the swap list (ui/views/swapForm.js). On Ethereum both stay.
|
|
419
536
|
{ symbol: "DAI", address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", decimals: 18, usd: 1, cls: "ar-token-dai" },
|
|
420
537
|
{ symbol: "USDe", address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", decimals: 18, usd: 1, cls: "ar-token-usde" },
|
|
421
|
-
// WETH:
|
|
422
|
-
//
|
|
538
|
+
// WETH: as in arbitrum-sepolia (see the comment there) - no pool is searched, WETH->ETH is an unwrap at
|
|
539
|
+
// WETH9. The address is measured: token0/token1 of the USDC/WETH pools, symbol()="WETH", decimals=18, name()="Wrapped Ether".
|
|
423
540
|
{ symbol: "WETH", address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
|
|
424
541
|
],
|
|
425
|
-
// DEX
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
//
|
|
542
|
+
// DEX LEG: the addresses come from the official Uniswap deployment list (deployments/json/42161.json) and are
|
|
543
|
+
// confirmed by chain calls on 2026-09-23: QuoterV2 code 8273 bytes, factory code 24535 bytes, getPool(
|
|
544
|
+
// USDC|USDT|DAI|USDe, WETH, 100|500|3000|10000) answers, quotes are live. WHAT THIS NETWORK LACKS: USDe has a
|
|
545
|
+
// pool but no liquidity (liquidity()=0), and the 10000 tier for DAI is tiny - a quote for 100000 DAI gives a
|
|
546
|
+
// quarter of one for 1000. That is visible in the measurement, and it is exactly why the leg goes through a pool
|
|
547
|
+
// rather than a mock.
|
|
431
548
|
uniswap: {
|
|
432
549
|
factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
433
550
|
quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
|
|
@@ -443,39 +560,36 @@ export const CHAINS = [
|
|
|
443
560
|
caip2: "eip155:1",
|
|
444
561
|
native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
|
|
445
562
|
rpcUrl: "https://ethereum-rpc.publicnode.com",
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
563
|
+
// The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
|
|
564
|
+
// the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
|
|
565
|
+
// say "demo", not show yesterday's constant.
|
|
449
566
|
priceFeed: { address: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
|
|
450
567
|
explorerTx: "https://etherscan.io/tx/",
|
|
451
568
|
explorerAddr: "https://etherscan.io/address/",
|
|
452
569
|
explorerName: "etherscan",
|
|
453
|
-
gasUsd: 3.4, //
|
|
570
|
+
gasUsd: 3.4, // L1 gas is noticeably dearer - visible in the demo when choosing a network
|
|
454
571
|
routeType: "atomic",
|
|
455
572
|
escrow: { address: null, mode: "simulated" },
|
|
456
573
|
tokens: [
|
|
457
574
|
{ symbol: "USDC", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
458
575
|
{ 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).
|
|
576
|
+
// DAI, USDe, USDS, PyUSD: USDS and PyUSD are the OWNER'S ADDRESSES, each confirmed by a chain read on
|
|
577
|
+
// 2026-09-23, block 26039940: code present, symbol()/decimals()/name() read (USDS: "USDS"/18/"USDS Stablecoin";
|
|
578
|
+
// PyUSD: "PYUSD"/6/"PayPal USD"). USDS and USDe differ by one letter, so they were confirmed separately. The
|
|
579
|
+
// factory measurement (getPool(token, WETH, fee)) on the same block: USDS pools only at 500 (~2.1 USDS) and 3000
|
|
580
|
+
// (empty); PyUSD at 500 (empty), 3000 (~33.8 PyUSD) and 10000 (~265.8 PyUSD); USDe has NO pool with WETH at any
|
|
581
|
+
// tier. This is REFERENCE, not selection: the list is fixed (DEX_ASSETS).
|
|
467
582
|
{ symbol: "DAI", address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", decimals: 18, usd: 1, cls: "ar-token-dai" },
|
|
468
583
|
{ symbol: "USDe", address: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", decimals: 18, usd: 1, cls: "ar-token-usde" },
|
|
469
584
|
{ symbol: "USDS", address: "0xdC035D45d973E3EC169d2276DDab16f1e407384F", decimals: 18, usd: 1, cls: "ar-token-usds" },
|
|
470
585
|
{ symbol: "PyUSD", address: "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8", decimals: 6, usd: 1, cls: "ar-token-pyusd" },
|
|
471
|
-
// WETH:
|
|
472
|
-
//
|
|
586
|
+
// WETH: no pool searched - WETH->ETH is an unwrap at WETH9 (1:1). hidden: true = do not show in the network
|
|
587
|
+
// showcase but keep in the swap list (see arbitrum-sepolia). The address is confirmed by measurement.
|
|
473
588
|
{ symbol: "WETH", address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: 18, cls: "ar-token-eth", peg: "native", hidden: true },
|
|
474
589
|
],
|
|
475
|
-
// DEX
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// сказано словами, а не показано нулём.
|
|
590
|
+
// DEX LEG: the addresses come from the official Uniswap deployment list (deployments/json/1.json), confirmed by
|
|
591
|
+
// calls: QuoterV2 code 8273 bytes, factory 24535 bytes, USDC|USDT|DAI to WETH pools exist at all four tiers and
|
|
592
|
+
// quotes are live. USDe has NO pool on any tier here - and that must be said in words, not shown as a zero.
|
|
479
593
|
uniswap: {
|
|
480
594
|
factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
481
595
|
quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
|
|
@@ -491,9 +605,9 @@ export const CHAINS = [
|
|
|
491
605
|
caip2: "eip155:8453",
|
|
492
606
|
native: { symbol: "ETH", decimals: 18, usd: 3212.4 },
|
|
493
607
|
rpcUrl: "https://mainnet.base.org",
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
608
|
+
// The Chainlink feed for the native price. The addresses are confirmed by a chain CALL (description() matched
|
|
609
|
+
// the pair, the value is fresh and within a sensible range). With no feed there is no price: the interface must
|
|
610
|
+
// say "demo", not show yesterday's constant.
|
|
497
611
|
priceFeed: { address: "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", pair: "ETH / USD", decimals: 8, min: 100, max: 20000 },
|
|
498
612
|
explorerTx: "https://basescan.org/tx/",
|
|
499
613
|
explorerAddr: "https://basescan.org/address/",
|
|
@@ -507,9 +621,9 @@ export const CHAINS = [
|
|
|
507
621
|
{ symbol: "DAI", address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", decimals: 18, usd: 1, cls: "ar-token-dai" },
|
|
508
622
|
{ symbol: "USDe", address: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", decimals: 18, usd: 1, cls: "ar-token-usde" },
|
|
509
623
|
],
|
|
510
|
-
// DEX
|
|
511
|
-
//
|
|
512
|
-
//
|
|
624
|
+
// THERE IS NO DEX LEG ON THIS NETWORK, and that is NOT "we did not check" but the product state: the network is
|
|
625
|
+
// coming soon, it has no uniswap block and no pools are searched here. What that means for the screen: it must
|
|
626
|
+
// say so in words (see mock/market.js: dexRoute), not show a dummy pool.
|
|
513
627
|
},
|
|
514
628
|
{
|
|
515
629
|
id: "bsc",
|
|
@@ -519,8 +633,8 @@ export const CHAINS = [
|
|
|
519
633
|
caip2: "eip155:56",
|
|
520
634
|
native: { symbol: "BNB", decimals: 18, usd: 612.5 },
|
|
521
635
|
rpcUrl: "https://bsc-dataseed.binance.org",
|
|
522
|
-
//
|
|
523
|
-
//
|
|
636
|
+
// The BNB price: the Chainlink feed is not confirmed by a call - so far the network shows "demo", not an
|
|
637
|
+
// invented number. It is added here once the address is verified like the others.
|
|
524
638
|
explorerTx: "https://bscscan.com/tx/",
|
|
525
639
|
explorerAddr: "https://bscscan.com/address/",
|
|
526
640
|
explorerName: "bscscan",
|
|
@@ -528,35 +642,35 @@ export const CHAINS = [
|
|
|
528
642
|
routeType: "atomic",
|
|
529
643
|
escrow: { address: null, mode: "simulated" },
|
|
530
644
|
tokens: [
|
|
531
|
-
//
|
|
532
|
-
// DAI
|
|
645
|
+
// Note: on BSC USDT and USDC have 18 decimals (not 6, as on Ethereum L1 and L2).
|
|
646
|
+
// DAI and USDe are not added on BNB Chain: the demo does not have them there.
|
|
533
647
|
{ symbol: "USDT", address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18, usd: 1, cls: "ar-token-usdt" },
|
|
534
648
|
{ symbol: "USDC", address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", decimals: 18, usd: 1, cls: "ar-token-usdc" },
|
|
535
649
|
],
|
|
536
|
-
// DEX
|
|
537
|
-
//
|
|
538
|
-
//
|
|
650
|
+
// THERE IS NO DEX LEG ON THIS NETWORK: it is coming soon, has no uniswap block, and no pools are searched here.
|
|
651
|
+
// This network's wrapped native is WBNB (its native is BNB, not ETH), but while the network is off the registry
|
|
652
|
+
// has none of its addresses: no point confirming what we do not use.
|
|
539
653
|
},
|
|
540
654
|
{
|
|
541
655
|
id: "bitcoin",
|
|
542
656
|
name: "Bitcoin",
|
|
543
657
|
vm: "bitcoin",
|
|
544
|
-
chainId: 0, //
|
|
545
|
-
caip2: "bip122:000000000019d6689c085ae165831e93", // bip122 +
|
|
658
|
+
chainId: 0, // Bitcoin has no EVM chainId; 0 is the SLIP-44 coin type and does not clash with EVM networks
|
|
659
|
+
caip2: "bip122:000000000019d6689c085ae165831e93", // bip122 + the first 32 chars of the zero block hash
|
|
546
660
|
native: { symbol: "BTC", decimals: 8, usd: 96000 },
|
|
547
|
-
//
|
|
661
|
+
// Bitcoin has no JSON-RPC: this is a block explorer's REST API. The demo does not call it.
|
|
548
662
|
rpcUrl: "https://blockstream.info/api",
|
|
549
663
|
explorerTx: "https://mempool.space/tx/",
|
|
550
664
|
explorerAddr: "https://mempool.space/address/",
|
|
551
665
|
explorerName: "mempool.space",
|
|
552
666
|
gasUsd: 1.2,
|
|
553
667
|
routeType: "atomic",
|
|
554
|
-
//
|
|
668
|
+
// In Bitcoin there is one asset - BTC itself: the protocol has no contract tokens.
|
|
555
669
|
tokens: [],
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
670
|
+
// An escrow contract cannot exist here, and that is not a gap: Bitcoin uses a native atomic swap (HTLC/adaptor
|
|
671
|
+
// signatures in the script) instead of a contract. So we say plainly what it settles with and do not pass the
|
|
672
|
+
// absence of a contract off as "coming soon". The demo cannot do Bitcoin yet (it connects an EVM wallet), so
|
|
673
|
+
// escrow: null - the network is listed but not selectable.
|
|
560
674
|
escrow: null,
|
|
561
675
|
settlement: "native-atomic-swap",
|
|
562
676
|
},
|
|
@@ -564,7 +678,7 @@ export const CHAINS = [
|
|
|
564
678
|
id: "tron",
|
|
565
679
|
name: "Tron",
|
|
566
680
|
vm: "tron",
|
|
567
|
-
chainId: 728126428, // network id: eth_chainId
|
|
681
|
+
chainId: 728126428, // network id: eth_chainId on TronGrid answers 0x2b6653dc
|
|
568
682
|
caip2: "tron:0x2b6653dc",
|
|
569
683
|
native: { symbol: "TRX", decimals: 6, usd: 0.28 },
|
|
570
684
|
rpcUrl: "https://api.trongrid.io",
|
|
@@ -573,11 +687,11 @@ export const CHAINS = [
|
|
|
573
687
|
explorerName: "tronscan",
|
|
574
688
|
gasUsd: 0.35,
|
|
575
689
|
routeType: "atomic",
|
|
576
|
-
//
|
|
577
|
-
//
|
|
690
|
+
// NOT EVM: today the demo connects only an EVM wallet (EIP-1193) and can neither read balances here nor sign.
|
|
691
|
+
// So the network is listed but not selectable - escrow: null.
|
|
578
692
|
escrow: null,
|
|
579
693
|
tokens: [
|
|
580
|
-
// TRC-20: symbol()/decimals()
|
|
694
|
+
// TRC-20: symbol()/decimals() verified through TronGrid triggerconstantcontract (USDT, 6).
|
|
581
695
|
{ symbol: "USDT", address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6, usd: 1, cls: "ar-token-usdt" },
|
|
582
696
|
],
|
|
583
697
|
},
|
|
@@ -585,7 +699,7 @@ export const CHAINS = [
|
|
|
585
699
|
id: "solana",
|
|
586
700
|
name: "Solana",
|
|
587
701
|
vm: "solana",
|
|
588
|
-
chainId: 101, //
|
|
702
|
+
chainId: 101, // Solana mainnet-beta id; below is the CAIP-2 with the genesis hash
|
|
589
703
|
caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
|
|
590
704
|
native: { symbol: "SOL", decimals: 9, usd: 198.4 },
|
|
591
705
|
rpcUrl: "https://api.mainnet-beta.solana.com",
|
|
@@ -594,10 +708,10 @@ export const CHAINS = [
|
|
|
594
708
|
explorerName: "solscan",
|
|
595
709
|
gasUsd: 0.002,
|
|
596
710
|
routeType: "atomic",
|
|
597
|
-
//
|
|
711
|
+
// NOT EVM: see the note at Tron.
|
|
598
712
|
escrow: null,
|
|
599
713
|
tokens: [
|
|
600
|
-
// SPL
|
|
714
|
+
// SPL mint: verified by getAccountInfo (owner Tokenkeg...) and getTokenSupply (decimals 6).
|
|
601
715
|
{ symbol: "USDC", address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
602
716
|
{ symbol: "USDT", address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", decimals: 6, usd: 1, cls: "ar-token-usdt" },
|
|
603
717
|
],
|
|
@@ -606,9 +720,9 @@ export const CHAINS = [
|
|
|
606
720
|
id: "monad",
|
|
607
721
|
name: "Monad",
|
|
608
722
|
vm: "evm",
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
//
|
|
723
|
+
// The demo cannot do this network yet: in the UI it behaves like Solana - grey and not selectable, with
|
|
724
|
+
// "not supported yet" written honestly in the row. The network's plan is not cancelled - the mark comes off
|
|
725
|
+
// when a contract and tokens appear.
|
|
612
726
|
unsupported: true,
|
|
613
727
|
chainId: 143,
|
|
614
728
|
caip2: "eip155:143",
|
|
@@ -621,21 +735,21 @@ export const CHAINS = [
|
|
|
621
735
|
routeType: "atomic",
|
|
622
736
|
escrow: { address: null, mode: "simulated" },
|
|
623
737
|
tokens: [
|
|
624
|
-
// USDC (Circle)
|
|
738
|
+
// USDC (Circle) and USDT0: symbol()/decimals() verified by a call on the Monad network (chainId 143).
|
|
625
739
|
{ symbol: "USDC", address: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
626
740
|
{ symbol: "USDT", onChainSymbol: "USDT0", address: "0xe7cd86e13AC4309349F30B3435a9d337750fC82D", decimals: 6, usd: 1, cls: "ar-token-usdt" },
|
|
627
741
|
],
|
|
628
|
-
// DEX
|
|
629
|
-
//
|
|
630
|
-
//
|
|
742
|
+
// NO DEX LEG HERE: the network is coming soon, pools are not searched and addresses are not written into the
|
|
743
|
+
// registry. No point keeping an address in the interface that cannot be used: what we measured earlier simply
|
|
744
|
+
// stops being checked, and keeping it as "data" would be a lie.
|
|
631
745
|
},
|
|
632
746
|
{
|
|
633
747
|
id: "hyperevm",
|
|
634
748
|
name: "Hyperliquid EVM",
|
|
635
749
|
vm: "evm",
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
750
|
+
// The demo cannot do this network yet: in the UI it behaves like Solana - grey and not selectable, with
|
|
751
|
+
// "not supported yet" written honestly in the row. The network's plan is not cancelled - the mark comes off
|
|
752
|
+
// when a contract and tokens appear.
|
|
639
753
|
unsupported: true,
|
|
640
754
|
chainId: 999,
|
|
641
755
|
caip2: "eip155:999",
|
|
@@ -648,37 +762,37 @@ export const CHAINS = [
|
|
|
648
762
|
routeType: "atomic",
|
|
649
763
|
escrow: { address: null, mode: "simulated" },
|
|
650
764
|
tokens: [
|
|
651
|
-
// USDC
|
|
765
|
+
// USDC and USDT0 (the contract's symbol = "USD₮0"): verified by calls on the network itself (chainId 999).
|
|
652
766
|
{ symbol: "USDC", address: "0xb88339CB7199b77E23DB6E890353E22632Ba630f", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
|
653
767
|
{ symbol: "USDT", onChainSymbol: "USD₮0", address: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", decimals: 6, usd: 1, cls: "ar-token-usdt" },
|
|
654
768
|
],
|
|
655
|
-
// DEX
|
|
769
|
+
// NO DEX LEG HERE: the network is coming soon, pools are not searched and addresses are not written into the registry.
|
|
656
770
|
},
|
|
657
771
|
];
|
|
658
772
|
|
|
659
773
|
export const DEFAULT_CHAIN = "arbitrum-sepolia";
|
|
660
774
|
|
|
661
|
-
//
|
|
775
|
+
// THE ALLOW-LIST OF PROVIDERS - INTERFACE POLICY, NOT A CHAIN CHECK.
|
|
662
776
|
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
777
|
+
// The chain answers a DIFFERENT question: whose key signed the quote (the maker registry, providerOf - the core
|
|
778
|
+
// reads it, sdk/src/quotes.mjs). THIS list is the interface's decision about whom it trades with at all. A provider
|
|
779
|
+
// whose address the chain confirms but who is not named here is refused (the code `quote-provider-not-allowed`), and
|
|
780
|
+
// the person sees that BEFORE signing: the quote shows the fee, but its issuance will not happen.
|
|
667
781
|
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
// (`ARRAKIS_ALLOWED_PROVIDERS`,
|
|
671
|
-
//
|
|
782
|
+
// WHY A SEPARATE LIST AND NOT A READ FROM THE BACKEND. Selection is the interface's business: it changes here,
|
|
783
|
+
// without redeploying contracts or editing someone else's node. The backend has the same kind of list
|
|
784
|
+
// (`ARRAKIS_ALLOWED_PROVIDERS`, in the backend env) - TWO independent steps of one decision, not a copy: the page
|
|
785
|
+
// guards itself even if the service is configured differently.
|
|
672
786
|
//
|
|
673
|
-
//
|
|
674
|
-
// "
|
|
675
|
-
//
|
|
787
|
+
// THE ADDRESSES ARE LOWERCASE, as the quote gives them (field `provider`). A network with no entry here means
|
|
788
|
+
// "no list set" (the policy does not apply), NOT "let nobody in": otherwise a network without configuration would
|
|
789
|
+
// stall entirely. A network's list needs filling once a maker is registered there.
|
|
676
790
|
export const ALLOWED_PROVIDERS = {
|
|
677
791
|
"local-anvil": ["0x60af2ad4963c738aa6ac16d6cb0af1982508ba21"],
|
|
678
792
|
};
|
|
679
793
|
|
|
680
|
-
//
|
|
681
|
-
//
|
|
794
|
+
// The allow-list for a network - or null if it is not set (no policy). The core accepts both an array and a
|
|
795
|
+
// predicate; here an array is returned in lowercase: the list is what is visible and changed in one place.
|
|
682
796
|
export function allowedProvidersFor(chainOrId) {
|
|
683
797
|
const id = typeof chainOrId === "string" ? chainOrId : chainOrId && chainOrId.id;
|
|
684
798
|
const list = ALLOWED_PROVIDERS[id];
|
|
@@ -686,12 +800,11 @@ export function allowedProvidersFor(chainOrId) {
|
|
|
686
800
|
return list.map((a) => String(a).toLowerCase());
|
|
687
801
|
}
|
|
688
802
|
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
// развернул, - своей фабрикой провайдер попадает в тот же список.
|
|
803
|
+
// THE LIST OF KNOWN FACTORY BUILDS - INTERFACE POLICY, next to allowedProviders. The value is the extcodehash
|
|
804
|
+
// (keccak256 of the code) of a factory the interface will accept: the factory comes from the PROVIDER REGISTRY
|
|
805
|
+
// record, and its code is compared against this list. Empty for a network = no policy (the factory must still be
|
|
806
|
+
// named by the record and match the signed quote). Filled at deploy: the factory has no immutables, so one build
|
|
807
|
+
// gives the same hash to anyone who deployed it - a provider's own factory lands in the same list.
|
|
695
808
|
export const KNOWN_FACTORY_CODES = {};
|
|
696
809
|
|
|
697
810
|
export function knownFactoryCodesFor(chainOrId) {
|
|
@@ -701,10 +814,10 @@ export function knownFactoryCodesFor(chainOrId) {
|
|
|
701
814
|
return list.map((h) => String(h).toLowerCase());
|
|
702
815
|
}
|
|
703
816
|
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
//
|
|
817
|
+
// THE LIST OF KNOWN ESCROW-IMPLEMENTATION BUILDS - INTERFACE POLICY, at the network where the factory address
|
|
818
|
+
// (escrow.address) lives. Its code hash is a PROTOCOL CONSTANT (the implementation has no immutables), but the
|
|
819
|
+
// policy may be unset: a network without an implementationCodeHash field has NONE (an old factory does not give the
|
|
820
|
+
// pair). Empty = no policy; non-empty = the interface trades only through factories leading to this build.
|
|
708
821
|
export function knownImplementationCodesFor(chainOrId) {
|
|
709
822
|
const id = typeof chainOrId === "string" ? chainOrId : chainOrId && chainOrId.id;
|
|
710
823
|
const chain = CHAINS.find((c) => c.id === id) || chainById(id);
|
|
@@ -721,7 +834,7 @@ export function chainByChainId(chainId) {
|
|
|
721
834
|
return CHAINS.find((c) => c.chainId === Number(chainId)) || null;
|
|
722
835
|
}
|
|
723
836
|
|
|
724
|
-
//
|
|
837
|
+
// A network's accepted tokens: native first, then contract ones.
|
|
725
838
|
export function tokensOf(chainId) {
|
|
726
839
|
const c = chainById(chainId);
|
|
727
840
|
return [nativeOf(chainId), ...c.tokens];
|
|
@@ -740,7 +853,15 @@ export function nativeOf(chainId) {
|
|
|
740
853
|
};
|
|
741
854
|
}
|
|
742
855
|
|
|
743
|
-
//
|
|
856
|
+
// A network's WRAPPED native token (WETH/WBNB...), or null. It is what the liquidity leg settles (KyberSwap Limit
|
|
857
|
+
// Order trades ERC20 against ERC20 and cannot deliver native) and what the native leg sells to buy native coin: the
|
|
858
|
+
// engine's composed route (www/js/evm/asyncExec.js) turns the token into this wrapped token first, then into native.
|
|
859
|
+
export function wrappedNativeOf(chainId) {
|
|
860
|
+
const c = chainById(chainId);
|
|
861
|
+
return (c.tokens || []).find((t) => t && t.peg === "native") || null;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// A token by its composite id "<chainId>:<SYMBOL>" or by two arguments.
|
|
744
865
|
export function tokenById(idOrChainId, symbolMaybe) {
|
|
745
866
|
let chainId = idOrChainId;
|
|
746
867
|
let symbol = symbolMaybe;
|
|
@@ -752,137 +873,126 @@ export function tokenById(idOrChainId, symbolMaybe) {
|
|
|
752
873
|
}
|
|
753
874
|
|
|
754
875
|
|
|
755
|
-
// Monero
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
// Получатель ордера (claimer) для ТЕСТОВ на тестовых сетях.
|
|
876
|
+
// The demo's Monero network. networkType=mainnet because monero-js (the vendored mnemonic/key/address library)
|
|
877
|
+
// supports only mainnet address prefixes (see the vendored readme). The monero-ts mode (WebAssembly wallet2) also
|
|
878
|
+
// handles stagenet.
|
|
879
|
+
// The order recipient (claimer) for TESTS on test networks.
|
|
760
880
|
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
881
|
+
// IMPORTANT: the contract requires locker and claimer to differ, so a payment address must not be given here -
|
|
882
|
+
// the transaction would be rejected. The connected wallet pays (anyone), while the funds are claimed by whoever is
|
|
883
|
+
// recorded here, presenting the order secret.
|
|
884
|
+
// This is a TEST stub: in production the recipient address comes from the maker's RFQ node with the quote.
|
|
765
885
|
export const TEST_CLAIMER = "0xc14C7841C7d6350859474dD6DF8dC9672047A6A0";
|
|
766
886
|
|
|
767
887
|
export const MONERO = {
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
//
|
|
888
|
+
// The network in which the demo generates its demo wallet and recipient address. It must match XMR_NETWORKS
|
|
889
|
+
// (below) and the chain the node is connected to: an address of another network in the recipient field means XMR
|
|
890
|
+
// sent nowhere. A check verifies the match.
|
|
771
891
|
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",
|
|
892
|
+
confirmTarget: 10, // confirmations before claim (the litepaper example)
|
|
893
|
+
blockTimeSim: 120_000, // 2 minutes per block, in sim time
|
|
894
|
+
blockTimeReal: 120_000, // the real Monero mainnet target
|
|
895
|
+
// The Monero node. Its address is set in ONE named place (see ENDPOINTS) and nowhere else: the demo has no
|
|
896
|
+
// parameters (no ?rpc=, no localStorage) and no RPC switching is planned. The node is needed only for the network
|
|
897
|
+
// status and live-confirmation mode; keys, balances and withdrawals are not requested through it.
|
|
898
|
+
// A DEPLOYMENT sets the node address in ENDPOINTS; on the local stand the page must reach OUR node - the
|
|
899
|
+
// production proxy may be unavailable (502), and the network height is one source of the recovery file's scan
|
|
900
|
+
// height, i.e. without a node the file reaches its owner without a height.
|
|
901
|
+
node: ENDPOINTS.moneroNode,
|
|
786
902
|
nodePollMs: 30_000,
|
|
787
903
|
};
|
|
788
904
|
|
|
789
905
|
// ---------------------------------------------------------------------------
|
|
790
|
-
//
|
|
906
|
+
// The backend (app/): swap sessions and watching the Monero leg.
|
|
791
907
|
//
|
|
792
|
-
// API
|
|
793
|
-
//
|
|
794
|
-
// с флагом --api=http://127.0.0.1:8787 (см. tools/serve.mjs) - тоже без правки политики.
|
|
908
|
+
// The API lives on the same domain at /api/ (Angie proxies to 127.0.0.1:8787), so CSP needs no edit - connect-src
|
|
909
|
+
// 'self' covers it. Locally the same path is served with an --api flag - also without touching the policy.
|
|
795
910
|
// ---------------------------------------------------------------------------
|
|
796
911
|
export const API = {
|
|
797
912
|
base: "/api",
|
|
798
|
-
//
|
|
913
|
+
// The Monero-leg source: 'app' - the real backend over HTTP, 'mock' - the demo simulator with no network.
|
|
799
914
|
xmrSource: "app",
|
|
800
|
-
pollMs: 5_000, //
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
915
|
+
pollMs: 5_000, // how often the front end polls a swap session
|
|
916
|
+
// HOW MANY PROVIDERS TO SHOW in the price block. A list of dozens of rows is unreadable, and a person cannot
|
|
917
|
+
// compare more than five prices anyway - he picks the best. The rest are not silenced: a summary line ("and N
|
|
918
|
+
// more") tells of their existence.
|
|
804
919
|
maxProviders: 5,
|
|
805
|
-
timeoutMs: 60_000, //
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
920
|
+
timeoutMs: 60_000, // One request's timeout: a hung backend must not hang the screen. 60 s, not 10: creating the
|
|
921
|
+
// deal wallet is the only LONG path - it scans the chain from the restore height - and the old 10 s cut off a
|
|
922
|
+
// WORKING request. From outside it looked like "Swap wallet unavailable". The deadline was raised twice
|
|
923
|
+
// (8 -> 10 -> 60): a short timeout here is not a guard but a false refusal.
|
|
809
924
|
};
|
|
810
925
|
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
//
|
|
816
|
-
//
|
|
817
|
-
// stagenet (префикс 5), и в одном UI соседствовали две цепи. Смешение цепей в свопе означает
|
|
818
|
-
// потерянные деньги (см. monero/address.js), поэтому теперь и мок, и живой режим - stagenet.
|
|
926
|
+
// The Monero network for each source. The backend on the server works with a stagenet wallet (monero-wallet-rpc is
|
|
927
|
+
// up on a stagenet node), so in 'app' mode the swap address must be a stagenet one: otherwise wallet-rpc cannot
|
|
928
|
+
// create a watch-only wallet for it. The demo's mock mode was historically mainnet - addresses from the demo examples.
|
|
929
|
+
// ONE CHAIN FOR THE WHOLE DEMO. The mock mode used to pretend to be mainnet, which only hurt: mock addresses looked
|
|
930
|
+
// like mainnet (prefix 4) while the live node is stagenet (prefix 5), and two chains sat side by side in one UI.
|
|
931
|
+
// Mixing chains in a swap means lost money (see monero/address.js), so now both mock and live are stagenet.
|
|
819
932
|
//
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
933
|
+
// PLUS THE FAMILIAR NAME REGTEST. The app/mock keys answer "which network this source's wallet is on". A third
|
|
934
|
+
// source entry has none: fakechain is the official name of monerod's regtest mode, which the CI contour node calls
|
|
935
|
+
// itself. It is needed so the network name is ACCEPTED: the core compares xmrNetwork against the engine's network
|
|
936
|
+
// list (sdk/src/config.mjs, STANDARD_XMR_NETWORKS), and the core's wallet adapter takes its network list FROM THIS
|
|
937
|
+
// registry (sdk/src/adapters/monero-wallet.mjs). The demo's wallet stays stagenet.
|
|
825
938
|
export const XMR_NETWORKS = { app: "stagenet", mock: "stagenet", fakechain: "fakechain" };
|
|
826
939
|
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
// действительно поднята, не должны расходиться.
|
|
940
|
+
// The sweep page: a separate domain with its own (stricter) policy and a WASM wallet. The user makes the XMR
|
|
941
|
+
// withdrawal there, from his recovery file - the demo does not sign for him.
|
|
942
|
+
// Where the link to the withdrawal page leads. In production - the production domain, on the local stand - its own
|
|
943
|
+
// address (its own node and backend there). A separate function so the rule is one: the link and where the page is
|
|
944
|
+
// actually served must not diverge.
|
|
833
945
|
export function sweepOrigin() {
|
|
834
946
|
const h = typeof location !== "undefined" ? location.hostname : "";
|
|
835
947
|
return /^(localhost|127\.0\.0\.1|\[::1\])$/.test(h) || /\.local$/.test(h) ? SWEEP.localOrigin : SWEEP.origin;
|
|
836
948
|
}
|
|
837
949
|
|
|
838
950
|
export const SWEEP = {
|
|
839
|
-
origin:
|
|
951
|
+
origin: ENDPOINTS.sweepOrigin,
|
|
840
952
|
localOrigin: "http://localhost:5195",
|
|
841
953
|
};
|
|
842
954
|
|
|
843
955
|
// ---------------------------------------------------------------------------
|
|
844
|
-
// EVM
|
|
845
|
-
//
|
|
846
|
-
// (это не секрет: он уходит в QR и в relay). Метаданные показываются пользователю в кошельке.
|
|
956
|
+
// The EVM wallet: WalletConnect (Reown). projectId is the app's public identifier, visible to any site with
|
|
957
|
+
// WalletConnect (not a secret: it goes into the QR and the relay). The metadata is shown to the user in the wallet.
|
|
847
958
|
// ---------------------------------------------------------------------------
|
|
848
959
|
export const EVM = {
|
|
849
960
|
projectId: "18c13c05b6c0ab011ffff7cee0700e2d",
|
|
850
961
|
metadata: {
|
|
851
|
-
name: "
|
|
962
|
+
name: "NinseiSwap",
|
|
852
963
|
description: "Non-custodial XMR <-> EVM atomic swap. Demo build: real wallet, real balances, no real funds move.",
|
|
853
|
-
url:
|
|
964
|
+
url: PAGE_ORIGIN,
|
|
854
965
|
icons: [],
|
|
855
966
|
},
|
|
856
967
|
};
|
|
857
968
|
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
//
|
|
872
|
-
// реальная секунда равна одной sim-минуте.
|
|
969
|
+
// THERE IS NO FEE RATE HERE - AND THERE MUST NOT BE. The only source of truth is the SIGNED QUOTE. The rate, amount
|
|
970
|
+
// and recipient of the fee are part of the order terms; the factory no longer has feeBps()/feeFor() (the router was
|
|
971
|
+
// removed), and the node does not compare its setting with the chain - it SIGNS it, while the amount comes in the
|
|
972
|
+
// quote and is checked by the formula mirror (www/js/evm/fees.js, rfq/fundEscrow.mjs). A copy in the config would
|
|
973
|
+
// diverge from the signed set - and it would surface at signing, not here. The record with a rate (0.003) was
|
|
974
|
+
// removed on 28 September 2026; the move to the quote was the fee-in-order stage.
|
|
975
|
+
|
|
976
|
+
// Mock makers. The spread is what a maker keeps; the Ninsei fee is counted separately.
|
|
977
|
+
// THE LIST OF INVENTED MAKERS IS REMOVED. It used to hold Makers A/B/C with a markup, uptime, reserve and delay -
|
|
978
|
+
// all of it reaching the interface as facts. We do not measure someone else's uptime, we do not know their reserve,
|
|
979
|
+
// and a provider forms its own markup and shows it only in its price. Now quotes come from a provider through the
|
|
980
|
+
// backend (/api/rate), and there is nothing to invent next to them.
|
|
981
|
+
|
|
982
|
+
// Timings. All in sim milliseconds (see js/core/clock.js): at speed=60 one real second equals one sim minute.
|
|
873
983
|
export const TIMING = {
|
|
874
|
-
quoteValid: 12_000, //
|
|
984
|
+
quoteValid: 12_000, // quote lifetime (12 s in sim)
|
|
875
985
|
quoteRefresh: 5_000,
|
|
876
|
-
fundingMined: 6_000, // tx
|
|
877
|
-
makerLock: 30_000, //
|
|
986
|
+
fundingMined: 6_000, // the funding tx landed in a block
|
|
987
|
+
makerLock: 30_000, // the maker locks the XMR
|
|
878
988
|
xmrConfirm: MONERO.blockTimeSim,
|
|
879
|
-
readyWindow: 60 * 60_000, //
|
|
989
|
+
readyWindow: 60 * 60_000, // how long the user has to mark ready after the lock
|
|
880
990
|
makerClaim: 45_000,
|
|
881
991
|
sweep: 30_000,
|
|
882
|
-
t0Refund: 20 * 60_000, //
|
|
883
|
-
t1Refund: 40 * 60_000, //
|
|
884
|
-
watchtowerGrace: 5 * 60_000, // watchtower
|
|
885
|
-
makerReclaim: 10 * 60_000, //
|
|
992
|
+
t0Refund: 20 * 60_000, // after this the ETH can be refunded if the maker did not lock the XMR
|
|
993
|
+
t1Refund: 40 * 60_000, // after this the ETH can be refunded if the maker did not claim
|
|
994
|
+
watchtowerGrace: 5 * 60_000, // the watchtower refunds if the user left
|
|
995
|
+
makerReclaim: 10 * 60_000, // the maker takes the XMR back if the user did not mark ready
|
|
886
996
|
};
|
|
887
997
|
|
|
888
998
|
export const DEMO = {
|
|
@@ -890,15 +1000,15 @@ export const DEMO = {
|
|
|
890
1000
|
defaultSpeed: 60,
|
|
891
1001
|
scenarios: ["happy", "maker_no_lock", "user_no_ready", "maker_stall"],
|
|
892
1002
|
defaultScenario: "happy",
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
// mock -
|
|
1003
|
+
// There is no wallet mode any more: keys in the demo are computed by monero-js, the only path (the WASM wallet
|
|
1004
|
+
// monero-ts moved to the separate sweep domain - its own policy and COOP/COEP).
|
|
1005
|
+
// mock - confirmations by a sim timer; live - by the real Monero mainnet height
|
|
896
1006
|
chainModes: ["mock", "live"],
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
//
|
|
1007
|
+
// LIVE HEIGHT BY DEFAULT. "mock" means confirmations by a sandbox timer - a demo mode, and it is no longer
|
|
1008
|
+
// substituted by itself: a deal must be counted by the real chain height, else "confirmed" on screen means
|
|
1009
|
+
// nothing. Demo mode remains a choice, not a default.
|
|
900
1010
|
defaultChainMode: "live",
|
|
901
|
-
//
|
|
1011
|
+
// The Monero-leg source: 'app' - the real backend (see API.xmrSource), 'mock' - the simulator.
|
|
902
1012
|
xmrSources: ["app", "mock"],
|
|
903
1013
|
defaultXmrSource: "app",
|
|
904
1014
|
defaultChain: DEFAULT_CHAIN,
|