@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.
- package/CHANGELOG.md +35 -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 +554 -449
- package/engine/core/finality.js +34 -53
- package/engine/core/format.js +35 -36
- package/engine/core/preSignGuards.js +109 -117
- package/engine/core/rfqSource.js +57 -75
- package/engine/core/sellWindow.js +23 -24
- package/engine/core/store.js +20 -21
- package/engine/core/swap.js +242 -243
- package/engine/evm/amounts.js +50 -51
- package/engine/evm/asyncExec.js +577 -0
- package/engine/evm/asyncLegs.js +308 -0
- package/engine/evm/asyncRoute.js +179 -0
- package/engine/evm/auth.js +41 -48
- package/engine/evm/claimGas.js +28 -47
- package/engine/evm/depositor.js +39 -43
- package/engine/evm/dex.js +266 -267
- package/engine/evm/escrow.js +113 -129
- package/engine/evm/factory.js +71 -77
- package/engine/evm/fees.js +41 -42
- package/engine/evm/funding.js +160 -162
- package/engine/evm/gasReserve.js +67 -36
- package/engine/evm/index.js +46 -53
- package/engine/evm/injected.js +34 -42
- package/engine/evm/permit.js +129 -56
- package/engine/evm/prices.js +30 -45
- package/engine/evm/session.js +188 -189
- package/engine/mock/chain.js +17 -18
- package/engine/monero/address.js +33 -48
- package/engine/monero/halvesSweep.js +25 -43
- package/engine/monero/node.js +14 -15
- package/engine/monero/scan.js +82 -95
- package/engine/monero/swapKeys.js +30 -33
- package/engine/monero/unlock.js +17 -20
- package/engine/monero/wallet.js +32 -42
- package/engine/recovery/recoveryFile.js +154 -161
- package/engine/recovery/restore.js +50 -61
- package/engine/sdk/bridge.js +244 -234
- package/engine/ui/dom.js +4 -5
- package/engine/ui/walletModal.js +20 -31
- package/index.d.ts +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 +5 -5
- 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/mock/chain.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/mock/chain.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
|
-
//
|
|
5
|
+
// Mock chain: the escrow contract on EVM and the Monero leg.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// There are NO real transactions here. Everything returned is marked mock, and the hashes
|
|
8
|
+
// are deterministic from the swap id - so the demo is reproducible.
|
|
9
|
+
// Swap-out point: once the contract exists, these functions are replaced by ethers.js calls,
|
|
10
|
+
// while the interface (fund/lock/claim/refund/sweep) is preserved.
|
|
12
11
|
|
|
13
12
|
import { randomHex } from "../core/format.js";
|
|
14
13
|
import * as node from "../monero/node.js";
|
|
@@ -23,12 +22,12 @@ function hashOf(...parts) {
|
|
|
23
22
|
return "0x" + hh.toString(16).padStart(8, "0") + randomHex(28);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
// CREATE2
|
|
25
|
+
// CREATE2 address of the per-order escrow (litepaper §8: its own contract per swap, no admin).
|
|
27
26
|
export function escrowAddress(swapId) {
|
|
28
27
|
return "0x" + hashOf("escrow", swapId).slice(2, 42);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
//
|
|
30
|
+
// Address of the joint Monero output (S_a + S_b, V_a + V_b in the real protocol).
|
|
32
31
|
export function moneroLockAddress(swapId) {
|
|
33
32
|
return "8" + hashOf("xmrlock", swapId).slice(2, 96);
|
|
34
33
|
}
|
|
@@ -41,10 +40,10 @@ export function fundEscrow({ swapId, ethAmountUsd, ethUsd }) {
|
|
|
41
40
|
ethAmount: ethUsd > 0 ? ethAmountUsd / ethUsd : 0,
|
|
42
41
|
valueUsd: ethAmountUsd,
|
|
43
42
|
gasUsd: 0.21,
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
43
|
+
// WE DO NOT INVENT THE METHOD NAME. This used to be lockAndSwap(bytes32 quoteHash, bytes makerSig) - no
|
|
44
|
+
// such method exists in the contract at all, yet that exact string went into the swap record and was shown
|
|
45
|
+
// on screen as "Escrow method". The demo path moves no money, so its honest label is this one, without the
|
|
46
|
+
// invented signature. The real call name is written by the live path itself, from the actual call.
|
|
48
47
|
method: "simulated funding (no real transaction)",
|
|
49
48
|
confirmations: 1,
|
|
50
49
|
};
|
|
@@ -69,12 +68,12 @@ export function refundEscrow({ swapId, by }) {
|
|
|
69
68
|
return { mock: true, txHash: hashOf("refund", swapId, by), by };
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
//
|
|
71
|
+
// Monero node confirmations: 10 blocks of 2 minutes (litepaper §5.0).
|
|
73
72
|
//
|
|
74
|
-
// chainMode=mock -
|
|
75
|
-
// chainMode=live -
|
|
76
|
-
//
|
|
77
|
-
//
|
|
73
|
+
// chainMode=mock - confirmations follow sim-time (fast, for showing the flow).
|
|
74
|
+
// chainMode=live - confirmations are counted from the real height growth of the mainnet node:
|
|
75
|
+
// the swap then honestly takes ~20 minutes regardless of the demo speed. If the node
|
|
76
|
+
// is unavailable or the height at lock time was not recorded, we fall back to the sim counter.
|
|
78
77
|
export function confirmations(swap, simElapsedMs) {
|
|
79
78
|
const target = swap.timeline.confTarget;
|
|
80
79
|
|
package/engine/monero/address.js
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/monero/address.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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// Зачем это нужно в демке: без проверки checksum опечатка в адресе получения
|
|
15
|
-
// выглядит валидной, а в atomic-свопе ошибка в адресе означает потерянные деньги.
|
|
16
|
-
//
|
|
17
|
-
// Префиксы сетей (из src/cryptonote_config.h, официальные значения):
|
|
18
|
-
// mainnet: 18 обычный, 19 с payment id, 42 суб-адрес
|
|
19
|
-
// stagenet: 24 / 25 / 36
|
|
20
|
-
// testnet: 53 / 54 / 63
|
|
21
|
-
// Вендоренная monero-js умеет ТОЛЬКО mainnet (у неё в address.js жёстко 18/19/42),
|
|
22
|
-
// поэтому здесь свой энкодер: библиотека даёт публичные ключи, а строку адреса собираем мы.
|
|
5
|
+
// Monero address checks and assembly: format + real checksum + network prefixes.
|
|
6
|
+
// A detail easy to get wrong: Monero has its OWN base58 - block-based (8 bytes -> 11 chars, the remainder last).
|
|
7
|
+
// It is NOT compatible with bitcoin-base58, where a leading '1' means a zero byte; here a block is a NUMBER and
|
|
8
|
+
// '1' can be just a digit. So bs58 does not fit and base58 is implemented numerically below.
|
|
9
|
+
// Without the checksum a typo in the payout address looks valid, and in an atomic swap that means lost money.
|
|
10
|
+
// Network prefixes (official): mainnet 18 plain, 19 with payment id, 42 subaddress.
|
|
11
|
+
// The vendored monero-js knows ONLY mainnet, so we have our own encoder: the library gives public keys, we build the string.
|
|
23
12
|
|
|
24
13
|
const BLOCK_CHARS = 11;
|
|
25
14
|
|
|
26
|
-
// encoded bytes -> chars (
|
|
15
|
+
// encoded bytes -> chars (from the vendored base58 table)
|
|
27
16
|
const CHARS_BY_BYTES = { 1: 2, 2: 3, 3: 5, 4: 6, 5: 7, 6: 9, 7: 10, 8: 11 };
|
|
28
17
|
const BYTES_BY_CHARS = { 2: 1, 3: 2, 5: 3, 6: 4, 7: 5, 9: 6, 10: 7, 11: 8 };
|
|
29
18
|
|
|
@@ -39,7 +28,7 @@ const PREFIXES = {
|
|
|
39
28
|
63: { network: "testnet", kind: "subaddress" },
|
|
40
29
|
};
|
|
41
30
|
|
|
42
|
-
//
|
|
31
|
+
// reverse table: network + kind -> prefix byte (for address assembly)
|
|
43
32
|
export const PREFIX_BY_NETWORK = {
|
|
44
33
|
mainnet: { primary: 18, integrated: 19, subaddress: 42 },
|
|
45
34
|
stagenet: { primary: 24, integrated: 25, subaddress: 36 },
|
|
@@ -54,16 +43,16 @@ export function prefixFor(network, kind = "primary") {
|
|
|
54
43
|
return prefix;
|
|
55
44
|
}
|
|
56
45
|
|
|
57
|
-
//
|
|
46
|
+
// Synchronous format check (for the UI before the library loads).
|
|
58
47
|
export function formatCheck(addr) {
|
|
59
48
|
const a = String(addr || "").trim();
|
|
60
49
|
if (![95, 106].includes(a.length)) return false;
|
|
61
50
|
if (!/^[1-9A-HJ-NP-Za-km-z]+$/.test(a)) return false;
|
|
62
|
-
//
|
|
51
|
+
// the first letter is set by the network prefix: 4/8 mainnet, 5/7 stagenet, 9/A/B testnet
|
|
63
52
|
return ["4", "5", "7", "8", "9", "A", "B"].includes(a[0]);
|
|
64
53
|
}
|
|
65
54
|
|
|
66
|
-
//
|
|
55
|
+
// The first letter follows directly from the prefix: 4/8 mainnet, 5/7 stagenet, 9/A/B testnet
|
|
67
56
|
export function networkFromShape(addr) {
|
|
68
57
|
const a = String(addr || "").trim();
|
|
69
58
|
if (!formatCheck(a)) return null;
|
|
@@ -93,7 +82,7 @@ async function loadLib() {
|
|
|
93
82
|
}
|
|
94
83
|
}
|
|
95
84
|
|
|
96
|
-
//
|
|
85
|
+
// The Monero-base58 alphabet (same as Bitcoin, different encoding rules).
|
|
97
86
|
const B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
98
87
|
const B58_VALUES = (() => {
|
|
99
88
|
const m = new Map();
|
|
@@ -101,7 +90,7 @@ const B58_VALUES = (() => {
|
|
|
101
90
|
return m;
|
|
102
91
|
})();
|
|
103
92
|
|
|
104
|
-
//
|
|
93
|
+
// Block of bytes -> base58 string (as a number: no "leading '1' = zero byte").
|
|
105
94
|
function blockToBase58(block) {
|
|
106
95
|
let num = 0n;
|
|
107
96
|
for (const b of block) num = (num << 8n) | BigInt(b);
|
|
@@ -113,12 +102,10 @@ function blockToBase58(block) {
|
|
|
113
102
|
return s;
|
|
114
103
|
}
|
|
115
104
|
|
|
116
|
-
// base58
|
|
105
|
+
// base58 block string -> exactly `bytes` bytes, right-aligned.
|
|
117
106
|
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// внутри числа - тогда bs58 выдаёт лишний нулевой байт, payload разъезжается, и checksum
|
|
121
|
-
// ложно не сходится. На этом ловились: 18 адресов из 120 (15%) проверялись как «с опечаткой».
|
|
107
|
+
// Why not bs58 from npm: its decoder treats every leading '1' as a zero BYTE (bitcoin semantics). In Monero a
|
|
108
|
+
// block is a number and '1' can be just a digit, so bs58 adds a spurious zero byte and the checksum falsely fails.
|
|
122
109
|
function base58ToBlock(str, bytes) {
|
|
123
110
|
let num = 0n;
|
|
124
111
|
for (const ch of str) {
|
|
@@ -135,11 +122,10 @@ function base58ToBlock(str, bytes) {
|
|
|
135
122
|
return out;
|
|
136
123
|
}
|
|
137
124
|
|
|
138
|
-
//
|
|
125
|
+
// Block-based Monero base58: string -> bytes.
|
|
139
126
|
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// сначала tot_block_cnt блоков, затем last_block_enc_len). Поэтому остаток берём с конца строки.
|
|
127
|
+
// Block order matters: Monero encodes full 8-byte blocks first (11 chars each), and the PARTIAL remainder goes
|
|
128
|
+
// LAST with fewer chars. So the remainder is taken from the end of the string.
|
|
143
129
|
export function decodeMoneroBase58(str) {
|
|
144
130
|
const s = String(str);
|
|
145
131
|
const L = s.length;
|
|
@@ -159,8 +145,8 @@ export function decodeMoneroBase58(str) {
|
|
|
159
145
|
return out;
|
|
160
146
|
}
|
|
161
147
|
|
|
162
|
-
//
|
|
163
|
-
//
|
|
148
|
+
// Block-based Monero base58: bytes -> string (inverse of decode above). Full 8-byte blocks are 11 chars
|
|
149
|
+
// (left-padded with '1'), the remainder goes last.
|
|
164
150
|
export function encodeMoneroBase58(bytes) {
|
|
165
151
|
const arr = Array.from(bytes);
|
|
166
152
|
const L = arr.length;
|
|
@@ -178,7 +164,7 @@ export function encodeMoneroBase58(bytes) {
|
|
|
178
164
|
return out;
|
|
179
165
|
}
|
|
180
166
|
|
|
181
|
-
//
|
|
167
|
+
// Address payload (without checksum): prefix + public spend + public view [+ payment id]
|
|
182
168
|
export function addressPayload({ network, kind = "primary", spendPub, viewPub, paymentId }) {
|
|
183
169
|
const prefix = prefixFor(network, kind);
|
|
184
170
|
const spend = hexToBytes(spendPub, 32, "public spend key");
|
|
@@ -201,8 +187,8 @@ function hexToBytes(hex, expectedBytes, label) {
|
|
|
201
187
|
return out;
|
|
202
188
|
}
|
|
203
189
|
|
|
204
|
-
//
|
|
205
|
-
// bs58
|
|
190
|
+
// Assembly of the address from public keys: deps = { keccak256 } (in the browser, from the monero-js bundle).
|
|
191
|
+
// bs58 is not needed: Monero base58 is block-based and numeric, see the comment at base58ToBlock.
|
|
206
192
|
export function addressFromKeys({ network, kind = "primary", spendPub, viewPub, paymentId }, deps) {
|
|
207
193
|
if (!deps?.keccak256) throw new Error("addressFromKeys needs { keccak256 }");
|
|
208
194
|
const payload = addressPayload({ network, kind, spendPub, viewPub, paymentId });
|
|
@@ -213,9 +199,9 @@ export function addressFromKeys({ network, kind = "primary", spendPub, viewPub,
|
|
|
213
199
|
return encodeMoneroBase58(full);
|
|
214
200
|
}
|
|
215
201
|
|
|
216
|
-
//
|
|
217
|
-
// expectNetwork (
|
|
218
|
-
//
|
|
202
|
+
// Full check: format + block decode + keccak256 checksum + network.
|
|
203
|
+
// expectNetwork (optional): if set, an address of another network is an error - on a stagenet swap a mainnet
|
|
204
|
+
// address is lost money, not a nitpick.
|
|
219
205
|
export async function checkAddress(addr, opts = {}) {
|
|
220
206
|
const a = String(addr || "").trim();
|
|
221
207
|
if (!formatCheck(a)) {
|
|
@@ -235,13 +221,12 @@ export async function checkAddress(addr, opts = {}) {
|
|
|
235
221
|
if (payloadLen < 65) throw new Error("Address is too short");
|
|
236
222
|
const payload = bytes.slice(0, payloadLen);
|
|
237
223
|
const checksum = bytes.slice(payloadLen);
|
|
238
|
-
// keccak256
|
|
224
|
+
// keccak256 from the bundle needs Buffer: it does not take Uint8Array
|
|
239
225
|
const digest = Uint8Array.from(l.keccak256(l.Buffer.from(payload)));
|
|
240
226
|
const match = checksum.length === 4 && checksum.every((b, i) => b === digest[i]);
|
|
241
227
|
if (!match) {
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
// должен это говорить, иначе отладка уходит не туда.
|
|
228
|
+
// The demo mock addresses (wallet.js, mock mode) intentionally have no checksum: the UI marks them as fake,
|
|
229
|
+
// so a "typo" is not the only explanation for such an address and the text must say so.
|
|
245
230
|
return { ok: false, reason: "Checksum mismatch: the address has a typo, is corrupted, or is a demo mock address (mock addresses have no checksum)" };
|
|
246
231
|
}
|
|
247
232
|
|
|
@@ -257,7 +242,7 @@ export async function checkAddress(addr, opts = {}) {
|
|
|
257
242
|
}
|
|
258
243
|
}
|
|
259
244
|
|
|
260
|
-
//
|
|
245
|
+
// Helper for code that has already loaded the bundle: build an address of the right network from wallet keys.
|
|
261
246
|
export async function addressForNetwork({ network, kind = "primary", spendPub, viewPub }) {
|
|
262
247
|
const l = await loadLib();
|
|
263
248
|
if (!l) throw new Error("monero-js bundle is not available");
|
|
@@ -1,68 +1,50 @@
|
|
|
1
1
|
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/monero/halvesSweep.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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// с обязательством. Связь «раскрытая половина и точка в адресе» проверить он не может: ed25519 в EVM нет.
|
|
13
|
-
// Значит её проверяем мы, и ДО всякой синхронизации: (s_a + s_b)*G обязана совпасть с публичным ключом траты
|
|
14
|
-
// ордера. Не совпало - не метём и говорим прямо.
|
|
15
|
-
//
|
|
16
|
-
// Секреты здесь не создаются: обе половины уже есть у стороны (своя - в файле, чужая - раскрыта в цепи).
|
|
5
|
+
// KEYS FOR SWEEPING FROM A HALVES FILE. One logic for two consumers: the sweep page (sweep.js) and the
|
|
6
|
+
// check, so the two cannot drift.
|
|
7
|
+
// This is the main check of the whole sweep: the contract only verifies that the HASH of the revealed half
|
|
8
|
+
// matched the commitment, but ed25519 does not exist in the EVM, so WE must verify that (s_a + s_b)*G equals
|
|
9
|
+
// the order's public spend key - before any synchronisation. No match: do not sweep and say so.
|
|
10
|
+
// No secrets are created here: both halves already exist for the side (its own in the file, the other on chain).
|
|
17
11
|
export function spendKeysFromRevealedHalf({
|
|
18
12
|
halves, ownSpendHalf, ownViewHalf, otherSpendPoint, otherViewHalf, revealedHalf,
|
|
19
13
|
}) {
|
|
20
|
-
if (!halves) throw new Error("
|
|
14
|
+
if (!halves) throw new Error("half maths is required");
|
|
21
15
|
for (const [name, v] of Object.entries({ ownSpendHalf, ownViewHalf, otherSpendPoint, otherViewHalf, revealedHalf })) {
|
|
22
|
-
if (v === undefined || v === null || v === "") throw new Error("
|
|
16
|
+
if (v === undefined || v === null || v === "") throw new Error("not set: " + name);
|
|
23
17
|
}
|
|
24
18
|
const sum = halves.combineHalves(BigInt(ownSpendHalf), BigInt(revealedHalf));
|
|
25
19
|
const sumPub = halves.publicHalves(sum).ed;
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// уже ПОСЛЕ чтения раскрытой половины из цепи: до этой строки дело доходило, а дальше нет.
|
|
20
|
+
// The expected spend key is built FROM OWN HALF AND THE COUNTERPARTY POINT (what was in the order).
|
|
21
|
+
// The half from the file arrives as a string, so it must be converted - forgetting this crashed the XMR
|
|
22
|
+
// sweep with "expected bigint, got string".
|
|
30
23
|
const expected = halves.combinePublic(halves.publicHalves(BigInt(ownSpendHalf)).ed, otherSpendPoint);
|
|
31
24
|
const norm = (h) => String(h).replace(/^0x/i, "").toLowerCase();
|
|
32
25
|
if (norm(sumPub) !== norm(expected)) {
|
|
33
26
|
throw new Error(
|
|
34
|
-
"
|
|
35
|
-
"
|
|
27
|
+
"revealed half does not match the address: (s_a + s_b)*G != the order spend key. " +
|
|
28
|
+
"The half is from another order or substituted - sweeping is not allowed"
|
|
36
29
|
);
|
|
37
30
|
}
|
|
38
|
-
//
|
|
39
|
-
//
|
|
31
|
+
// The full VIEW key is also a sum (own half from the file + the counterparty half); it cannot spend, but
|
|
32
|
+
// without it the wallet cannot find our XMR on chain.
|
|
40
33
|
const viewSum = halves.combineHalves(BigInt(ownViewHalf), BigInt(otherViewHalf));
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
34
|
+
// BYTE ORDER IS A FORMAT, NOT COSMETICS, and here it was wrong until 28.09.2026: the old code wrote the
|
|
35
|
+
// scalar most-significant-byte first, while the library expects Monero order (least-significant first).
|
|
36
|
+
// Both monero-ts 0.11.16 builds rejected the reversed key; the workarounds kept elsewhere must not fire.
|
|
44
37
|
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// ОДИНАКОВО - значит расхождение было нашим, а не библиотеки: прежняя запись числа давала развёрнутый
|
|
49
|
-
// порядок, и обе точки входа чинили это обходным путём (страница вывода - reversedKey, инструмент узла -
|
|
50
|
-
// flip). Обходные пути оставлены КАК СЕТЬ для чужого файла, но срабатывать не должны, и срабатывание
|
|
51
|
-
// видно в журнале.
|
|
52
|
-
//
|
|
53
|
-
// ПРЕФИКСА 0x ЗДЕСЬ НЕТ И БЫТЬ НЕ ДОЛЖНО: библиотека выдаёт ключ 64 hex без префикса и такой принимает,
|
|
54
|
-
// а вид "0x"+64 hex отвергает на разборе - "failed to parse secret spend key".
|
|
55
|
-
const be32 = (v) => BigInt(v).toString(16).padStart(64, "0"); // обычная запись числа, старший байт первым
|
|
56
|
-
const le32 = (v) => be32(v).match(/../g).reverse().join(""); // запись Monero: младший байт первым
|
|
38
|
+
// NO 0x PREFIX here: the library returns and accepts 64 hex without it, and rejects "0x"+64 hex at parse.
|
|
39
|
+
const be32 = (v) => BigInt(v).toString(16).padStart(64, "0"); // ordinary number notation, most significant byte first
|
|
40
|
+
const le32 = (v) => be32(v).match(/../g).reverse().join(""); // Monero notation: least significant byte first
|
|
57
41
|
return { privateSpendKey: le32(sum), privateViewKey: le32(viewSum) };
|
|
58
42
|
}
|
|
59
43
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// без всякой ошибки. Ровно на этом мы уже стояли: и проверка, и инструмент пары разбирали ключ напрямую, и
|
|
63
|
-
// после исправления порядка байт оба обязаны были бы покраснеть - если бы их не поправили вместе с модулем.
|
|
44
|
+
// THE REVERSE IS ALSO ONE FUNCTION FOR ALL: reading a returned key directly as a number
|
|
45
|
+
// (BigInt("0x" + key)) silently reverses Monero order and gives another scalar.
|
|
64
46
|
export function scalarFromMoneroHex(h) {
|
|
65
47
|
const hex = String(h).replace(/^0x/i, "");
|
|
66
|
-
if (!/^[0-9a-f]{64}$/.test(hex)) throw new Error("
|
|
48
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) throw new Error("key is not in Monero notation: expected 64 hex");
|
|
67
49
|
return BigInt("0x" + hex.match(/../g).reverse().join(""));
|
|
68
50
|
}
|
package/engine/monero/node.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/monero/node.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
|
-
//
|
|
5
|
+
// The live Monero node: the only real external dependency of the demo.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// 1)
|
|
13
|
-
// 2)
|
|
14
|
-
//
|
|
7
|
+
// The node address is the MONERO.node constant in www/js/core/config.js - the proxy
|
|
8
|
+
// address is supplied at deploy time (see the deploy config for the Monero RPC proxy).
|
|
9
|
+
// The demo has no parameters or overrides: the address is one and visible in the code.
|
|
10
|
+
// We use the node for two honest things:
|
|
11
|
+
// 1) network status in the header: real height, nettype, node availability;
|
|
12
|
+
// 2) "live chain" mode: Monero confirmations are counted from real height growth,
|
|
13
|
+
// not from a timer (slow - a block is ~2 minutes, but real).
|
|
15
14
|
//
|
|
16
|
-
//
|
|
17
|
-
// (wallet2
|
|
15
|
+
// Balances, outputs and keys are NOT queried through this RPC: that needs a wallet
|
|
16
|
+
// (wallet2 in WASM + view key), which is the next step, not what the demo does.
|
|
18
17
|
|
|
19
18
|
import { MONERO } from "../core/config.js";
|
|
20
19
|
|
|
@@ -32,7 +31,7 @@ async function rpc(method, params = {}, path = "/json_rpc") {
|
|
|
32
31
|
const res = await fetch(RPC + path, {
|
|
33
32
|
method: "POST",
|
|
34
33
|
headers: { "Content-Type": "application/json" },
|
|
35
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: "
|
|
34
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: "ninsei-demo", method, params }),
|
|
36
35
|
signal: ctl.signal,
|
|
37
36
|
});
|
|
38
37
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
@@ -52,7 +51,7 @@ export function lastError() {
|
|
|
52
51
|
return cache.error;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
//
|
|
54
|
+
// Network height at call time (from cache, if fresh).
|
|
56
55
|
export async function height() {
|
|
57
56
|
const info = await getInfo();
|
|
58
57
|
return info ? info.height : null;
|
|
@@ -94,7 +93,7 @@ function normalize(info) {
|
|
|
94
93
|
};
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
//
|
|
96
|
+
// Poll every 30 seconds while the page is open: the header shows the live height.
|
|
98
97
|
let poller = null;
|
|
99
98
|
export function startPolling(onUpdate, intervalMs = 30_000) {
|
|
100
99
|
if (poller) clearInterval(poller);
|