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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/engine/atomic/dleq2.js +86 -111
  3. package/engine/atomic/halfenc.js +58 -72
  4. package/engine/atomic/halves.js +31 -53
  5. package/engine/atomic/order-client.js +15 -16
  6. package/engine/atomic/order.js +44 -62
  7. package/engine/atomic/orderContext.js +26 -50
  8. package/engine/atomic/orderHalfCommit.js +17 -18
  9. package/engine/core/chainSource.js +54 -69
  10. package/engine/core/clock.js +11 -12
  11. package/engine/core/config.js +554 -449
  12. package/engine/core/finality.js +34 -53
  13. package/engine/core/format.js +35 -36
  14. package/engine/core/preSignGuards.js +109 -117
  15. package/engine/core/rfqSource.js +57 -75
  16. package/engine/core/sellWindow.js +23 -24
  17. package/engine/core/store.js +20 -21
  18. package/engine/core/swap.js +242 -243
  19. package/engine/evm/amounts.js +50 -51
  20. package/engine/evm/asyncExec.js +577 -0
  21. package/engine/evm/asyncLegs.js +308 -0
  22. package/engine/evm/asyncRoute.js +179 -0
  23. package/engine/evm/auth.js +41 -48
  24. package/engine/evm/claimGas.js +28 -47
  25. package/engine/evm/depositor.js +39 -43
  26. package/engine/evm/dex.js +266 -267
  27. package/engine/evm/escrow.js +113 -129
  28. package/engine/evm/factory.js +71 -77
  29. package/engine/evm/fees.js +41 -42
  30. package/engine/evm/funding.js +160 -162
  31. package/engine/evm/gasReserve.js +67 -36
  32. package/engine/evm/index.js +46 -53
  33. package/engine/evm/injected.js +34 -42
  34. package/engine/evm/permit.js +129 -56
  35. package/engine/evm/prices.js +30 -45
  36. package/engine/evm/session.js +188 -189
  37. package/engine/mock/chain.js +17 -18
  38. package/engine/monero/address.js +33 -48
  39. package/engine/monero/halvesSweep.js +25 -43
  40. package/engine/monero/node.js +14 -15
  41. package/engine/monero/scan.js +82 -95
  42. package/engine/monero/swapKeys.js +30 -33
  43. package/engine/monero/unlock.js +17 -20
  44. package/engine/monero/wallet.js +32 -42
  45. package/engine/recovery/recoveryFile.js +154 -161
  46. package/engine/recovery/restore.js +50 -61
  47. package/engine/sdk/bridge.js +244 -234
  48. package/engine/ui/dom.js +4 -5
  49. package/engine/ui/walletModal.js +20 -31
  50. package/index.d.ts +8 -0
  51. package/package.json +1 -1
  52. package/src/adapters/evm-wallet.mjs +16 -0
  53. package/src/adapters/monero-wallet.mjs +1 -1
  54. package/src/adapters/monero.mjs +1 -1
  55. package/src/adapters/node-test-evm-wallet.mjs +5 -5
  56. package/src/config.mjs +1 -1
  57. package/src/engine.mjs +4 -0
  58. package/src/legs/cow-spec.mjs +254 -0
  59. package/src/legs/cow.mjs +315 -0
  60. package/src/legs/evm.mjs +86 -2
  61. package/src/legs/kyber-spec.mjs +265 -0
  62. package/src/legs/kyber.mjs +425 -0
  63. package/src/legs/shape.mjs +35 -0
  64. package/src/legs/uniswaprelay-spec.mjs +541 -0
  65. package/src/legs/uniswaprelay.mjs +422 -0
  66. package/src/legs/uniswapx-spec.mjs +329 -0
  67. package/src/legs/uniswapx.mjs +362 -0
  68. package/src/quotes.mjs +1 -1
  69. package/src/swaps.mjs +1 -1
@@ -1,51 +1,48 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/factory.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
3
- // tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
2
+ // Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
4
3
  // Everything below this header is identical to the source.
5
4
 
6
- // Вызовы контракта-фабрики эскроу ArrakisSwap.
5
+ // Calls to the NinseiSwap escrow factory contract.
7
6
  //
8
- // Библиотек для EVM в проекте нет и не добавляем. ОБЕ ФУНКЦИИ ФАБРИКИ ТЕПЕРЬ ПРИНИМАЮТ ПОДПИСАННУЮ КОТИРОВКУ
9
- // (OrderQuote.Quote), подпись тремя словами (r, s, v) и три поля вносившего. Комиссия и адрес реестра - ЧАСТЬ
10
- // КОТИРОВКИ, а не настройка контракта: функций feeBps()/feeFor() у фабрики больше нет (роутер удалён,
11
- // решение #76), и спрашивать их не у чего. Все аргументы статические, поэтому calldata - селектор плюс слова
12
- // по 32 байта; собирается здесь, в одном месте, и проверяется tools/check-evm-abi.mjs.
7
+ // There are no EVM libraries in the project and we are not adding any. BOTH FACTORY FUNCTIONS NOW TAKE THE
8
+ // SIGNED QUOTE (OrderQuote.Quote), the signature as three words (r, s, v) and three depositor fields. The fee
9
+ // and the registry address are PART of the quote, not a contract setting: the factory no longer has feeBps()/
10
+ // feeFor() (the router was removed), so there is nothing to ask. All arguments are static, so calldata is a
11
+ // selector plus 32-byte words, built here in one place and checked mechanically.
13
12
  //
