@ninsei-labs/sdk 0.51.2 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/engine/core/config.js +16 -29
- package/engine/evm/factory.js +9 -2
- package/package.json +1 -1
- package/src/public/recovery.d.ts +4 -3
- package/src/recovery-key.mjs +26 -15
- package/src/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 0.52.0 — 2026-10-26
|
|
2
|
+
|
|
3
|
+
THE NEXT CONTRACT VERSION IS WIRED IN.
|
|
4
|
+
|
|
5
|
+
- `arbitrum-sepolia` moves to factory `0x69b5a6878b542960af4d239ecc60ad1c9e3e6932`, cashier
|
|
6
|
+
`0x05b7433f66f2b75cc7ece9ec00af0fb810f9c9e0`, registry
|
|
7
|
+
`0x4707a6684b654fbd74372ec624efc0f825af0537` and implementation code hash
|
|
8
|
+
`0xc6cde36301b6551a6ca8bd2c47fbd5ffd01ffcdf22d2f6909be2b3b2dcf360b4`
|
|
9
|
+
(deployment block `323014007`).
|
|
10
|
+
- `OrderCreated` now carries `xmrAmount`. `findSwaps` reads it from the current event and reports
|
|
11
|
+
`xmrAmountReason: "event"`; orders from the previous event keep the clone word-18 fallback.
|
|
12
|
+
- The previous event topic is still accepted, so an old factory remains searchable.
|
|
13
|
+
|
|
1
14
|
## 0.51.2 — 2026-10-22
|
|
2
15
|
|
|
3
16
|
- The withdrawal window falls back to the full scan when `scanTxs` or the balance read fails, and reports
|
package/engine/core/config.js
CHANGED
|
@@ -363,35 +363,22 @@ export const CHAINS = [
|
|
|
363
363
|
explorerName: "arbiscan (sepolia)",
|
|
364
364
|
gasUsd: 0.01,
|
|
365
365
|
routeType: "atomic",
|
|
366
|
-
// THE DEMO'S MAIN NETWORK: the Ninsei escrow factory and the interface's fee cashier live here
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
// PREVIOUS (ceiling 3%, no early refund): 0xEa619bDC78989627093B7830990367B2ECE52CE5 (height 314309038,
|
|
383
|
-
// code 8143 bytes). PREVIOUS live (router with a 10% ceiling): 0x9f856cd9c14fd9493961473b804039b09a4012ef
|
|
384
|
-
// (height 313597131, code 8723 bytes). PREVIOUS before it (factory without a fee):
|
|
385
|
-
// 0xcb54af9ec11344d448c7900ccb08530a23783259 (height 312222006, code 6035 bytes), and earlier ones still.
|
|
386
|
-
// mode: "live" - the form sends REAL transactions: order creation TOGETHER WITH THE MONEY in one transaction
|
|
387
|
-
// (createOrderAndFund; the escrow has no separate funding function - an order is born funded, see FACTORY_METHODS
|
|
388
|
-
// in www/js/evm/factory.js and ESCROW_METHODS in escrow.js). The old lock(sealedHalf) and hashlock are gone: the
|
|
389
|
-
// half is not sent but revealed by presentation. Switching back to "simulated" is one edit, to show a demo with
|
|
390
|
-
// no money movement. cashier: the INTERFACE's fee cashier, named in quotes and now deployed (the second CREATE of
|
|
391
|
-
// the same pass). implementationCodeHash: the escrow implementation the factory carries, checked against the chain.
|
|
392
|
-
// deploymentBlock: the height the factory was created at; a log search for its orders starts here, not at zero,
|
|
393
|
-
// because public RPCs cap a log query at about 10M blocks and would refuse a scan from genesis.
|
|
394
|
-
escrow: { address: "0x3eE25f12758ec28e5b3045d16132D89Cd0bf341F", mode: "live", deploymentBlock: 321430239, cashier: "0x1123A3875775Da25FA181Af0d91EB278E94a5dfb", implementationCodeHash: "0xf97af2b4eed8c646d82bce001e52ff03c065d43127073f8ee52271b65c35d659" },
|
|
366
|
+
// THE DEMO'S MAIN NETWORK: the Ninsei escrow factory and the interface's fee cashier live here. The next
|
|
367
|
+
// contract version (contracts-testnet-2026.10.26) is a FACT, not an intention:
|
|
368
|
+
// factory 0x69b5a6878b542960af4d239ecc60ad1c9e3e6932 (height 323014007,
|
|
369
|
+
// transaction 0xbe8244da0cb1429edc92d18c7c8b5ba1c6b17c42a5806609d930a6496e918408);
|
|
370
|
+
// cashier 0x05b7433f66f2b75cc7ece9ec00af0fb810f9c9e0;
|
|
371
|
+
// registry 0x4707a6684b654fbd74372ec624efc0f825af0537 (this release deploys it);
|
|
372
|
+
// implementation 0x2E0214c6242fB794a4cC861D609Ecee544735170, codehash
|
|
373
|
+
// 0xc6cde36301b6551a6ca8bd2c47fbd5ffd01ffcdf22d2f6909be2b3b2dcf360b4.
|
|
374
|
+
// `OrderCreated` now carries xmrAmount; the SDK reads it from the event and keeps the clone fallback for the
|
|
375
|
+
// previous event. PREVIOUS factory before this version: 0x3eE25f12758ec28e5b3045d16132D89Cd0bf341F
|
|
376
|
+
// (height 321430239, cashier 0x1123A3875775Da25FA181Af0d91EB278E94a5dfb).
|
|
377
|
+
// mode: "live" - order creation TOGETHER WITH THE MONEY in one transaction (createOrderAndFund). cashier: the
|
|
378
|
+
// INTERFACE's fee cashier, named in quotes. implementationCodeHash: the escrow implementation the factory
|
|
379
|
+
// carries, checked against the chain. deploymentBlock: the height the factory was created at; a log search for
|
|
380
|
+
// its orders starts here, not at zero, because public RPCs cap a log query at about 10M blocks.
|
|
381
|
+
escrow: { address: "0x69b5a6878b542960af4d239ecc60ad1c9e3e6932", mode: "live", deploymentBlock: 323014007, cashier: "0x05b7433f66f2b75cc7ece9ec00af0fb810f9c9e0", implementationCodeHash: "0xc6cde36301b6551a6ca8bd2c47fbd5ffd01ffcdf22d2f6909be2b3b2dcf360b4" },
|
|
395
382
|
tokens: [
|
|
396
383
|
// Test USDC (Circle): decimals 6 and symbol "USDC" verified on chain by the same node.
|
|
397
384
|
{ symbol: "USDC", address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", decimals: 6, usd: 1, cls: "ar-token-usdc" },
|
package/engine/evm/factory.js
CHANGED
|
@@ -22,11 +22,18 @@ export const FACTORY_METHODS = {
|
|
|
22
22
|
createOrderAndFund: "0x3a504547",
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
// The OrderCreated event topic. It grew the registry address, the provider and the signing key
|
|
26
|
-
//
|
|
25
|
+
// The OrderCreated event topic. It grew the registry address, the provider and the signing key, and now carries
|
|
26
|
+
// `xmrAmount` in bulk (#32):
|
|
27
|
+
// cast keccak "OrderCreated(address,address,address,bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint64,uint64,bytes32,address,address,address)"
|
|
27
28
|
// The topic lives in TWO places and both must match; a check pins them.
|
|
28
29
|
export const ORDER_CREATED_TOPIC =
|
|
30
|
+
"0x09add13cca47b227c377447295bfaa00ef3169bfed0f5513d06f8be04af78aad";
|
|
31
|
+
|
|
32
|
+
// THE PREVIOUS EVENT, KEPT FOR ORDERS ALREADY ON CHAIN. It has no `xmrAmount`, so those orders still fall back to
|
|
33
|
+
// reading word 18 of the clone's code. A new order uses the topic above.
|
|
34
|
+
export const ORDER_CREATED_TOPIC_V1 =
|
|
29
35
|
"0xa22cdabaed3cce4f51dde71626a108ffe9c7bc4216fd14d5e719f892e2fd8ab0";
|
|
36
|
+
export const ORDER_CREATED_TOPICS = Object.freeze([ORDER_CREATED_TOPIC, ORDER_CREATED_TOPIC_V1]);
|
|
30
37
|
|
|
31
38
|
// QUOTE FIELDS IN CONTRACT ORDER. The order is part of the ABI: a swap gives another call. One list for both
|
|
32
39
|
// functions, so they cannot diverge.
|
package/package.json
CHANGED
package/src/public/recovery.d.ts
CHANGED
|
@@ -333,10 +333,11 @@ export interface FoundSwap {
|
|
|
333
333
|
readonly payToken: string | null;
|
|
334
334
|
/** Why `payToken` is absent. `not-on-chain` means the order event and clone carry no token; do not infer one. */
|
|
335
335
|
readonly payTokenReason: "not-on-chain";
|
|
336
|
-
/** The XMR side of the order in atomic units
|
|
337
|
-
* was passed or the contract is not
|
|
336
|
+
/** The XMR side of the order in atomic units. The current factory emits it in `OrderCreated`; for an older event
|
|
337
|
+
* it is read from the clone's condition word 18, and null when no code reader was passed or the contract is not
|
|
338
|
+
* the expected clone. */
|
|
338
339
|
readonly xmrAmount: string | null;
|
|
339
|
-
readonly xmrAmountReason: "no-code-reader" | "code" | "not-clone" | null;
|
|
340
|
+
readonly xmrAmountReason: "event" | "no-code-reader" | "code" | "not-clone" | null;
|
|
340
341
|
readonly index: number;
|
|
341
342
|
readonly side: "locker" | "claimer";
|
|
342
343
|
/** The Monero height a withdrawal may start from, derived from the order's block time. Null when the caller did
|
package/src/recovery-key.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/b
|
|
|
17
17
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
18
18
|
import { createHalves } from "../engine/atomic/halves.js";
|
|
19
19
|
import { halfCommitmentHex } from "../engine/atomic/orderHalfCommit.js";
|
|
20
|
-
import { ORDER_CREATED_TOPIC } from "../engine/evm/factory.js";
|
|
20
|
+
import { ORDER_CREATED_TOPIC, ORDER_CREATED_TOPICS } from "../engine/evm/factory.js";
|
|
21
21
|
import { SdkError } from "./errors.mjs";
|
|
22
22
|
import { probeMoneroNode } from "./probes.mjs";
|
|
23
23
|
import { ed25519, keccak256, randomBytes, secp256k1 } from "./primitives.mjs";
|
|
@@ -359,10 +359,15 @@ function topicAddress(address) {
|
|
|
359
359
|
|
|
360
360
|
function decodeOrderLog(log) {
|
|
361
361
|
const topics = Array.isArray(log && log.topics) ? log.topics : [];
|
|
362
|
-
|
|
362
|
+
const topic0 = String(topics[0] || "").toLowerCase();
|
|
363
|
+
if (topics.length < 4 || !ORDER_CREATED_TOPICS.includes(topic0)) return null;
|
|
363
364
|
const data = String(log.data || "").replace(/^0x/, "");
|
|
364
|
-
|
|
365
|
+
const modern = topic0 === ORDER_CREATED_TOPIC;
|
|
366
|
+
if (data.length < 64 * (modern ? 13 : 12)) return null;
|
|
365
367
|
const word = (i) => "0x" + data.slice(i * 64, (i + 1) * 64).toLowerCase();
|
|
368
|
+
// THE FIELD OFFSETS MOVED WHEN `xmrAmount` JOINED THE EVENT. The old topic has no XMR amount; its orders fall
|
|
369
|
+
// back to the clone read, and a caller can still tell why from `xmrAmountReason`.
|
|
370
|
+
const shift = modern ? 1 : 0;
|
|
366
371
|
return {
|
|
367
372
|
escrow: "0x" + String(topics[1]).slice(26).toLowerCase(),
|
|
368
373
|
locker: "0x" + String(topics[2]).slice(26).toLowerCase(),
|
|
@@ -373,12 +378,13 @@ function decodeOrderLog(log) {
|
|
|
373
378
|
edPointClaimer: word(3),
|
|
374
379
|
edViewPointLocker: word(4),
|
|
375
380
|
amount: BigInt(word(5)).toString(),
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
xmrAmount: modern ? BigInt(word(6)).toString() : null,
|
|
382
|
+
readyBy: Number(BigInt(word(6 + shift))),
|
|
383
|
+
t1: Number(BigInt(word(7 + shift))),
|
|
384
|
+
salt: word(8 + shift),
|
|
385
|
+
registry: "0x" + data.slice((9 + shift) * 64 + 24, (10 + shift) * 64),
|
|
386
|
+
provider: "0x" + data.slice((10 + shift) * 64 + 24, (11 + shift) * 64),
|
|
387
|
+
quoteKey: "0x" + data.slice((11 + shift) * 64 + 24, (12 + shift) * 64),
|
|
382
388
|
transactionHash: log.transactionHash || null,
|
|
383
389
|
blockNumber: log.blockNumber != null ? Number(log.blockNumber) : null,
|
|
384
390
|
logIndex: log.logIndex != null ? Number(log.logIndex) : null,
|
|
@@ -431,7 +437,7 @@ async function readLogs(evmLogs, request) {
|
|
|
431
437
|
export async function recoveryIndexTaken({ mnemonic, chainId, index, factory, evmLogs, fromBlock = 0, toBlock = "latest" } = {}) {
|
|
432
438
|
if (typeof factory !== "string" || !ADDRESS_RE.test(factory)) throw bad("factory");
|
|
433
439
|
const own = takerHalves({ mnemonic, chainId, index });
|
|
434
|
-
const logs = await readLogs(evmLogs, { address: factory, topics: [
|
|
440
|
+
const logs = await readLogs(evmLogs, { address: factory, topics: [ORDER_CREATED_TOPICS], fromBlock, toBlock });
|
|
435
441
|
return logs.map(decodeOrderLog).filter(Boolean).some((order) =>
|
|
436
442
|
order.commitHalfLocker.toLowerCase() === own.spendCommitment.toLowerCase() ||
|
|
437
443
|
order.commitHalfClaimer.toLowerCase() === own.spendCommitment.toLowerCase());
|
|
@@ -581,8 +587,8 @@ export async function findSwaps({ mnemonic, chainId, factory, address = null, ev
|
|
|
581
587
|
if (address) {
|
|
582
588
|
const padded = topicAddress(address);
|
|
583
589
|
const [asLocker, asClaimer] = await Promise.all([
|
|
584
|
-
readLogs(evmLogs, { ...base, topics: [
|
|
585
|
-
readLogs(evmLogs, { ...base, topics: [
|
|
590
|
+
readLogs(evmLogs, { ...base, topics: [ORDER_CREATED_TOPICS, null, padded, null] }),
|
|
591
|
+
readLogs(evmLogs, { ...base, topics: [ORDER_CREATED_TOPICS, null, null, padded] }),
|
|
586
592
|
]);
|
|
587
593
|
const seen = new Set();
|
|
588
594
|
for (const log of asLocker.concat(asClaimer)) {
|
|
@@ -592,7 +598,7 @@ export async function findSwaps({ mnemonic, chainId, factory, address = null, ev
|
|
|
592
598
|
logs.push(log);
|
|
593
599
|
}
|
|
594
600
|
} else {
|
|
595
|
-
logs = await readLogs(evmLogs, { ...base, topics: [
|
|
601
|
+
logs = await readLogs(evmLogs, { ...base, topics: [ORDER_CREATED_TOPICS] });
|
|
596
602
|
}
|
|
597
603
|
const orders = logs.map(decodeOrderLog).filter(Boolean);
|
|
598
604
|
const found = [];
|
|
@@ -637,9 +643,14 @@ export async function findSwaps({ mnemonic, chainId, factory, address = null, ev
|
|
|
637
643
|
for (const order of found) {
|
|
638
644
|
Object.assign(order, await recoveredStartHeight(order, { evmBlockTime, moneroTip: tip, moneroBlockTimestamp: header, cache, readersReason }));
|
|
639
645
|
}
|
|
640
|
-
// THE XMR AMOUNT
|
|
641
|
-
//
|
|
646
|
+
// THE XMR AMOUNT COMES FROM THE EVENT IN THE CURRENT FACTORY. The previous event has no field; those orders fall
|
|
647
|
+
// back to the clone read, where word 18 is read only from a clone of the exact shape. This is data, not a
|
|
648
|
+
// withdrawal verdict.
|
|
642
649
|
for (const order of found) {
|
|
650
|
+
if (order.xmrAmount !== null && order.xmrAmount !== undefined) {
|
|
651
|
+
order.xmrAmountReason = "event";
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
643
654
|
if (typeof evmCode !== "function") {
|
|
644
655
|
order.xmrAmount = null;
|
|
645
656
|
order.xmrAmountReason = "no-code-reader";
|
package/src/version.mjs
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
// tools/sync-version.mjs. Do not edit it by hand - set the version in package.json and run `npm run sync:version`.
|
|
7
7
|
// The `version` check in tools/check.mjs refuses a mismatch, and refuses a runtime read of package.json here.
|
|
8
8
|
|
|
9
|
-
export const SDK_VERSION = "0.
|
|
9
|
+
export const SDK_VERSION = "0.52.0";
|