@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,55 +1,74 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/gasReserve.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
- // ЗАПАС ГАЗА ПОКУПАТЕЛЯ НА ДЕЙСТВИЯ ОРДЕРА (issue #97, часть B): ОТМЕТКА ГОТОВНОСТИ И ВОЗМОЖНЫЙ ВОЗВРАТ.
5
+ // THE BUYER GAS RESERVE FOR ORDER ACTIONS: THE READINESS MARK AND A POSSIBLE REFUND.
7
6
  //
8
- // СУТЬ. В прямом обмене человек вносит средства в эскроу, а после прихода XMR сам зовёт markReady (когда XMR
9
- // подтверждены) либо refund (когда сделка не состоялась). Оба вызова стоят газа в НАТИВНОЙ монете сети, и у
10
- // человека, пришедшего из USDC, своего натива нет. Поэтому ДО котировки решается, СКОЛЬКО НАТИВА ОСТАВИТЬ под
11
- // эти два вызова; решение показывается и в ETH, и в долларах, а строкой входит в разбивку цены.
7
+ // THE POINT. In a direct swap the person deposits into the escrow, and after the XMR arrives calls markReady itself
8
+ // (when the XMR is confirmed) or refund (when the swap did not happen). Both calls cost gas in the network
9
+ // NATIVE coin, and a person coming from USDC has no native of their own. So BEFORE the quote it is decided HOW MUCH
10
+ // NATIVE to leave for those two calls; the decision is shown both in ETH and in dollars, and enters the price breakdown as a line.
12
11
  //
13
- // ПОЧЕМУ ЧИСТЫЕ ФУНКЦИИ. Решение стоит денег и обязано проверяться без сети (tools/check-buy-gas-reserve.mjs)
14
- // и считаться ОДНИМ И ТЕМ ЖЕ кодом на экране и в прогоне. Сеть касается только чтения цены газа - его
15
- // подставляет вызывающий (www/js/evm/prices.js кладёт gasPriceWei в живые цены, экран передаёт его сюда).
12
+ // WHY PURE FUNCTIONS. The decision costs money and must be checked without a network
13
+ // and computed by THE SAME code on screen and in the run. Only reading the gas price touches the network - the
14
+ // caller supplies it (www/js/evm/prices.js puts gasPriceWei into the live prices, the screen passes it here).
16
15
  //
17
- // ЧИСЛА НЕ ВЫДУМАНЫ. Пределы газа - те же, с которыми НАШ ЖЕ код отправляет обе транзакции
18
- // (движок сделки теперь в пакете: sdk/src/swap-flow.mjs - markReadyOrder - gas 120000, refundOrder - gas 250000). Проверка
19
- // tools/check-buy-gas-reserve.mjs сверяет обе константы со строками того файла, поэтому правка одного места
20
- // без другого краснит прогон - ровно так же, как это сделано для газа забора в rfq/reverseGas.mjs.
16
+ // THE NUMBERS ARE NOT INVENTED. The gas limits are the same ones OUR OWN code sends both transactions with
17
+ // (the swap engine is now in the package: sdk/src/swap-flow.mjs - markReadyOrder - gas 120000, refundOrder - gas
18
+ // 250000). The check compares both constants against the lines of that file, so
19
+ // editing one place without the other reddens the run - exactly as done for the claim gas in rfq/reverseGas.mjs.
21
20
  //
22
- // ПОЧЕМУ ЗАПАС ФИКСИРУЕТСЯ ДО КОТИРОВКИ. `amount` ордера входит в ПОДПИСАННУЮ котировку, и решение о запасе
23
- // меняет итоговую сумму взноса; значит оно принимается ДО запроса котировки и дальше не пересчитывается.
24
- // Экран кладёт готовый план в состояние формы (www/js/ui/views/swapForm.js), а экран подписи пользуется тем
25
- // же зафиксированным планом - второй пересчёт разошёлся бы с первым.
21
+ // WHY THE RESERVE IS FIXED BEFORE THE QUOTE. The order `amount` is part of the SIGNED quote, and the reserve
22
+ // decision changes the final deposit amount; so it is made BEFORE the quote request and not recomputed afterwards.
23
+ // The screen puts the ready plan into the form state (www/js/ui/views/swapForm.js), and the signing screen uses the
24
+ // same fixed plan - a second recomputation would diverge from the first.
26
25
 