14
13
  // cast sig "predict((address,address,address,address,address,address,uint256,uint64,uint64,bytes32,bytes32,bytes32,uint256,uint256,uint256,address,uint64,uint256,uint256),bytes32,bytes32,uint8,bytes32,bytes32,bytes32)" -> 0x0824d205
15
- // cast sig "createOrderAndFund(<тот же tuple>,...)" -> 0x4703a9d3
14
+ // cast sig "createOrderAndFund(<same tuple>,...)" -> 0x4703a9d3
16
15
 
17
16
  export const FACTORY_METHODS = {
18
- // СОСТАВ КОТИРОВКИ ВЫРОС ПОЛЕМ xmrAmount (#110 шаг 3): XMR-сторона сделки входит в подписанный набор, поэтому
19
- // селекторы обеих прежних функций сменились вторично (прежде - от поля cashier, #103).
17
+ // THE QUOTE GREW THE xmrAmount FIELD: the XMR side of the deal is part of the signed set, so both selectors
18
+ // changed a second time (earlier for the cashier field).
20
19
  predict: "0x0824d205",
21
- // создать эскроу И внести деньги ОДНОЙ транзакцией (перевод value вместе с вызовом).
20
+ // create the escrow AND deposit funds in ONE transaction (a value transfer with the call).
22
21
  createOrderAndFund: "0x4703a9d3",
23
- // создать эскроу и внести деньги, когда ВНОСЯЩИЙ восстановлен ИЗ ПОДПИСИ над дайджестом котировки
24
- // (issue #97, часть A): транзакцию шлёт кто угодно, а `locker` записан подписавшим.
22
+ // create the escrow and deposit when the DEPOSITOR is recovered FROM THE SIGNATURE over the quote digest:
23
+ // anyone may send the transaction, and `locker` is set to the signer.
25
24
  createOrderAndFundByDepositor: "0x2acd187b",
26
25
  };
27
26
 
28
- // ТЕМА СОБЫТИЯ OrderCreated. Она выросла адресом реестра, провайдером и ключом подписи:
27
+ // The OrderCreated event topic. It grew the registry address, the provider and the signing key:
29
28
  // cast keccak "OrderCreated(address,address,address,bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint64,uint64,bytes32,address,address,address)"
30
- // Тема живёт в ДВУХ местах - здесь и в watchtower/scan.mjs, - и обе обязаны совпадать: разойтись молча они
31
- // не могут, сверку держит проверка ABI.
29
+ // The topic lives in TWO places and both must match; a check pins them.
32
30
  export const ORDER_CREATED_TOPIC =
33
31
  "0xa22cdabaed3cce4f51dde71626a108ffe9c7bc4216fd14d5e719f892e2fd8ab0";
34
32
 
35
- // ПОЛЯ КОТИРОВКИ В ПОРЯДКЕ КОНТРАКТА. Порядок - часть ABI: перестановка даёт другой вызов (чужую функцию
36
- // или ордер с чужими условиями). Список ОДИН на обе функции: разойтись им нечем.
33
+ // QUOTE FIELDS IN CONTRACT ORDER. The order is part of the ABI: a swap gives another call. One list for both
34
+ // functions, so they cannot diverge.
37
35
  export const QUOTE_FIELDS = [
38
36
  "provider", "quoteKey", "registry", "cashier", "locker", "claimer", "amount", "readyBy", "t1", "salt",
39
37
  "commitHalfClaimer", "edPointClaimer", "chainId", "feeBps", "fee", "feeRecipient", "validUntil", "nonce", "xmrAmount",
40
38
  ];
41
- // Поля вносившего котировка НЕ несёт: они уходят отдельными аргументами (входят в init-код эскроу).
39
+ // The depositor fields are NOT in the quote: they go as separate arguments (into the escrow init code).
42
40
  const LOCKER_HALF_FIELDS = ["commitHalfLocker", "edPointLocker", "edViewPointLocker"];
43
41
  const ADDR_FIELDS = ["provider", "quoteKey", "registry", "cashier", "locker", "claimer", "feeRecipient"];
44
42
  const B32_FIELDS = ["salt", "commitHalfClaimer", "edPointClaimer", "commitHalfLocker", "edPointLocker", "edViewPointLocker"];
45
43
 
46
- // Слово calldata: значение, выровненное вправо до 32 байт. Числа приходят десятичными (wei, сроки), а в
47
- // calldata обязаны быть шестнадцатеричными: дополнение нулями десятичных цифр кодировало бы их как hex и
48
- // уводило сумму в другое число. Поймано сверкой с эталоном Foundry.
44
+ // A calldata word: a value right-aligned to 32 bytes. Numbers arrive decimal (wei, deadlines) but must be hex in
45
+ // calldata: padding decimal digits as hex would change the number. Caught by comparison with a Foundry reference.
49
46
  export function word(value, bytes = 32) {
50
47
  const raw = String(value).trim();
51
48
  let hex;
@@ -55,34 +52,34 @@ export function word(value, bytes = 32) {
55
52
  } else if (/^[0-9]+$/.test(raw)) {
56
53
  hex = BigInt(raw).toString(16);
57
54
  } else {
58
- throw new Error("не число и не hex: " + value);
55
+ throw new Error("not a number and not hex: " + value);
59
56
  }
60
- if (!/^[0-9a-f]*$/.test(hex)) throw new Error("не hex: " + value);
61
- if (hex.length > bytes * 2) throw new Error(`значение не влезает в ${bytes} байт: ${value}`);
57
+ if (!/^[0-9a-f]*$/.test(hex)) throw new Error("not hex: " + value);
58
+ if (hex.length > bytes * 2) throw new Error(`value does not fit in ${bytes} bytes: ${value}`);
62
59
  return hex.padStart(bytes * 2, "0");
63
60
  }
64
61
 
65
- // ФОРМА ПРИВОДИТСЯ, А НЕ ТРЕБУЕТСЯ: точки и обязательства в записи движка лежат БЕЗ префикса 0x, а word()
66
- // требует префикс. Байты одни и те же, поэтому оба вида кодируются одинаково.
62
+ // FORM IS NORMALISED, NOT DEMANDED: points and commitments in the engine record sit WITHOUT the 0x prefix, while
63
+ // word() requires it. Same bytes, so both forms encode identically.
67
64
  const asHex32 = (v) => {
68
65
  const x = String(v === undefined || v === null ? "" : v).trim();
69
66
  return /^[0-9a-fA-F]{64}$/.test(x) ? "0x" + x.toLowerCase() : x;
70
67
  };
71
68
 