27
- // ПРЕДЕЛЫ ГАЗА - ИЗ НАШЕГО ЖЕ КОДА. Строки-источники: sdk/src/swap-flow.mjs (gas: 120_000n у отметки и
28
- // gas: 250_000n у возврата). Заниженный предел - это отказ уже после раскрытия половины; неиспользованный
29
- // газ в лимите не списывается, поэтому запас берётся с запасом.
26
+ // THE DEPOSIT'S GAS COMES FROM THE FUNDING CODE, NOT FROM A SECOND NUMBER. `orderGasReservePlan` below can
27
+ // include the escrow deposit (createOrderAndFund, evm/funding.js: GAS_CREATE_ORDER_AND_FUND): on the path where
28
+ // the wallet holds no native of its own the deposit is paid AFTER the swap, so its gas must be part of the
29
+ // reserve that decides whether the wallet can afford the path. The constant is imported, not copied.
30
+ import { GAS_CREATE_ORDER_AND_FUND } from "./funding.js";
31
+
32
+ // THE GAS LIMITS ARE FROM OUR OWN CODE. Source lines: sdk/src/swap-flow.mjs (gas: 120_000n at the mark and
33
+ // gas: 250_000n at the refund). A limit set too low is a refusal already after the half is revealed; unused
34
+ // gas in the limit is not charged, so the reserve is taken with a margin.
30
35
  export const ORDER_READY_GAS_LIMIT = 120_000n; // markReady - sdk/src/swap-flow.mjs markReadyOrder
31
36
  export const ORDER_REFUND_GAS_LIMIT = 250_000n; // refund - sdk/src/swap-flow.mjs refundOrder
32
37
  export const ORDER_GAS_LIMIT = ORDER_READY_GAS_LIMIT + ORDER_REFUND_GAS_LIMIT;
38
+ // THE ESCROW DEPOSIT'S OWN GAS LIMIT - the same number funding sends createOrderAndFund with. It is NOT folded
39
+ // into ORDER_GAS_LIMIT (that constant's meaning - the mark and the refund - stays intact for the checks that pin
40
+ // it); it is added ON TOP when the path pays the deposit separately (the no-own-native path), see
41
+ // orderGasReserveWei's `depositGasLimit`.
42
+ export const ORDER_DEPOSIT_GAS_LIMIT = GAS_CREATE_ORDER_AND_FUND;
33
43
 
34
44
  const toBig = (v) => {
35
45
  if (v === null || v === undefined || v === "") return null;
36
46
  try { return BigInt(typeof v === "bigint" ? v : String(v)); } catch { return null; }
37
47
  };
38
48
 