72
- // СЛОВА ПОДПИСАННОГО НАБОРА. Подпись - 65 байт r || s || v; v приводится к 27/28 (контракт принимает оба
73
- // соглашения). r и s - ПОЛНЫЕ 32 байта, v - тоже ПОЛНОЕ слово из 32 байт (ABI uint8 не бывает короче слова:
74
- // неполное слово сдвинуло бы хвост calldata и вызов ушёл бы на несуществующий селектор).
69
+ // WORDS OF THE SIGNED SET. The signature is 65 bytes r || s || v; v is normalised to 27/28 (the contract takes
70
+ // both). r and s are FULL 32 bytes, v is a FULL 32-byte word (ABI uint8 is never shorter: a short word would shift
71
+ // the calldata tail and the call would go to a non-existent selector).
75
72
  function quoteWords(quote) {
76
73
  for (const name of QUOTE_FIELDS) {
77
- if (quote[name] === undefined || quote[name] === null || quote[name] === "") throw new Error("в котировке нет поля: " + name);
74
+ if (quote[name] === undefined || quote[name] === null || quote[name] === "") throw new Error("the quote has no field: " + name);
78
75
  }
79
- // ФОРМА ПРОВЕРЯЕТСЯ, А НЕ ДОСТРАИВАЕТСЯ: короткий адрес или усечённый bytes32 молча выровнялся бы
80
- // нулями и ушёл в контракт ДРУГИМ значением.
76
+ // FORM IS CHECKED, NOT PADDED: a short address or a truncated bytes32 would be silently zero-padded and reach
77
+ // the contract as a DIFFERENT value.
81
78
  for (const name of ADDR_FIELDS) {
82
- if (!/^(0x)?[0-9a-fA-F]{40}$/.test(String(quote[name]).trim())) throw new Error("поле котировки " + name + " - не адрес");
79
+ if (!/^(0x)?[0-9a-fA-F]{40}$/.test(String(quote[name]).trim())) throw new Error("quote field " + name + " is not an address");
83
80
  }
84
81
  for (const name of B32_FIELDS) {
85
- if (!/^(0x)?[0-9a-fA-F]{64}$/.test(String(quote[name]).trim())) throw new Error("поле котировки " + name + " - не bytes32");
82
+ if (!/^(0x)?[0-9a-fA-F]{64}$/.test(String(quote[name]).trim())) throw new Error("quote field " + name + " is not bytes32");
86
83
  }
87
84
  const map = {
88
85
  provider: word(quote.provider, 32), quoteKey: word(quote.quoteKey, 32), registry: word(quote.registry, 32),
@@ -101,17 +98,17 @@ function quoteWords(quote) {
101
98
 
102
99
  function signatureWords(signature) {
103
100
  const hex = String(signature || "").replace(/^0x/, "");
104
- if (!/^[0-9a-fA-F]{130}$/.test(hex)) throw new Error("подпись котировки должна быть 65 байт: " + String(signature).slice(0, 20));
101
+ if (!/^[0-9a-fA-F]{130}$/.test(hex)) throw new Error("the quote signature must be 65 bytes: " + String(signature).slice(0, 20));
105
102
  const r = hex.slice(0, 64), s = hex.slice(64, 128);
106
103
  let v = parseInt(hex.slice(128, 130), 16);
107
104
  if (v < 27) v += 27;
108
105
  return word("0x" + r, 32) + word("0x" + s, 32) + word(v, 32);
109
106
  }
110
107
 
111
- // ОБЩАЯ ЧАСТЬ predict и createOrderAndFund.
108
+ // The common part of predict and createOrderAndFund.
112
109
  function lockerHalfWords(quote) {
113
110
  return LOCKER_HALF_FIELDS.map((name) => {
114
- if (quote[name] === undefined || quote[name] === null || quote[name] === "") throw new Error("в котировке нет поля вносившего: " + name);
111
+ if (quote[name] === undefined || quote[name] === null || quote[name] === "") throw new Error("the quote has no depositor field: " + name);
115
112
  return word(asHex32(quote[name]), 32);
116
113
  }).join("");
117
114
  }
@@ -120,45 +117,44 @@ function quoteCallArgs(quote) {
120
117
  return quoteWords(quote) + signatureWords(quote.signature) + lockerHalfWords(quote);
121
118
  }
122
119
 
123
- // ПРЕДСКАЗАТЬ АДРЕС. Состав аргументов совпадает с createOrderAndFund БАЙТ В БАЙТ (адрес - хеш кода
124
- // создания, и подпись в него входит).
120
+ // PREDICT THE ADDRESS. The argument set matches createOrderAndFund BYTE FOR BYTE (the address is the code hash of
121
+ // creation, and the signature is part of it).
125
122
  export function encodePredict(quote) {
126
123
  return FACTORY_METHODS.predict + quoteCallArgs(quote);
127
124
  }
128
125
 
129
- // СОЗДАТЬ И ВНЕСТИ. Вносившим становится msg.sender; его адрес уже внутри котировки, а фабрика сверяет его
130
- // с отправителем.
126
+ // CREATE AND DEPOSIT. The depositor becomes msg.sender; its address is already inside the quote and the factory
127
+ // checks it against the sender.
131
128
  export function encodeCreateOrderAndFund(quote) {
132
129
  return FACTORY_METHODS.createOrderAndFund + quoteCallArgs(quote);
133
130
  }
134
131
 
135
- // СОЗДАТЬ И ВНЕСТИ, когда вносящий восстановлен ИЗ ЕГО ПОДПИСИ над ДАЙДЖЕСТОМ КОТИРОВКИ (#97, часть A).
136
- // Транзакцию шлёт кто угодно, поэтому подписантов ДВОЕ: `quote.signature` - подпись провайдера (её
137
- // проверяет эскроу), `depositorSignature` - подпись ВНОСЯЩЕГО (её проверяет фабрика и требует, чтобы
138
- // восстановленный адрес совпал с `quote.locker`). Обе - над ОДНИМ дайджестом котировки.
132
+ // CREATE AND DEPOSIT when the depositor is recovered FROM ITS SIGNATURE over the quote digest. Anyone may send
133
+ // the transaction, so there are TWO signers: quote.signature (the provider, checked by the escrow) and
134
+ // depositorSignature (the depositor, checked by the factory, which requires the recovered address to equal
135
+ // quote.locker). Both over ONE digest.
139
136
  export function encodeCreateOrderAndFundByDepositor(quote, depositorSignature) {
140
137
  return FACTORY_METHODS.createOrderAndFundByDepositor + quoteWords(quote) +
141
138
  signatureWords(quote.signature) + signatureWords(depositorSignature) + lockerHalfWords(quote);
142
139
  }
143
140
 
144
- // Ответ predict() - один адрес в 32-байтовом слове.
141
+ // predict() answer - one address in a 32-byte word.
145
142
  export function decodeAddress(returnedHex) {
146
143
  const hex = String(returnedHex || "").replace(/^0x/, "");
147
- if (hex.length < 64) throw new Error("короткий ответ: " + returnedHex);
144
+ if (hex.length < 64) throw new Error("short answer: " + returnedHex);
148
145
  return "0x" + hex.slice(24, 64);
149
146
  }
150
147
 
151
148
 
152
149
  // ============================================================================================
153
- // РЕАЛИЗАЦИЯ ЭСКРОУ: ЕЁ АДРЕС И ХЕШ ЕЁ КОДА (issue #114, поправка 2).
154
- // С #114 эскроу каждого ордера - КЛОН EIP-1167 одной общей реализации, а адрес реализации и хеш её кода
155
- // лежат у фабрики (implementation() / implementationCodeHash()). ФАБРИКА при создании ордера проверяет эту
156
- // пару; КЛИЕНТ обязан проверить ТУ ЖЕ пару, иначе клон исполнит чужой код, а денежные пути "пройдут" впустую
157
- // (delegatecall на адрес без кода УСПЕШЕН). Здесь - только чтение полей и вердикт; вызовы к цепи делает
158
- // носитель (session.js: readContract/readCode), поэтому функция чистая и проверяется без сети.
150
+ // ESCROW IMPLEMENTATION: ITS ADDRESS AND CODE HASH. Every order escrow is an EIP-1167 CLONE of one shared
151
+ // implementation, and the implementation address and code hash live at the factory. The FACTORY checks the pair
152
+ // at order creation; the CLIENT must check the SAME pair, else the clone runs foreign code and the money paths
153
+ // "pass" for nothing (a delegatecall to an address without code SUCCEEDS). Here we only read fields and give a
154
+ // verdict; the chain calls are made by the carrier, so the function is pure.
159
155
  // ============================================================================================
160
156
 
161
- // Селекторы посчитаны инструментом, не вручную:
157
+ // Selectors are computed by a tool, not by hand:
162
158
  // cast sig "implementation()" -> 0x5c60da1b
163
159
  // cast sig "implementationCodeHash()" -> 0xbc0a3981
164
160
  export const FACTORY_READS = {
@@ -166,32 +162,32 @@ export const FACTORY_READS = {
166
162
  implementationCodeHash: "0xbc0a3981",
167
163
  };
168
164
 
169
- // Чтение поля фабрики без аргументов: calldata равен селектору.
165
+ // Reading a factory field with no arguments: calldata is the selector.
170
166
  export function encodeFactoryRead(name) {
171
167
  const sel = FACTORY_READS[name];
172
- if (typeof sel !== "string" || !/^0x[0-9a-f]{8}$/.test(sel)) throw new Error("нет такого чтения фабрики: " + name);
168
+ if (typeof sel !== "string" || !/^0x[0-9a-f]{8}$/.test(sel)) throw new Error("no such factory read: " + name);
173
169
  return sel;
174
170
  }
175
171
 
176
- // implementation() - один адрес в 32-байтовом слове.
172
+ // implementation() - one address in a 32-byte word.
177
173
  export function decodeImplementation(returnedHex) {
178
174
  return decodeAddress(returnedHex);
179
175
  }
180
176
 
181
- // implementationCodeHash() - одно слово bytes32.
177
+ // implementationCodeHash() - one bytes32 word.
182
178
  export function decodeImplementationCodeHash(returnedHex) {
183
179
  const hex = String(returnedHex || "").replace(/^0x/, "");
184
- if (hex.length < 64) throw new Error("короткий ответ implementationCodeHash(): " + returnedHex);
180
+ if (hex.length < 64) throw new Error("short answer from implementationCodeHash(): " + returnedHex);
185
181
  return "0x" + hex.slice(0, 64).toLowerCase();
186
182
  }
187
183
 
188
- // ВЕРДИКТ ПО РЕАЛИЗАЦИИ - те же три условия, что проверяет фабрика (и что закрывает поправки #114):
189
- // 1) адрес реализации назван и её КОД на цепи непуст: delegatecall на адрес без кода успешен, поэтому
190
- // клон без реализации принял бы ETH, а markReady/claim/refund "прошли" бы, ничего не сделав;
191
- // 2) хеш кода реализации равен recorded-хешу фабрики (сравнение делает сама фабрика через codehash);
192
- // 3) recorded-хеш входит в политику интерфейса (список известных сборок) - если она задана.
193
- // Если передан hashCode (keccak256 байтов кода), сверяется и живой код: тогда вердикт не доверяет одному
194
- // лишь ответу фабрики, а пересчитывает хеш из eth_getCode.
184
+ // VERDICT ON THE IMPLEMENTATION - the same three conditions the factory checks:
185
+ // 1) the implementation address is named and its CODE on chain is non-empty: a delegatecall to an address without
186
+ // code succeeds, so a clone without an implementation would take ETH and markReady/claim/refund would "pass";
187
+ // 2) the implementation code hash equals the factory recorded hash (compared by the factory via codehash);
188
+ // 3) the recorded hash is in the interface policy (a list of known builds), if set.
189
+ // If hashCode is given (keccak256 of the code bytes), the live code is compared too: the verdict then recomputes
190
+ // the hash from eth_getCode.
195
191
  export function verifyFactoryImplementation({ implementation, implementationCodeHash, code, hashCode, policy } = {}) {
196
192
  if (!implementation || !/^0x[0-9a-fA-F]{40}$/.test(String(implementation))) {
197
193
  return { ok: false, why: "implementation-missing" };
@@ -215,13 +211,11 @@ export function verifyFactoryImplementation({ implementation, implementationCode
215
211
  return { ok: true, codeHash: pinned };
216
212
  }
217
213
 
218
- // ЧТЕНИЕ ПАРЫ У ФАБРИКИ + ВЕРДИКТ - ОДНО МЕСТО НА ВСЕ НОСИТЕЛИ (SDK, узел, сторож). Читает пару полей
219
- // фабрики (implementation() / implementationCodeHash()) и КОД по адресу реализации, затем применяет тот же
220
- // вердикт, что и фабрика при создании ордера (verifyFactoryImplementation выше). Цепь инжектится: read -
221
- // это eth_call ({to,data}) -> hex, code - это eth_getCode (address) -> hex. Модуль остаётся чистым: ни
222
- // одного импорта, ни одного сетевого вызова, поэтому проверяется без сети. Носители подставляют СВОИ
223
- // чтения (SDK - evmCall/evmCode кошелька страницы, узел и сторож - свой RPC). hashCode, если передан,
224
- // пересчитывает живой код: тогда вердикт не доверяет одному лишь ответу фабрики.
214
+ // READING THE PAIR AT THE FACTORY + VERDICT - ONE PLACE FOR ALL CARRIERS (SDK, node, watchtower). It reads the
215
+ // factory fields (implementation()/implementationCodeHash()) and the CODE at the implementation address, then
216
+ // applies the same verdict the factory applies at order creation (verifyFactoryImplementation above). The chain
217
+ // is injected: read - eth_call({to,data}) -> hex, code - eth_getCode(address) -> hex. The module stays pure: no
218
+ // import, no network call. Carriers pass their OWN reads. hashCode, if given, recomputes the live code.
225
219
  export async function verifyFactoryImplementationOnChain({ factory, read, code, policy = null, hashCode = null } = {}) {
226
220
  if (!factory || !/^0x[0-9a-fA-F]{40}$/.test(String(factory))) {
227
221
  return { ok: false, why: "factory-missing" };
@@ -233,8 +227,8 @@ export async function verifyFactoryImplementationOnChain({ factory, read, code,
233
227
  implRaw = await read({ to: factory, data: FACTORY_READS.implementation });
234
228
  hashRaw = await read({ to: factory, data: FACTORY_READS.implementationCodeHash });
235
229
  } catch {
236
- // ЧТЕНИЕ ПАРЫ НЕ УДАЛОСЬ - ЭТО НАЗВАННЫЙ ОТКАЗ, А НЕ МОЛЧАНИЕ. Старая фабрика (без этих геттеров)
237
- // отвечает revert'ом: пара не прочитана, вердикта нет, и носитель обязан отказать.
230
+ // READING THE PAIR FAILED - A NAMED REFUSAL, NOT SILENCE. An old factory (without these getters) reverts:
231
+ // the pair is unread, there is no verdict, and the carrier must refuse.
238
232
  return { ok: false, why: "implementation-unreadable", factory: String(factory).toLowerCase() };
239
233
  }
240
234
  let implementation, implementationCodeHash;
@@ -1,90 +1,89 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/fees.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
3
- // tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
2
+ // Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
4
3
  // Everything below this header is identical to the source.
5
4
 
6
- // Комиссия ArrakisSwap: ЧАСТЬ ПОДПИСАННОЙ КОТИРОВКИ (OrderQuote v7), а не настройка контракта.
5
+ // The NinseiSwap fee: PART OF THE SIGNED QUOTE (OrderQuote v7), not a contract setting.
7
6
  //
8
- // ГДЕ ЖИВЁТ СТАВКА. Раньше её и получателя назначала ФАБРИКА (или роутер) в неизменяемых полях, и клиент
9
- // спрашивал их у цепи (feeBps()/feeRecipient()/feeFor()). Теперь этих функций у контракта НЕТ: роутер удалён
10
- // (решение #76), а ставка, величина и получатель комиссии входят в ПОДПИСАННЫЙ набор условий ордера. Значит
11
- // источник правды ОДИН - котировка, и второго места быть не должно:
12
- // * в настройках страницы ставки НЕТ; копия разошлась бы с подписанным набором;
13
- // * у фабрики спрашивать нечего - она ничего о комиссии не знает;
14
- // * величина в wei посчитана по формуле `amount * feeBps / 10000` (округление ВНИЗ) и здесь сверяется с
15
- // этой же формулой ЗЕРКАЛОМ: расхождение обязано остановить подпись, а не превратиться в отказ эскроу
16
- // (WrongAmount/BadFee) уже после неё.
7
+ // WHERE THE RATE LIVES. Before, it and the recipient were set by the FACTORY (or router) in immutable fields, and
8
+ // the client asked the chain for them (feeBps()/feeRecipient()/feeFor()). Now the contract has NO such functions:
9
+ // the router was removed, and the rate, amount and recipient of the fee are part of the SIGNED set
10
+ // of order terms. So the source of truth is ONE - the quote - and there must be no second place:
11
+ // * the page settings hold NO rate; a copy would diverge from the signed set;
12
+ // * there is nothing to ask the factory - it knows nothing about the fee;
13
+ // * the amount in wei is computed by the formula `amount * feeBps / 10000` (rounding DOWN) and here it is verified
14
+ // against that same formula by the MIRROR: a divergence must stop the signature, not turn into an escrow
15
+ // refusal (WrongAmount/BadFee) after it.
17
16
  //
18
- // КТО ПЛАТИТ. Комиссию платит ПОЛЬЗОВАТЕЛЬ ИНТЕРФЕЙСА. Направление видно из ролей котировки: провайдер
19
- // забирает ETH - покупка (комиссия СВЕРХУ взноса), провайдер вносит ETH - продажа (комиссия ВЫЧЕТОМ из
20
- // выплаты, `amount - fee`). Провайдер не назван ни там, ни там - понять нечего, отказ.
17
+ // WHO PAYS. The fee is paid by the USER OF THE INTERFACE. The direction is visible from the quote roles: the
18
+ // provider takes ETH - a buy (fee ON TOP of the deposit), the provider deposits ETH - a sell (fee DEDUCTED from
19
+ // the payout, `amount - fee`). The provider is named in neither - nothing to tell, refusal.
21
20
  //
22
- // ОДНА ЕДИНИЦА ВРЕМЕНИ. Время в проекте - СЕКУНДЫ (сроки ордера, срок годности котировки, block.timestamp):
23
- // пересчёт между шкалами и был единственным местом, где ошибка на тысячу проходит молча.
21
+ // ONE UNIT OF TIME. Time in the project is SECONDS (order deadlines, quote validity, block.timestamp):
22
+ // converting between scales was the only place where an error by a thousand passes silently.
24
23
 
25
- // Потолок ставки, зашитый в контракте (NinseiEscrow.MAX_FEE_BPS = 200 = 2%). Нужен здесь, чтобы отличить
26
- // осмысленную ставку от испорченной: читать мусор как ставку и считать по нему комиссию нельзя.
24
+ // The rate ceiling hard-wired in the contract (NinseiEscrow.MAX_FEE_BPS = 200 = 2%). Needed here to tell a
25
+ // meaningful rate from a corrupted one: reading garbage as a rate and computing a fee from it is not allowed.
27
26
  export const MAX_FEE_BPS = 200;
28
27
 
29
- // ЗЕРКАЛО ФОРМУЛЫ КОНТРАКТА. Одно место на всю страницу: вторая копия разошлась бы с первой ровно там,
30
- // где это дороже всего - на величине, которую требует эскроу.
28
+ // THE MIRROR OF THE CONTRACT FORMULA. One place for the whole page: a second copy would diverge from the first
29
+ // exactly where it is most expensive - on the amount the escrow requires.
31
30
  export function feeWeiFor(amountWei, feeBps) {
32
31
  const amount = BigInt(amountWei === null || amountWei === undefined || amountWei === "" ? 0 : amountWei);
33
32
  const bps = BigInt(Number(feeBps) || 0);
34
- if (amount < 0n || bps < 0n) throw new Error("комиссия: отрицательная сумма или ставка");
35
- return (amount * bps) / 10000n; // ВНИЗ, как в Solidity: amount * feeBps / 10000
33
+ if (amount < 0n || bps < 0n) throw new Error("fee: negative amount or rate");
34
+ return (amount * bps) / 10000n; // DOWN, as in Solidity: amount * feeBps / 10000
36
35
  }
37
36
 
38
37
  const sameAddr = (a, b) => String(a || "").toLowerCase() === String(b || "").toLowerCase();
39
38
  const zeroAddr = (a) => !a || /^0x0{40}$/i.test(String(a));
40
39
 
41
- // ПАМЯТЬ О ПОСЛЕДНЕЙ ПОДПИСАННОЙ КОТИРОВКЕ. Ставка становится известна ТОЛЬКО с котировкой; до неё её нет,
42
- // и показывать вместо неё ноль или число из конфига значило бы врать. Проверки подставляют свои значения
43
- // через resetFeeCache + feeTermsFromQuote.
40
+ // MEMORY OF THE LAST SIGNED QUOTE. The rate becomes known ONLY with the quote; before it there is none,
41
+ // and showing zero or a config value instead would be lying. The checks insert their own values
42
+ // via resetFeeCache + feeTermsFromQuote.
44
43
  let lastTerms = null;
45
44
 
46
- // СБРОС ПАМЯТИ. Нужен проверкам и любому повтору после смены котировки.
45
+ // RESET THE MEMORY. Needed by the checks and by any retry after the quote changes.
47
46
  export function resetFeeCache() {
48
47
  lastTerms = null;
49
48
  }
50
49
 
51
- // КОМИССИЯ ИЗ КОТИРОВКИ. Возвращает ставку, величину (сверенную зеркалом), получателя и направление; при
52
- // ненулевой ставке без получателя - отказ (комиссия сгорела бы). Результат кладётся в память для экрана.
50
+ // FEE FROM THE QUOTE. Returns the rate, the amount (verified by the mirror), the recipient and the direction;
51
+ // with a non-zero rate and no recipient - a refusal (the fee would burn). The result is kept in memory for the screen.
53
52
  export function feeTermsFromQuote(quote) {
54
- if (!quote || typeof quote !== "object") throw new Error("нет подписанной котировки: комиссию и адрес реестра взять неоткуда");
53
+ if (!quote || typeof quote !== "object") throw new Error("no signed quote: there is nowhere to take the fee and the registry address");
55
54
  const amount = BigInt(String(quote.amount));
56
55
  const bps = Number(quote.feeBps === undefined || quote.feeBps === null ? 0 : quote.feeBps);
57
- if (!Number.isFinite(bps) || bps < 0 || bps > MAX_FEE_BPS) throw new Error("ставка комиссии вне потолка: " + quote.feeBps);
56
+ if (!Number.isFinite(bps) || bps < 0 || bps > MAX_FEE_BPS) throw new Error("fee rate outside the ceiling: " + quote.feeBps);
58
57
  const fee = feeWeiFor(amount, bps);
59
58
  if (BigInt(String(quote.fee === undefined || quote.fee === null ? 0 : quote.fee)) !== fee) {
60
- throw new Error("подписанная комиссия " + quote.fee + " расходится с формулой amount * rate (" + fee + ") - подпись не начата");
59
+ throw new Error("signed fee " + quote.fee + " diverges from the formula amount * rate (" + fee + ") - signature not started");
61
60
  }
62
- if (bps !== 0 && zeroAddr(quote.feeRecipient)) throw new Error("при ненулевой ставке получатель комиссии обязан быть назван");
61
+ if (bps !== 0 && zeroAddr(quote.feeRecipient)) throw new Error("with a non-zero rate the fee recipient must be named");
63
62
  const providerIsLocker = sameAddr(quote.locker, quote.provider);
64
63
  const providerIsClaimer = sameAddr(quote.claimer, quote.provider);
65
- if (providerIsLocker === providerIsClaimer) throw new Error("не понять, кто платит комиссию: провайдер не назван ни вносившим, ни забирающим");
66
- const feeOnTop = providerIsClaimer; // провайдер забирает ETH -> покупка -> комиссия сверху взноса
64
+ if (providerIsLocker === providerIsClaimer) throw new Error("cannot tell who pays the fee: the provider is named neither as depositor nor as claimer");
65
+ const feeOnTop = providerIsClaimer; // the provider takes ETH -> a buy -> fee on top of the deposit
67
66
  const terms = { bps, feeWei: fee, totalWei: feeOnTop ? amount + fee : amount, recipient: quote.feeRecipient || null, feeOnTop, legacy: false };
68
67
  lastTerms = terms;
69
68
  return terms;
70
69
  }
71
70
 
72
- // СИНХРОННЫЙ ДОСТУП К ПРОЧИТАННОМУ. Пусто - значит "ещё не читали либо не смогли": это НЕ ноль, и
73
- // вызывающий обязан закрыть подпись, а не посчитать комиссию нулевой.
71
+ // SYNCHRONOUS ACCESS TO WHAT WAS READ. Empty means "not read yet or could not": this is NOT zero, and
72
+ // the caller must close the signature, not count the fee as zero.
74
73
  export function cachedFeeTerms() {
75
74
  return lastTerms;
76
75
  }
77
76
 
78
- // СТАВКА ДЛЯ ПОКАЗА. Единственное место, откуда страница берёт число для строки разбивки цены. Ответа нет -
79
- // null, и экран говорит, что ставка ещё неизвестна.
77
+ // RATE FOR DISPLAY. The only place the page takes the number for the price-breakdown line from. No answer -
78
+ // null, and the screen says the rate is not known yet.
80
79
  export function cachedFeeRate() {
81
80
  if (!lastTerms || !Number.isFinite(Number(lastTerms.bps))) return null;
82
81
  return Number(lastTerms.bps) / 10000;
83
82
  }
84
83
 
85
- // РАНЬШЕ ЭТА ФУНКЦИЯ ЧИТАЛА СТАВКУ У ЦЕПИ. Теперь читать не у чего: ставка - в подписанной котировке, и
86
- // приходит она вместе с нею (feeTermsFromQuote). Функция оставлена ради вызывающих экранов: до котировки она
87
- // честно возвращает то, что уже известно (обычно null - "ставка неизвестна"), и ничего не выдумывает.
84
+ // THIS FUNCTION USED TO READ THE RATE FROM THE CHAIN. Now there is nothing to read: the rate is in the signed
85
+ // quote and arrives with it (feeTermsFromQuote). The function is kept for the screens that call it: before the
86
+ // quote it honestly returns what is already known (usually null - "rate unknown"), and invents nothing.
88
87
  export async function ensureFeeTerms() {
89
88
  return lastTerms;
90
89
  }