39
- // СКОЛЬКО ВЕЙ НУЖНО НА ОБА ВЫЗОВА. Цену газа даёт цепь, пределы - наши. Ноль цены и нечитаемое число
40
- // означают «не измерено», и это НЕ ноль газа: неизвестное требование обязано называться, а не выдаваться за
41
- // ноль (то же правило, что у газа забора: rfq/reverseGas.mjs).
42
- export function orderGasReserveWei({ gasPriceWei, readyGasLimit = ORDER_READY_GAS_LIMIT, refundGasLimit = ORDER_REFUND_GAS_LIMIT } = {}) {
49
+ // HOW MANY WEI IS NEEDED FOR BOTH CALLS. The gas price comes from the chain, the limits are ours. A zero price
50
+ // and an unreadable number mean "not measured", and this is NOT zero gas: an unknown requirement must be named,
51
+ // not passed off as zero (the same rule as for the claim gas: rfq/reverseGas.mjs).
52
+ export function orderGasReserveWei({ gasPriceWei, readyGasLimit = ORDER_READY_GAS_LIMIT, refundGasLimit = ORDER_REFUND_GAS_LIMIT, depositGasLimit = 0n, extraFeeWei = 0n } = {}) {
43
53
  const gp = toBig(gasPriceWei);
44
54
  if (gp === null || gp <= 0n) return null;
45
55
  const ready = toBig(readyGasLimit);
46
56
  const refund = toBig(refundGasLimit);
47
- if (ready === null || refund === null || ready <= 0n || refund <= 0n) return null;
48
- return gp * (ready + refund);
57
+ // THE DEPOSIT IS OPT-IN AND DEFAULTS TO ZERO: the mark+refund reserve is unchanged for every existing caller
58
+ // (and the checks that pin 370 000 gas). A no-own-native path passes ORDER_DEPOSIT_GAS_LIMIT here so the reserve
59
+ // it is judged against includes the deposit it will pay after the swap. A negative deposit is a defect -> null.
60
+ const deposit = toBig(depositGasLimit);
61
+ // THE SECOND LEG'S EXTRA FEE (a composed route) IS ADDED ON TOP, and it is a FLAT wei amount, not gas: the
62
+ // conversion WETH->native costs the person that fee, so the native the path must cover is larger by it. Defaults to
63
+ // zero so every existing caller (and the checks pinning the mark+refund reserve) is unchanged.
64
+ const extra = toBig(extraFeeWei);
65
+ if (ready === null || refund === null || deposit === null || extra === null) return null;
66
+ if (ready <= 0n || refund <= 0n || deposit < 0n || extra < 0n) return null;
67
+ return gp * (ready + refund + deposit) + extra;
49
68
  }
50
69
 
51
- // ПОКАЗ В ОБЕИХ ВЕЛИЧИНАХ. У человека, пришедшего из USDC, нет чувства масштаба ETH, поэтому запас показан и
52
- // в ETH, и в долларах. Доллар - оценка по живому курсу натива; нет курса - нет и оценки (null), а не выдумка.
70
+ // DISPLAY IN BOTH UNITS. A person coming from USDC has no sense of ETH scale, so the reserve is shown both
71
+ // in ETH and in dollars. The dollar is an estimate at the live native rate; no rate - no estimate (null), not a made-up one.
53
72
  export function gasReserveDisplay({ reserveWei, nativeUsd, decimals = 18 } = {}) {
54
73
  const w = toBig(reserveWei);
55
74
  if (w === null || w <= 0n) return null;
@@ -58,9 +77,9 @@ export function gasReserveDisplay({ reserveWei, nativeUsd, decimals = 18 } = {})
58
77
  return { native, usd };
59
78
  }
60
79
 
61
- // РЕШЕНИЕ ПО ГАЛОЧКЕ. Галочка НУЖНА, только когда своего натива НЕ ХВАТАЕТ на оба вызова; при достатке ETH
62
- // она не показывается вовсе - человек платит своим газом, и просить у него нечего. Нечитаемый баланс натива -
63
- // отдельное состояние: запас предлагаем (лучше предложить, чем промолчать о нехватке), и это названо полем.
80
+ // DECISION BY THE CHECKBOX. The checkbox is NEEDED only when one own native is NOT ENOUGH for both calls; with enough
81
+ // ETH it is not shown at all - the person pays with their own gas, and there is nothing to ask of them. An unreadable
82
+ // native balance is a separate state: we offer the reserve (better to offer than stay silent about the shortfall), and this is named by a field.
64
83
  export function gasReserveChoice({ nativeBalanceWei, reserveWei } = {}) {
65
84
  const need = toBig(reserveWei);
66
85
  if (need === null || need <= 0n) return { measured: false, enough: null, show: false, balanceUnread: false };
@@ -69,16 +88,28 @@ export function gasReserveChoice({ nativeBalanceWei, reserveWei } = {}) {
69
88
  return { measured: true, enough: have >= need, show: have < need, balanceUnread: false };
70
89
  }
71
90
 
72
- // ПОЛНОЕ РЕШЕНИЕ ОДНИМ ОБЪЕКТОМ - именно оно фиксируется в состоянии формы до котировки. Все числовые поля -
73
- // строки либо null: так план переживает запись и чтение, и BigInt не превращается в «[object BigInt]».
74
- export function orderGasReservePlan({ gasPriceWei = null, nativeBalanceWei = null, nativeUsd = null, decimals = 18 } = {}) {
75
- const reserveWei = orderGasReserveWei({ gasPriceWei });
91
+ // THE WHOLE DECISION IN ONE OBJECT - exactly what is fixed in the form state before the quote. All numeric fields
92
+ // are strings or null: so the plan survives writing and reading, and BigInt does not become "[object BigInt]".
93
+ export function orderGasReservePlan({ gasPriceWei = null, nativeBalanceWei = null, nativeUsd = null, decimals = 18, depositGasLimit = 0n, extraFeeWei = 0n } = {}) {
94
+ // depositGasLimit is carried INTO the reserve (see orderGasReserveWei): on the no-own-native path the plan must
95
+ // be honest about the deposit the wallet will pay after the swap, so the reserve it decides on is larger. On a
96
+ // COMPOSED route extraFeeWei additionally carries the second leg's own fee, which is deducted from the swap
97
+ // proceeds - so the reserve must cover it too.
98
+ const reserveWei = orderGasReserveWei({ gasPriceWei, depositGasLimit, extraFeeWei });
99
+ const deposit = toBig(depositGasLimit);
100
+ const extra = toBig(extraFeeWei);
76
101
  const display = reserveWei === null ? null : gasReserveDisplay({ reserveWei, nativeUsd, decimals });
77
102
  const choice = gasReserveChoice({ nativeBalanceWei, reserveWei });
78
103
  return {
79
104
  measured: reserveWei !== null,
80
105
  gasPriceWei: gasPriceWei === null || gasPriceWei === undefined ? null : String(gasPriceWei),
81
106
  reserveWei: reserveWei === null ? null : reserveWei.toString(),
107
+ // THE DEPOSIT IS REPORTED SEPARATELY as well: a reader must be able to see how much of the reserve is the
108
+ // escrow deposit rather than the mark/refund, and a drift between this field and the constant reddens a check.
109
+ depositGasLimit: deposit === null ? null : deposit.toString(),
110
+ // THE SECOND LEG'S FEE IS REPORTED SEPARATELY as well, so a reader sees how much of the reserve is the
111
+ // conversion fee rather than gas or the deposit, and a drift between this field and the number reddens a check.
112
+ extraFeeWei: extra === null ? null : extra.toString(),
82
113
  native: display ? display.native : null,
83
114
  usd: display ? display.usd : null,
84
115
  enough: choice.enough,
@@ -1,19 +1,12 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/index.js for the SDK package.
2
- // Edit the source under www/js, then run: node tools/build-sdk-engine.mjs
3
- // tools/check-sdk-engine.mjs reddens on any drift, so a stale copy cannot ship silently.
2
+ // Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
4
3
  // Everything below this header is identical to the source.
5
4
 
6
- // Кошелёк, которым пользуется интерфейс демки.
7
- //
8
- // Реализация одна: WalletConnect (www/js/evm/walletconnect.js) - настоящий кошелёк
9
- // пользователя, настоящие балансы. Раньше здесь был выбор режима (WalletConnect или
10
- // встроенный демо-кошелёк) для Demo controls; демо-кошелёк убран вместе с переключателем,
11
- // потому что мы движемся к настоящему продукту, а не к макету.
12
- //
13
- // Чего в демке по-прежнему НЕТ: переводов. Контракта escrow ещё не существует, адрес escrow
14
- // генерируется моком (www/js/mock/chain.js), поэтому шаг фондирования остаётся имитацией -
15
- // simulateEscrowFunding() ниже. Когда контракт появится, на его месте будет
16
- // provider.sendTransaction/eth_sendTransaction, а имитация исчезнет вместе с моком цепочки.
5
+ // The wallet used by the demo interface.
6
+ // One implementation: WalletConnect (www/js/evm/walletconnect.js) - the user's real wallet, real balances.
7
+ // The built-in demo wallet and its mode switch were removed: we are heading to a real product, not a mockup.
8
+ // Still NO transfers: there is no escrow contract yet and the address is mocked (mock/chain.js), so funding
9
+ // is a simulation (simulateEscrowFunding below); provider.sendTransaction will replace it.
17
10
 
18
11
  import {
19
12
  address,
@@ -33,40 +26,43 @@ import {
33
26
  restoreInjected,
34
27
  } from "./session.js";
35
28
  import { discoverWallets } from "./injected.js";
36
- // fundOrder нужен ЛОКАЛЬНЫМ именем: export * из модуля ./funding.js« ниже только реэкспортирует, но не
37
- // создаёт переменную в этом модуле. Из-за этого fundOrderLive вызывал несуществующее имя и падал.
29
+ // fundOrder needs a LOCAL name: `export * from ./funding.js` only re-exports and does not create a local
30
+ // variable, so fundOrderLive called a missing name and crashed.
38
31
  import { fundOrder } from "./funding.js";
39
32
  export * from "./factory.js";
40
33
  export * from "./escrow.js";
41
34
  export * from "./funding.js";
42
- // ВНОСЯЩИЙ ПО ПОДПИСИ И РАЗРЕШЕНИЕ НА ТОКЕН. Форма подписи вносящего (evm/depositor.js) и вердикт о
43
- // разрешении по подписи (evm/permit.js) выходят через ТОТ ЖЕ шов движка: страница и SDK пользуются одним
44
- // кодом протокола, а не двумя похожими.
35
+ // The depositor signature form (evm/depositor.js) and the permit verdict (evm/permit.js) go through the SAME
36
+ // engine seam: the page and the SDK share one protocol code, not two lookalikes.
45
37
  export * from "./depositor.js";
46
38
  export * from "./permit.js";
39
+ // THE ASYNCHRONOUS ROUTE, EXECUTED - through the same engine seam as the decision that chooses it. The page reaches
40
+ // it through the bridge; the SDK carries the same module in its engine mirror (tools/build-sdk-engine.mjs).
41
+ export * from "./asyncRoute.js";
42
+ export * from "./asyncExec.js";
43
+ // THE LIQUIDITY LEG OF A COMPOSED ROUTE (KyberSwap Limit Order) - through the same engine seam as the CoW leg,
44
+ // so the page and the SDK carry one implementation each.
45
+ export * from "./asyncLegs.js";
47
46
 
48
- // currentProvider - ПРОВАЙДЕР КОШЕЛЬКА НАРУЖУ, ЯДРУ: склейник отдаёт его адаптеру SDK (evm-wallet.mjs), чтобы
49
- // подпись шла тем же ключом, что и у страницы. Экранам он не нужен: они читают status()/isConnected().
47
+ // currentProvider - the wallet provider goes OUT to the SDK core (evm-wallet.mjs) so signing uses the same
48
+ // key as the page. Screens do not need it; they read status()/isConnected().
50
49
  export { address, balanceOf, chainIdOf, connect, disconnect, isConnected, refreshBalances, restore, status, subscribe, switchChain, currentProvider };
51
50
  export { connectInjected, restoreInjected };
52
- // ВАЖНО: это импорт, а не реэкспорт. Конструкция export { X } из модуля ...« только реэкспортирует имя и НЕ
53
- // создаёт его в этом модуле, а fundOrderLive ниже вызывает sendTransaction и readContract напрямую.
54
- // Ровно на этом уже падало дважды: сначала fundOrder, затем readContract - оба имени были доступны
55
- // снаружи, но не внутри файла.
51
+ // IMPORTANT: this is an import, not a re-export. `export { X } from ...` only re-exports and does NOT create
52
+ // a local name, while fundOrderLive uses sendTransaction/readContract directly - this already broke twice.
56
53
  import { sendTransaction, readContract, readReceipt, readCode } from "./session.js";
57
54
  export { sendTransaction, readContract, readReceipt, readCode };
58
55
 
59
- // Список браузерных кошельков в этом браузере (EIP-6963, с фолбэком на window.ethereum).
60
- // Пустой список - обычное дело: значит расширения нет, и остаётся WalletConnect.
56
+ // The browser wallets available here (EIP-6963, falling back to window.ethereum). An empty list is normal:
57
+ // no extension, so WalletConnect remains.
61
58
  export function wallets() {
62
59
  return discoverWallets();
63
60
  }
64
61
 
65
- // Балансы всех принимаемых токенов сети (нативный первым) - для экранов, которым нужен список.
62
+ // Balances of all accepted tokens of the network (native first), for screens that need the list.
66
63
 
67
- // ИМИТАЦИЯ: настоящей транзакции не отправляем, потому что отправлять некуда - escrow-контракта
68
- // нет. Возвращаем поддельный хеш, чтобы состояние сделки осталось выводимым (см. docs/03).
69
- // В UI это сказано прямо: демка не двигает реальные деньги.
64
+ // SIMULATION: no real transaction is sent (there is no escrow contract). A fake hash is returned so the
65
+ // swap state stays derivable; the UI says plainly the demo moves no real money.
70
66
  export async function simulateEscrowFunding(tx) {
71
67
  await new Promise((r) => setTimeout(r, 900));
72
68
  const bytes = new Uint8Array(32);
@@ -75,31 +71,28 @@ export async function simulateEscrowFunding(tx) {
75
71
  return { hash, ...tx, simulated: true };
76
72
  }
77
73
 
78
- // БОЕВОЙ ПУТЬ ФОНДИРОВАНИЯ: настоящие транзакции в кошельке пользователя.
74
+ // THE LIVE FUNDING PATH: real transactions in the user's wallet.
79
75
  //
80
- // Заменяет simulateEscrowFunding. Отличие принципиальное: симуляция выдумывала хеш, здесь уходят две
81
- // настоящие транзакции - createOrder в фабрику и lock(sealedHalf) в созданный эскроу.
76
+ // Replaces simulateEscrowFunding. The difference is fundamental: the simulation invented a hash, here two
77
+ // real transactions go out - createOrder to the factory and lock(sealedHalf) to the new escrow.
82
78
  //
83
- // Разделение обязанностей намеренное: криптография (половины, DLEQ, посылка) живёт в www/js/atomic/ и
84
- // сюда НЕ заглядывает. Эта функция получает уже готовые sealedCommitment и sealedHalf - то есть ровно то,
85
- // что уходит в контракт. Порядок вычислений задаёт вызывающий: сначала проверить сторону контрагента
86
- // (раздел 20.5), и только потом лочить.
79
+ // Separation is deliberate: cryptography (halves, DLEQ, envelope) lives in www/js/atomic/ and does NOT reach
80
+ // here. This function gets the ready sealedCommitment and sealedHalf - exactly what goes to the contract.
81
+ // The caller sets the order of steps: verify the counterparty first, then lock.
87
82
  //
88
- // factory передаётся снаружи: адрес берётся из конфига сети тем, кто вызывает, чтобы здесь не заводить
89
- // второй источник правды об адресах.
90
- // ПОДПИСЬ ПРИВЕДЕНА К НОВОЙ СХЕМЕ, и это была не косметика: здесь оставались хешлок, обязательство и
91
- // посылка от прежнего устройства, а обязательств на половины и точек ed25519 - которые fundOrder ТРЕБУЕТ
92
- // и без которых падает - не было вовсе. То есть живой путь фондирования не мог пройти даже первый шаг:
93
- // вызов уходил дальше без четырёх обязательных полей. Ни синтаксис, ни проверки этого не видели, потому
94
- // что живой путь никем не запускается: он требует настоящего кошелька и подписи.
95
- // ЧЕТЫРЕ ПОЛЯ И ДВА СРОКА ЯВНО. Сроки приходят готовыми (readyBy, t1), а не считаются здесь от текущего
96
- // времени: они входят в контекст, к которому привязано доказательство, значит должны быть решены ДО него.
83
+ // factory is passed in (the caller takes it from the network config) so there is no second source of truth
84
+ // for addresses here.
85
+ // THE SIGNATURE WAS BROUGHT TO THE NEW SCHEME: the old hashlock/commitment/envelope fields remained, while
86
+ // the half commitments and ed25519 points that fundOrder REQUIRES were absent - so the live funding path
87
+ // could not pass even the first step. Neither syntax nor checks saw this, because the live path is never run.
88
+ // FOUR FIELDS AND TWO DEADLINES EXPLICITLY. The deadlines come ready (readyBy, t1), not computed from now:
89
+ // they go into the context the proof binds to, so they must be fixed before it.
97
90
  export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, depositorSignature, onStep }) {
98
91
  return fundOrder({
99
92
  factory,
100
- quote, // подписанная котировка: несёт комиссию, адрес реестра и провайдера
101
- // ПОДПИСЬ ВНОСЯЩЕГО НАД ДАЙДЖЕСТОМ КОТИРОВКИ (issue #97, части A/C). Есть - фондирование идёт путём
102
- // createOrderAndFundByDepositor, и транзакцию вправе отправить кто угодно. Нет - прежний путь.
93
+ quote, // signed quote: carries the fee, registry address and provider
94
+ // THE DEPOSITOR SIGNATURE OVER THE QUOTE DIGEST. Present: funding goes via
95
+ // createOrderAndFundByDepositor and anyone may send the transaction. Absent: the old path.
103
96
  depositorSignature,
104
97
  locker,
105
98
  claimer,
@@ -108,16 +101,16 @@ export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker
108
101
  edPointLocker,
109
102
  edPointClaimer,
110
103
  edViewPointLocker,
111
- salt, // повтор фондирования обязан попасть в тот же адрес эскроу
112
- amountLabel, // "0.03 ETH" вместо числа в wei на кнопке
104
+ salt, // retrying funding must land at the same escrow address
105
+ amountLabel, // "0.03 ETH" instead of the wei number on the button
113
106
  amountWei,
114
107
  claimWindowSeconds,
115
108
  readyWindowSeconds,
116
- readyBy, // сроки, к которым уже привязано доказательство
109
+ readyBy, // deadlines the proof is already bound to
117
110
  t1,
118
111
  call: readContract,
119
112
  send: sendTransaction,
120
- receipt: readReceipt, // адрес эскроу берём из квитанции, а не из предсказания
113
+ receipt: readReceipt, // the escrow address comes from the receipt, not from a prediction
121
114
  onStep,
122
115
  });
123
116
  }
@@ -1,27 +1,22 @@
1
1
  // GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/injected.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
- // Браузерные кошельки: обнаружение по EIP-6963 и подключение по EIP-1193.
5
+ // BROWSER WALLETS: detection by EIP-6963 and connection by EIP-1193.
6
+ // WHY EIP-6963, NOT window.ethereum.isMetaMask. With two extensions installed, window.ethereum is "whoever was
7
+ // last", and guessing by flags gives wrong names and icons. EIP-6963 solves this: a wallet announces itself with
8
+ // an `eip6963:announceProvider` event (name, icon, provider), and the page requests it with `eip6963:requestProvider`.
9
+ // So the wallet list is real and updates if the user installs an extension with the tab open.
10
+ // window.ethereum stays as a fallback for old wallets; there the honest name is "Browser wallet".
11
+ // NO STATE HERE: this module only finds providers and does the handshake. State and balances live in evm/session.js.
7
12
  //
8
- // ПОЧЕМУ EIP-6963, А НЕ window.ethereum.isMetaMask. При двух установленных расширениях
9
- // window.ethereum - это «кто успел последним», и угадывание по флагам даёт чужие имена и иконки:
10
- // пользователь видит «MetaMask», а подключается Rabby. EIP-6963 решает это стандартно: кошелёк
11
- // объявляет себя событием `eip6963:announceProvider` с именем, иконкой и своим провайдером, а
12
- // страница просит объявиться событием `eip6963:requestProvider`. Так список кошельков настоящий,
13
- // и он же обновляется, если пользователь поставил расширение при открытой вкладке.
14
13
  //
15
- // window.ethereum остаётся фолбэком для старых кошельков, которые 6963 ещё не умеют. В этом
16
- // случае честное имя - «Browser wallet»: назвать его MetaMask мы не имеем права, не проверив.
17
14
  //
18
- // Здесь НЕТ состояния: этот модуль только находит провайдеров и делает рукопожатие. Состояние,
19
- // балансы и смена сети живут в evm/session.js - общие для обоих коннекторов.
20
15
 
21
16
  const ANNOUNCE = "eip6963:announceProvider";
22
17
  const REQUEST = "eip6963:requestProvider";
23
18
 
24
- // Коды EIP-1193, которые важны на экране: отказ пользователя - это не ошибка приложения.
19
+ // EIP-1193 codes that matter on screen: a user refusal is not an application error.
25
20
  export const USER_REJECTED = 4001;
26
21
 
27
22
  export function isUserRejection(error) {
@@ -30,7 +25,7 @@ export function isUserRejection(error) {
30
25
  return /rejected|denied|cancell?ed|closed by user/i.test(String((error && error.message) || error || ""));
31
26
  }
32
27
 
33
- // Человеческое имя для фолбэка: флаги - единственное, что есть у старых кошельков.
28
+ // A human name for the fallback: flags are all the old wallets have.
34
29
  function fallbackName(eth) {
35
30
  if (!eth) return null;
36
31
  if (eth.isMetaMask) return "MetaMask";
@@ -41,9 +36,8 @@ function fallbackName(eth) {
41
36
  return "Browser wallet";
42
37
  }
43
38
 
44
- // Собрать список кошельков: сначала объявившиеся по EIP-6963, затем фолбэк window.ethereum.
45
- // timeoutMs нужен потому, что announceProvider - событие: без паузы список будет пустым даже при
46
- // установленном кошельке (у расширения свой темп инициализации).
39
+ // Build the wallet list: first those announced by EIP-6963, then the window.ethereum fallback. timeoutMs is
40
+ // needed because announceProvider is an event: without a pause the list would be empty even with a wallet installed.
47
41
  export function discoverWallets({ win = globalThis, timeoutMs = 250 } = {}) {
48
42
  const found = [];
49
43
  const seen = new Set();
@@ -70,7 +64,7 @@ export function discoverWallets({ win = globalThis, timeoutMs = 250 } = {}) {
70
64
  if (win && typeof win.addEventListener === "function") {
71
65
  win.addEventListener(ANNOUNCE, onAnnounce);
72
66
  if (typeof win.dispatchEvent === "function") {
73
- // Просим объявиться; ответы придут асинхронно, поэтому ждём короткую паузу.
67
+ // We ask them to announce; answers arrive asynchronously, so we wait a short pause.
74
68
  win.dispatchEvent(new Event(REQUEST));
75
69
  }
76
70
  }
@@ -78,8 +72,8 @@ export function discoverWallets({ win = globalThis, timeoutMs = 250 } = {}) {
78
72
  return new Promise((resolve) => {
79
73
  const finish = () => {
80
74
  if (win && typeof win.removeEventListener === "function") win.removeEventListener(ANNOUNCE, onAnnounce);
81
- // Фолбэк: старый кошелёк, который 6963 не умеет, но window.ethereum выставил.
82
- // providers (массив) бывает, когда установлено несколько кошельков без поддержки 6963.
75
+ // Fallback: an old wallet that does not know 6963 but sets window.ethereum. providers (array) appears with
76
+ // several wallets without 6963 support.
83
77
  const legacy = win && win.ethereum;
84
78
  if (!found.length && legacy) {
85
79
  const list = Array.isArray(legacy.providers) && legacy.providers.length ? legacy.providers : [legacy];
@@ -94,12 +88,11 @@ export function discoverWallets({ win = globalThis, timeoutMs = 250 } = {}) {
94
88
  });
95
89
  }
96
90
 
97
- // Подписка на «кошелёк появился». Нужна, чтобы список в модалке не был снимком на момент открытия:
98
- // пользователь может поставить расширение, не перезагружая страницу.
91
+ // Subscribe to "a wallet appeared". Needed so the modal list is not a snapshot at open time: the user may install
92
+ // an extension without reloading.
99
93
  //
100
- // Работаем именно с самим объявлением, а не с повторным обнаружением: событие EIP-6963 несёт и имя,
101
- // и провайдер кошелька, а события не переигрываются - кошелёк, объявившийся до нашей подписки, при
102
- // повторном запросе ответит снова, но снимок всё равно был бы потерян. Так список только дополняется.
94
+ // We work with the announcement itself, not a re-detection: the EIP-6963 event carries the name and provider, and
95
+ // events are not replayed, so a wallet that announced before our subscription would be lost on a re-request. The list only grows.
103
96
  export function watchWallets(fn, { win = globalThis } = {}) {
104
97
  if (!win || typeof win.addEventListener !== "function") return () => {};
105
98
  const known = new Map();
@@ -120,16 +113,16 @@ export function watchWallets(fn, { win = globalThis } = {}) {
120
113
  publish();
121
114
  };
122
115
  win.addEventListener(ANNOUNCE, onAnnounce);
123
- // Просим объявиться: кошельки, которые уже установлены, ответят прямо сейчас и попадут в список.
124
- // Пустой список наружу не отдаём - иначе UI очищался бы просто потому, что кошельков нет.
116
+ // We ask them to announce: installed wallets answer right now and enter the list. An empty list is not handed
117
+ // out, else the UI would be cleared just because there are no wallets.
125
118
  if (typeof win.dispatchEvent === "function") win.dispatchEvent(new Event(REQUEST));
126
119
  return () => win.removeEventListener(ANNOUNCE, onAnnounce);
127
120
  }
128
121
 
129
- // Рукопожатие EIP-1193: просим аккаунты (здесь кошелёк спросит разрешение) и узнаём сеть.
122
+ // EIP-1193 handshake: ask for accounts (here the wallet asks permission) and learn the network.
130
123
  export async function handshake(provider) {
131
124
  if (!provider || typeof provider.request !== "function") {
132
- throw new Error("Этот кошелёк не умеет EIP-1193 (нет request)");
125
+ throw new Error("This wallet does not support EIP-1193 (no request)");
133
126
  }
134
127
  const accounts = await provider.request({ method: "eth_requestAccounts" });
135
128
  const address = Array.isArray(accounts) ? accounts[0] : null;
@@ -137,10 +130,9 @@ export async function handshake(provider) {
137
130
  return { address: address || null, chainId: Number(chainIdHex) || null };
138
131
  }
139
132
 
140
- // Тихая проверка «сайт уже разрешён этому кошельку» - для восстановления после перезагрузки.
141
- // eth_accounts НЕ показывает окно кошелька: пустой ответ значит «разрешения нет», и это нормальное
142
- // состояние, а не ошибка. Именно поэтому нельзя звать здесь eth_requestAccounts: страница при
143
- // загрузке не должна выкидывать окно подключения.
133
+ // A silent check "the site is already allowed for this wallet" - for restoring after a reload. eth_accounts does
134
+ // NOT show the wallet window: an empty answer means "no permission", a normal state, not an error. That is why
135
+ // eth_requestAccounts must not be called here: the page must not pop a connect window on load.
144
136
  export async function silentAccounts(provider) {
145
137
  if (!provider || typeof provider.request !== "function") return [];
146
138
  try {
@@ -151,25 +143,25 @@ export async function silentAccounts(provider) {
151
143
  }
152
144
  }
153
145
 
154
- // Почему кошелёк мог не открыться - переводим код ошибки в понятную причину.
146
+ // Why the wallet may not have opened - translate the error code into a plain reason.
155
147
  //
156
- // -32002 - самая частая причина «нажал, а окно не появилось»: у кошелька уже есть открытый
157
- // запрос (окно висит на другом экране/вкладке, или предыдущая попытка не завершена).
158
- export function explainInjectedError(e, label = "Кошелёк") {
148
+ // -32002 is the most common cause of "pressed but no window": the wallet already has an open request (the window
149
+ // hangs on another screen/tab, or the previous attempt did not finish).
150
+ export function explainInjectedError(e, label = "Wallet") {
159
151
  const code = e && (e.code || (e.data && e.data.originalError && e.data.originalError.code));
160
152
  const message = String((e && e.message) || e);
161
153
  if (code === -32002 || /already pending|already processing/i.test(message)) {
162
154
  return (
163
155
  label +
164
- ": в кошельке уже есть незавершённый запрос, поэтому новое окно не открылось. " +
165
- "Открой расширение, заверши или отмени тот запрос и нажми снова."
156
+ ": the wallet already has a pending request, so a new window did not open. " +
157
+ "Open the extension, finish or cancel that request, then try again."
166
158
  );
167
159
  }
168
160
  if (/no provider|not connected|provider is disconnected|undefined is not a function/i.test(message)) {
169
- return label + ": расширение не отвечает (браузер мог выгрузить его или страница открыта не там, где оно работает).";
161
+ return label + ": the extension is not responding (the browser may have unloaded it, or the page is not where it works).";
170
162
  }
171
163
  if (/user rejected|user denied|rejected by user/i.test(message) || code === 4001) {
172
- return label + ": запрос отклонён в кошельке.";
164
+ return label + ": the request was rejected in the wallet.";
173
165
  }
174
166
  return label + ": " + message;
175
167
  }