@ninsei-labs/sdk 0.41.0 → 0.42.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 +42 -0
- package/README.md +11 -0
- package/index.d.ts +76 -0
- package/package.json +27 -3
- package/src/index.mjs +1 -0
- package/src/levelsWalkSpec.mjs +39 -23
- package/src/public/core.d.ts +13 -0
- package/src/public/core.mjs +6 -0
- package/src/public/evm.d.ts +184 -0
- package/src/public/evm.mjs +29 -0
- package/src/public/order.d.ts +161 -0
- package/src/public/order.mjs +22 -0
- package/src/public/recovery.d.ts +243 -0
- package/src/public/recovery.mjs +19 -0
- package/src/public/xmr.d.ts +318 -0
- package/src/public/xmr.mjs +29 -0
- package/src/version.mjs +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
## 0.42.1 — 2026-10-12
|
|
2
|
+
|
|
3
|
+
THE BID SIDE OF THE QUOTE WALK ROUNDED THE WRONG WAY. The specification fixes a rounding DIRECTION per side, and
|
|
4
|
+
the walk rounded both sides alike: a bid price came out up to 3 wei high and disagreed with the reference at a
|
|
5
|
+
level boundary.
|
|
6
|
+
|
|
7
|
+
- Per-level rounding follows the side now: a level's cost rounds up on the ask side, where the user pays, and down
|
|
8
|
+
on the bid side, where the user is paid; the XMR amount a budget yields at a level rounds up on the bid side.
|
|
9
|
+
- The level-fits test is asymmetric: a level is taken whole when `remaining >= full` on the ask side and when
|
|
10
|
+
`remaining > full` on the bid side.
|
|
11
|
+
- This removes a systematic 1-to-3-wei over-charge on bid prices and the disagreement with the reference at level
|
|
12
|
+
boundaries.
|
|
13
|
+
|
|
14
|
+
Checks: `node tools/check.mjs` - all 19 green; `node tools/sync-version.mjs`, `node tools/npm-tag.test.mjs`, the two
|
|
15
|
+
teeth runs and `tools/sweep-refusal.test.mjs` are green too.
|
|
16
|
+
|
|
17
|
+
## 0.42.0 — 2026-10-12
|
|
18
|
+
|
|
19
|
+
EVERY ENTRY THIS PACKAGE PUBLISHES CARRIES ITS OWN DECLARATIONS, AND THE SWEEP PAGE'S TWO PRIMITIVES ARE ON
|
|
20
|
+
`./xmr`. A node that imported a subpath got no types at all - only the root had them - while a sweep page kept its
|
|
21
|
+
own copies of two rules the engine already carries. Both are closed here.
|
|
22
|
+
|
|
23
|
+
- `./xmr`, `./order`, `./evm`, `./core` and `./recovery` each carry a declaration file next to their barrel
|
|
24
|
+
(`src/public/<entry>.d.ts`) declaring exactly the symbols that barrel re-exports, with the signatures read off the
|
|
25
|
+
engine modules. `./recovery` opens the recovery file a sweep page reads back: `ENVELOPE_VERSION`, `FILE_MAGIC`,
|
|
26
|
+
`PAYLOAD_VERSION`, `createRecoveryFile`, `decryptFile`, `scanStartHeight` and the rest of its 11 symbols.
|
|
27
|
+
- `./xmr` gains the sweep side: `spendKeysFromRevealedHalf` (the spend and view keys a revealed half yields, with the
|
|
28
|
+
check that `(s_a + s_b)*G` equals the order's public spend key before anything is swept) and `scalarFromMoneroHex`
|
|
29
|
+
(a Monero-notation key read as a number, never through `BigInt("0x" + key)`, which reverses the byte order).
|
|
30
|
+
- A `types` check keeps the declarations and the runtime equal in both directions: every subpath with an import or
|
|
31
|
+
default target must declare `types` (a subpath left without them must be named, with its reason, in the check's own
|
|
32
|
+
table - `./adapters` is), the declared file must exist and read as declarations, and every symbol the barrel
|
|
33
|
+
re-exports must be declared, and nothing else. `tools/check-types-teeth.mjs` is its breaking run: four patches, each
|
|
34
|
+
RED with its own named reason, each file restored byte for byte.
|
|
35
|
+
- `SDK_VERSION` is baked into `src/version.mjs` from `package.json` by `npm run sync:version`: a page cannot fetch
|
|
36
|
+
`package.json`, so the version is a value in the code, and the `version` check refuses a mismatch and refuses a
|
|
37
|
+
runtime read of the file. `engines.node` moves to `>=22`.
|
|
38
|
+
- `RouteRefusalCode` declares all 33 codes the four route providers refuse with, and a `refusals` check refuses to let
|
|
39
|
+
the provider tables and the type drift apart in either direction.
|
|
40
|
+
|
|
41
|
+
Nothing that already existed changed behaviour: the synchronous swap path is untouched.
|
|
42
|
+
|
|
1
43
|
## 0.41.0 — 2026-10-11
|
|
2
44
|
|
|
3
45
|
THE DEPOSITOR PATH IS GONE. The factory no longer offers `createOrderAndFundByDepositor`, so the package must
|
package/README.md
CHANGED
|
@@ -31,6 +31,13 @@ value that looks like data is more dangerous than a refusal.
|
|
|
31
31
|
`index.d.ts` is the single source of truth about the API shape, and this is checked: `tools/check-sdk.mjs`
|
|
32
32
|
compares the list of what the SDK returns with what is declared in the contract (both ways).
|
|
33
33
|
|
|
34
|
+
EVERY PUBLISHED SUBPATH CARRIES ITS OWN DECLARATIONS: `.` (`index.d.ts`), `./xmr`, `./order`, `./evm`,
|
|
35
|
+
`./core` and `./recovery` (`src/public/<entry>.d.ts`). Each of them declares exactly the symbols its barrel
|
|
36
|
+
re-exports, so a typed consumer sees the same surface the runtime has, and nothing else.
|
|
37
|
+
`tools/check.mjs --only=types` keeps the two sides equal (a subpath without a `types` target, a declarations
|
|
38
|
+
file that does not read as declarations, or a symbol declared on one side only is red), and
|
|
39
|
+
`tools/check-types-teeth.mjs` is its breaking run.
|
|
40
|
+
|
|
34
41
|
```js
|
|
35
42
|
import { createNinsei } from "@ninsei-labs/sdk";
|
|
36
43
|
|
|
@@ -209,3 +216,7 @@ The tag is passed to `npm publish` explicitly (`--tag <tag>`), and `tools/npm-ta
|
|
|
209
216
|
can go wrong **by name** - a prerelease that would take `latest`, and a plain version that would slide into a
|
|
210
217
|
channel someone else owns. A manual run of the release workflow may override the tag with an input; when it does,
|
|
211
218
|
the log says so. The derivation and its refusals are covered by `node tools/npm-tag.test.mjs`.
|
|
219
|
+
|
|
220
|
+
Before the tag, the version constant moves with the version: `npm run sync:version` writes `src/version.mjs` from
|
|
221
|
+
`package.json`. The number is BAKED into the code on purpose - a page cannot fetch `package.json`, and the file
|
|
222
|
+
need not be served beside a bundle - so `npm run check` refuses both a mismatch and a runtime read of it.
|
package/index.d.ts
CHANGED
|
@@ -494,6 +494,81 @@ export interface QuoteOffer {
|
|
|
494
494
|
readonly networks: { readonly asset: string | null; readonly currency: string | null; readonly stated: boolean };
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Every code a route provider of this package refuses with.
|
|
499
|
+
*
|
|
500
|
+
* The providers already name them in their own tables (`COW_REFUSAL_CODES` and its three siblings), and the
|
|
501
|
+
* battery proves each of those is reachable; what was missing was the TYPE, so an interface could enumerate
|
|
502
|
+
* them instead of meeting a string it cannot switch on. `broken` is shared by all four: it is the code for a
|
|
503
|
+
* case a provider could not classify. `RouteRefusalTables` below declares what those tables ARE - each of them a
|
|
504
|
+
* `readonly RouteRefusalCode[]` - so the union is the tables' TYPE and not a comment standing beside them.
|
|
505
|
+
*
|
|
506
|
+
* These are OUR codes, and they are what a person's message should be phrased from. A provider's OWN reason
|
|
507
|
+
* stays opaque on purpose (`QuoteRefusal.why`): that is data the provider sent, not a name of ours, so it is
|
|
508
|
+
* deliberately NOT part of this union.
|
|
509
|
+
*/
|
|
510
|
+
export type RouteRefusalCode =
|
|
511
|
+
// cow
|
|
512
|
+
| "cow-bad-order"
|
|
513
|
+
| "cow-bad-response"
|
|
514
|
+
| "cow-not-settled"
|
|
515
|
+
| "cow-order-unknown"
|
|
516
|
+
| "cow-refused"
|
|
517
|
+
| "cow-sign-failed"
|
|
518
|
+
| "cow-unknown-network"
|
|
519
|
+
| "cow-unreachable"
|
|
520
|
+
// kyber
|
|
521
|
+
| "kyber-bad-order"
|
|
522
|
+
| "kyber-bad-response"
|
|
523
|
+
| "kyber-not-settled"
|
|
524
|
+
| "kyber-order-unknown"
|
|
525
|
+
| "kyber-refused"
|
|
526
|
+
| "kyber-sign-failed"
|
|
527
|
+
| "kyber-unknown-network"
|
|
528
|
+
| "kyber-unreachable"
|
|
529
|
+
// uniswapx
|
|
530
|
+
| "uniswapx-bad-order"
|
|
531
|
+
| "uniswapx-bad-response"
|
|
532
|
+
| "uniswapx-no-submitter"
|
|
533
|
+
| "uniswapx-not-settled"
|
|
534
|
+
| "uniswapx-order-unknown"
|
|
535
|
+
| "uniswapx-refused"
|
|
536
|
+
| "uniswapx-sign-failed"
|
|
537
|
+
| "uniswapx-unknown-network"
|
|
538
|
+
| "uniswapx-unreachable"
|
|
539
|
+
// uniswap-relay
|
|
540
|
+
| "uniswap-relay-bad-order"
|
|
541
|
+
| "uniswap-relay-bad-response"
|
|
542
|
+
| "uniswap-relay-no-discovery"
|
|
543
|
+
| "uniswap-relay-not-settled"
|
|
544
|
+
| "uniswap-relay-sign-failed"
|
|
545
|
+
| "uniswap-relay-unknown-network"
|
|
546
|
+
| "uniswap-relay-unreachable"
|
|
547
|
+
// shared
|
|
548
|
+
| "broken";
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* WHAT THE FOUR REFUSAL TABLES *ARE*. Each route provider names its own refusals in one table - `COW_REFUSAL_CODES`
|
|
552
|
+
* (src/legs/cow.mjs), `KYBER_REFUSAL_CODES` (src/legs/kyber.mjs), `UNISWAPX_REFUSAL_CODES` (src/legs/uniswapx.mjs) and
|
|
553
|
+
* `UNISWAPRELAY_REFUSAL_CODES` (src/legs/uniswaprelay.mjs) - and every one of those tables is a
|
|
554
|
+
* `readonly RouteRefusalCode[]`. The dependency runs ONE WAY on purpose: a table answers to the union (a code that is
|
|
555
|
+
* not declared above cannot enter a table), while the union answers to no single table, so the same vocabulary is
|
|
556
|
+
* met in four modules.
|
|
557
|
+
*
|
|
558
|
+
* THEY ARE NOT ON THIS ENTRY'S SURFACE, and this is deliberately NOT a value: the package re-exports no provider and
|
|
559
|
+
* no table (src/index.mjs), the tables travel in the shipped source (`files` in package.json) and they are the
|
|
560
|
+
* VOCABULARY, not an API. A `declare const` here that the runtime does not export would be a lie, and the `surface`
|
|
561
|
+
* check refuses exactly that - so the tables are declared as their TYPE. That is what keeps the union from being a
|
|
562
|
+
* comment beside the tables: this declaration says what they are, and the `refusals` check says the same thing from
|
|
563
|
+
* the other side - a table declared here as a plain `readonly string[]` is a named problem there.
|
|
564
|
+
*/
|
|
565
|
+
export interface RouteRefusalTables {
|
|
566
|
+
readonly COW_REFUSAL_CODES: readonly RouteRefusalCode[];
|
|
567
|
+
readonly KYBER_REFUSAL_CODES: readonly RouteRefusalCode[];
|
|
568
|
+
readonly UNISWAPX_REFUSAL_CODES: readonly RouteRefusalCode[];
|
|
569
|
+
readonly UNISWAPRELAY_REFUSAL_CODES: readonly RouteRefusalCode[];
|
|
570
|
+
}
|
|
571
|
+
|
|
497
572
|
export interface QuoteRefusal {
|
|
498
573
|
readonly providerId: string;
|
|
499
574
|
readonly displayName: string | null;
|
|
@@ -1373,6 +1448,7 @@ export declare class SdkError extends Error {
|
|
|
1373
1448
|
}
|
|
1374
1449
|
|
|
1375
1450
|
/** The step codes the SDK works with (`state.step`): the same list as the `StepCode` type. */
|
|
1451
|
+
export declare const SDK_VERSION: string;
|
|
1376
1452
|
export declare const STEP_CODES: readonly StepCode[];
|
|
1377
1453
|
/** The check codes a preflight verdict names (`checks[].code`): the same list as the `CheckCode` type. */
|
|
1378
1454
|
export declare const CHECK_CODES: readonly CheckCode[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninsei-labs/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
"check:mirror": "node tools/check.mjs --only=mirror",
|
|
19
19
|
"check:pack": "node tools/check.mjs --only=pack",
|
|
20
20
|
"check:secrets": "node tools/check.mjs --only=secrets",
|
|
21
|
-
"check:
|
|
21
|
+
"check:types": "node tools/check.mjs --only=types",
|
|
22
|
+
"check:types-teeth": "node tools/check-types-teeth.mjs",
|
|
23
|
+
"check:refusals": "node tools/check.mjs --only=refusals",
|
|
24
|
+
"check:refusals-teeth": "node tools/check-refusals-teeth.mjs",
|
|
25
|
+
"check:sweep": "node tools/sweep-refusal.test.mjs", "sync:version": "node tools/sync-version.mjs"
|
|
22
26
|
},
|
|
23
27
|
"types": "./index.d.ts",
|
|
24
28
|
"exports": {
|
|
@@ -29,6 +33,26 @@
|
|
|
29
33
|
},
|
|
30
34
|
"./adapters": {
|
|
31
35
|
"default": "./src/adapters/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./xmr": {
|
|
38
|
+
"types": "./src/public/xmr.d.ts",
|
|
39
|
+
"default": "./src/public/xmr.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./order": {
|
|
42
|
+
"types": "./src/public/order.d.ts",
|
|
43
|
+
"default": "./src/public/order.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./evm": {
|
|
46
|
+
"types": "./src/public/evm.d.ts",
|
|
47
|
+
"default": "./src/public/evm.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./core": {
|
|
50
|
+
"types": "./src/public/core.d.ts",
|
|
51
|
+
"default": "./src/public/core.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./recovery": {
|
|
54
|
+
"types": "./src/public/recovery.d.ts",
|
|
55
|
+
"default": "./src/public/recovery.mjs"
|
|
32
56
|
}
|
|
33
57
|
},
|
|
34
58
|
"files": [
|
|
@@ -40,7 +64,7 @@
|
|
|
40
64
|
"LICENSE"
|
|
41
65
|
],
|
|
42
66
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
67
|
+
"node": ">=22"
|
|
44
68
|
},
|
|
45
69
|
"keywords": [
|
|
46
70
|
"xmr",
|
package/src/index.mjs
CHANGED
|
@@ -270,6 +270,7 @@ export function createNinsei(options) {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
export { SdkError, ERROR_CODES, STEP_CODES, CHECK_CODES };
|
|
273
|
+
export { SDK_VERSION } from "./version.mjs";
|
|
273
274
|
|
|
274
275
|
// THE LEVEL WALK IS ON THE PACKAGE SURFACE. A maker publishes a level set (chunks of volume with their own prices)
|
|
275
276
|
// and both the node and the client compute a firm price by WALKING it - the client must reach the SAME price before
|
package/src/levelsWalkSpec.mjs
CHANGED
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
// 3. the first level sets the minimum deal size (there is no separate min);
|
|
20
20
|
// 4. an EMPTY set = the side is off, and that is NOT an error;
|
|
21
21
|
// 5. the walk goes BOTH ways: "I give asset - how much XMR" and "I give XMR - how much asset";
|
|
22
|
-
// 6. rounding to atomic units is in the maker's favour
|
|
22
|
+
// 6. rounding to atomic units is in the maker's favour, EACH LEVEL ON ITS OWN: up on ask, where the user pays,
|
|
23
|
+
// down on bid, where the user is paid; the answer in XMR is rounded the maker's way too, down for a buyer
|
|
24
|
+
// (ask) and up for a seller (bid);
|
|
23
25
|
// 7. one orientation: volume in XMR, price in the asset per 1 XMR;
|
|
24
26
|
// 8. a fixed swap fee (fee in wei, separate for each side): the buyer pays the walk PLUS the fee, the seller
|
|
25
27
|
// receives the walk MINUS the fee. It is NOT spread over the volume: otherwise the average price would fall
|
|
@@ -83,51 +85,65 @@ export function checkLevels(levels, orientation = "ask") {
|
|
|
83
85
|
return { ok: true, enabled: true, count: levels.length, minAtomic: toBigInt(levels[0].amount), totalAtomic };
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// ROUND A NON-NEGATIVE FRACTION TO A WHOLE UNIT: up, or down. Money is BigInt only (rule 10); the fraction is
|
|
89
|
+
// non-negative where this is reached (amounts and prices are positive, a budget is positive).
|
|
90
|
+
function divRound(num, den, up) {
|
|
91
|
+
const q = num / den;
|
|
92
|
+
return up && q * den < num ? q + 1n : q;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
// THE COST OF A CHUNK OF VOLUME IN THE ASSET, ONE LEVEL AT A TIME (rule 6). Rounding is always in the maker's
|
|
96
|
+
// favour: UP on ask, where the user pays, DOWN on bid, where the node pays the user. A walk rounds each level on
|
|
97
|
+
// its own and adds the levels up, as the node does: rounding the sum once would differ by up to a unit per level,
|
|
98
|
+
// and the node would sign the maker another amount than its journal books.
|
|
99
|
+
function levelCostWei(amountAtomic, priceWeiPerXmr, orientation) {
|
|
100
|
+
return divRound(amountAtomic * priceWeiPerXmr, XMR_ATOMIC_PER_ONE, orientation === "ask");
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
//
|
|
98
|
-
|
|
103
|
+
// HOW MANY XMR ATOMS A BUDGET GIVES WITHIN ONE LEVEL (rule 6). The answer in XMR is rounded the maker's way too:
|
|
104
|
+
// DOWN for a buyer (ask), UP for a seller (bid). At a coarse asset several atomic units pay the same, and a
|
|
105
|
+
// seller sends no more of them than the payout needs.
|
|
106
|
+
function levelXmrForBudget(budgetWei, priceWeiPerXmr, orientation) {
|
|
107
|
+
return divRound(budgetWei * XMR_ATOMIC_PER_ONE, priceWeiPerXmr, orientation === "bid");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// THE FULL COST OF A SIDE (the sum of its levels' costs, each rounded on its own) and its volume - computed once.
|
|
111
|
+
function sideSummary(levels, orientation) {
|
|
99
112
|
let totalAtomic = 0n;
|
|
100
113
|
let fullCostWei = 0n;
|
|
101
114
|
for (const level of levels) {
|
|
102
115
|
const amount = toBigInt(level.amount);
|
|
103
116
|
const price = toBigInt(level.price);
|
|
104
117
|
totalAtomic += amount;
|
|
105
|
-
fullCostWei += levelCostWei(amount, price);
|
|
118
|
+
fullCostWei += levelCostWei(amount, price, orientation);
|
|
106
119
|
}
|
|
107
120
|
return { totalAtomic, fullCostWei };
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
// THE WALK WITHOUT THE FEE: a budget is given - how much XMR it yields (
|
|
111
|
-
function walkXmrForBudget(levels, budgetWei) {
|
|
123
|
+
// THE WALK WITHOUT THE FEE: a budget is given - how much XMR it yields (each level rounded on its own, rule 6).
|
|
124
|
+
function walkXmrForBudget(levels, budgetWei, orientation) {
|
|
112
125
|
let remaining = budgetWei;
|
|
113
126
|
let xmrAtomic = 0n;
|
|
114
127
|
for (const level of levels) {
|
|
115
128
|
const amount = toBigInt(level.amount);
|
|
116
129
|
const price = toBigInt(level.price);
|
|
117
|
-
const full = levelCostWei(amount, price);
|
|
118
|
-
|
|
130
|
+
const full = levelCostWei(amount, price, orientation);
|
|
131
|
+
// The maker's way once more: a buyer (ask) takes a level whole even when the budget only just reaches it; a
|
|
132
|
+
// seller (bid) sends no more than the payout needs, so an exact match is left to the partial step, rounded up.
|
|
133
|
+
const fits = orientation === "ask" ? remaining >= full : remaining > full;
|
|
134
|
+
if (fits) {
|
|
119
135
|
xmrAtomic += amount;
|
|
120
136
|
remaining -= full;
|
|
121
137
|
continue;
|
|
122
138
|
}
|
|
123
|
-
xmrAtomic += levelXmrForBudget(remaining, price);
|
|
139
|
+
xmrAtomic += levelXmrForBudget(remaining, price, orientation);
|
|
124
140
|
break;
|
|
125
141
|
}
|
|
126
142
|
return xmrAtomic;
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
// THE WALK WITHOUT THE FEE: an XMR volume is given - what it costs (each chunk's cost rounded
|
|
130
|
-
function walkCostForXmr(levels, xmrAtomic) {
|
|
145
|
+
// THE WALK WITHOUT THE FEE: an XMR volume is given - what it costs (each chunk's cost rounded on its own, rule 6).
|
|
146
|
+
function walkCostForXmr(levels, xmrAtomic, orientation) {
|
|
131
147
|
let left = xmrAtomic;
|
|
132
148
|
let costWei = 0n;
|
|
133
149
|
for (const level of levels) {
|
|
@@ -135,7 +151,7 @@ function walkCostForXmr(levels, xmrAtomic) {
|
|
|
135
151
|
const amount = toBigInt(level.amount);
|
|
136
152
|
const price = toBigInt(level.price);
|
|
137
153
|
const take = left < amount ? left : amount;
|
|
138
|
-
costWei += levelCostWei(take, price);
|
|
154
|
+
costWei += levelCostWei(take, price, orientation);
|
|
139
155
|
left -= take;
|
|
140
156
|
}
|
|
141
157
|
return costWei;
|
|
@@ -166,16 +182,16 @@ export function xmrForAsset({ levels, role, feeWei = 0n, assetWei, orientation =
|
|
|
166
182
|
if (asset === null || fee === null || fee < 0n) return { ok: false, reason: "levels-not-integer", role };
|
|
167
183
|
const budget = role === "buy" ? asset - fee : asset + fee;
|
|
168
184
|
if (budget <= 0n) return { ok: false, reason: "levels-fee-beyond-budget", role };
|
|
169
|
-
const { fullCostWei } = sideSummary(levels);
|
|
185
|
+
const { fullCostWei } = sideSummary(levels, orientation);
|
|
170
186
|
if (budget > fullCostWei) return { ok: false, reason: "levels-exceed-side", role };
|
|
171
187
|
// THE SMALLEST TRADE, from the set AS THE MAKER SENT IT (rule 3): callers pass it in minAtomic and the
|
|
172
188
|
// node's deductions never change it. Absent, the set's own first level is used, as it always was.
|
|
173
189
|
const min = minOf(levels, minAtomic);
|
|
174
190
|
// Its cost ON THE EFFECTIVE SET: after deductions the smallest trade may reach into a later level, so the
|
|
175
191
|
// cost is walked, not read off one price.
|
|
176
|
-
const minCost = walkCostForXmr(levels, min);
|
|
192
|
+
const minCost = walkCostForXmr(levels, min, orientation);
|
|
177
193
|
if (budget < minCost) return { ok: false, reason: "levels-below-minimum", role };
|
|
178
|
-
const xmrAtomic = walkXmrForBudget(levels, budget);
|
|
194
|
+
const xmrAtomic = walkXmrForBudget(levels, budget, orientation);
|
|
179
195
|
return { ok: true, role, xmrAtomic, assetWei: asset };
|
|
180
196
|
}
|
|
181
197
|
|
|
@@ -193,7 +209,7 @@ export function assetForXmr({ levels, role, feeWei = 0n, xmrAtomic, orientation
|
|
|
193
209
|
if (want > verdict.totalAtomic) return { ok: false, reason: "levels-exceed-side", role };
|
|
194
210
|
// THE SMALLEST TRADE AS THE MAKER SENT IT, never the effective set's first level (rule 3): see minOf.
|
|
195
211
|
if (want < minOf(levels, minAtomic)) return { ok: false, reason: "levels-below-minimum", role };
|
|
196
|
-
const walkWei = walkCostForXmr(levels, want);
|
|
212
|
+
const walkWei = walkCostForXmr(levels, want, orientation);
|
|
197
213
|
const assetWei = role === "buy" ? walkWei + fee : walkWei - fee;
|
|
198
214
|
if (assetWei <= 0n) return { ok: false, reason: "levels-fee-beyond-budget", role };
|
|
199
215
|
return { ok: true, role, xmrAtomic: want, assetWei };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// DECLARATIONS FOR THE `./core` ENTRY.
|
|
2
|
+
//
|
|
3
|
+
// src/public/core.mjs only NAMES what the engine already offers; these declarations NAME the same thing for a
|
|
4
|
+
// TypeScript consumer. Every signature is copied from the engine module the barrel re-exports
|
|
5
|
+
// (`../../engine/core/format.js`), and a symbol the barrel does not re-export is not declared here.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The number a person entered, in units, or null when the text carries no number.
|
|
9
|
+
*
|
|
10
|
+
* A comma without a dot is the decimal separator ("0,05" is 0.05) and a comma beside a dot separates thousands
|
|
11
|
+
* ("1,234.5" is 1234.5), so what the field shows and what the rest of the code computes cannot diverge.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseAmount(raw: string | number | null | undefined): number | null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// NEUTRAL HELPERS that the entries above and their consumers share: a decimal amount typed as text, in units.
|
|
2
|
+
//
|
|
3
|
+
// One function today. It is a separate entry because it belongs to neither the XMR side nor the EVM side, and a
|
|
4
|
+
// consumer forced to import it from either would learn a false lesson about where it lives.
|
|
5
|
+
|
|
6
|
+
export { parseAmount } from "../../engine/core/format.js";
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// DECLARATIONS FOR THE `./evm` ENTRY: factory calldata and the arithmetic of a claim.
|
|
2
|
+
//
|
|
3
|
+
// src/public/evm.mjs only NAMES what the engine already offers; these declarations NAME the same thing for a
|
|
4
|
+
// TypeScript consumer. Every signature is copied from the modules the barrel re-exports
|
|
5
|
+
// (`../../engine/evm/factory.js`, `../../engine/evm/claimGas.js`), and a symbol the barrel does not re-export is
|
|
6
|
+
// not declared here.
|
|
7
|
+
|
|
8
|
+
/** The factory's calldata selectors, computed from the contract's own signatures. */
|
|
9
|
+
export declare const FACTORY_METHODS: {
|
|
10
|
+
readonly predict: string;
|
|
11
|
+
readonly createOrderAndFund: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** The factory getters read to check the single shared escrow implementation every clone runs. */
|
|
15
|
+
export declare const FACTORY_READS: {
|
|
16
|
+
readonly implementation: string;
|
|
17
|
+
readonly implementationCodeHash: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** The OrderCreated event topic: the same value the contract declares. */
|
|
21
|
+
export declare const ORDER_CREATED_TOPIC: string;
|
|
22
|
+
|
|
23
|
+
/** The signed quote fields IN CONTRACT ORDER. The order is part of the ABI: a rearrangement is another call. */
|
|
24
|
+
export declare const QUOTE_FIELDS: readonly string[];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Everything `predict` and `createOrderAndFund` encode: the signed quote, the provider's signature, and the three
|
|
28
|
+
* depositor fields that travel as separate arguments (they go into the escrow's init code).
|
|
29
|
+
*/
|
|
30
|
+
export interface FactoryQuote {
|
|
31
|
+
readonly provider: string;
|
|
32
|
+
readonly quoteKey: string;
|
|
33
|
+
readonly registry: string;
|
|
34
|
+
readonly cashier: string;
|
|
35
|
+
readonly locker: string;
|
|
36
|
+
readonly claimer: string;
|
|
37
|
+
readonly amount: string | number | bigint;
|
|
38
|
+
readonly readyBy: string | number | bigint;
|
|
39
|
+
readonly t1: string | number | bigint;
|
|
40
|
+
readonly salt: string;
|
|
41
|
+
readonly commitHalfClaimer: string;
|
|
42
|
+
readonly edPointClaimer: string;
|
|
43
|
+
readonly chainId: string | number | bigint;
|
|
44
|
+
readonly feeBps: string | number | bigint;
|
|
45
|
+
readonly fee: string | number | bigint;
|
|
46
|
+
readonly feeRecipient: string;
|
|
47
|
+
readonly validUntil: string | number | bigint;
|
|
48
|
+
readonly nonce: string | number | bigint;
|
|
49
|
+
readonly xmrAmount: string | number | bigint;
|
|
50
|
+
/** r || s || v over the quote digest, 65 bytes, with or without the `0x` prefix. */
|
|
51
|
+
readonly signature: string;
|
|
52
|
+
readonly commitHalfLocker: string;
|
|
53
|
+
readonly edPointLocker: string;
|
|
54
|
+
readonly edViewPointLocker: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One calldata word: the value right-aligned to `bytes` (32 unless another width is asked for). Decimal digits are
|
|
59
|
+
* turned into hex as a NUMBER, so padding cannot change the value. Refuses anything that is neither.
|
|
60
|
+
*/
|
|
61
|
+
export declare function word(value: string | number | bigint, bytes?: number): string;
|
|
62
|
+
|
|
63
|
+
/** The `predict` call: the argument set matches `encodeCreateOrderAndFund` byte for byte. */
|
|
64
|
+
export declare function encodePredict(quote: FactoryQuote): string;
|
|
65
|
+
|
|
66
|
+
/** The `createOrderAndFund` call: creation and deposit in one transaction. */
|
|
67
|
+
export declare function encodeCreateOrderAndFund(quote: FactoryQuote): string;
|
|
68
|
+
|
|
69
|
+
/** `predict()`'s answer - one address in a 32-byte word. */
|
|
70
|
+
export declare function decodeAddress(returnedHex: string): string;
|
|
71
|
+
|
|
72
|
+
/** `implementation()`'s answer. */
|
|
73
|
+
export declare function decodeImplementation(returnedHex: string): string;
|
|
74
|
+
|
|
75
|
+
/** `implementationCodeHash()`'s answer - one bytes32 word. */
|
|
76
|
+
export declare function decodeImplementationCodeHash(returnedHex: string): string;
|
|
77
|
+
|
|
78
|
+
/** The getter's calldata by name: it takes no arguments, so the selector is the whole call. */
|
|
79
|
+
export declare function encodeFactoryRead(name: "implementation" | "implementationCodeHash"): string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The verdict on the escrow implementation, by the same three conditions the factory applies at order creation:
|
|
83
|
+
* the address is named and its code is non-empty, the recorded hash matches, and the hash is in the interface
|
|
84
|
+
* policy if one is set. Refusals are named, never silent.
|
|
85
|
+
*/
|
|
86
|
+
export type FactoryImplementationVerdict =
|
|
87
|
+
| {
|
|
88
|
+
readonly ok: false;
|
|
89
|
+
readonly why: string;
|
|
90
|
+
readonly codeHash?: string;
|
|
91
|
+
readonly got?: string;
|
|
92
|
+
readonly want?: string;
|
|
93
|
+
}
|
|
94
|
+
| { readonly ok: true; readonly codeHash: string };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* What the verdict is read from. `code` is the implementation's on-chain code; `hashCode` recomputes it when the
|
|
98
|
+
* carrier can hash bytes. An unreadable base is not zero: the reason names what was missing.
|
|
99
|
+
*/
|
|
100
|
+
export interface FactoryImplementationRequest {
|
|
101
|
+
readonly implementation?: string;
|
|
102
|
+
readonly implementationCodeHash?: string;
|
|
103
|
+
readonly code?: string | null;
|
|
104
|
+
readonly hashCode?: ((code: string) => string) | null;
|
|
105
|
+
/** The interface's own list of known builds. An empty or absent list means "no policy". */
|
|
106
|
+
readonly policy?: readonly string[] | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The verdict on the implementation, from the fields alone. */
|
|
110
|
+
export declare function verifyFactoryImplementation(request?: FactoryImplementationRequest): FactoryImplementationVerdict;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The verdict read at the factory itself: `implementation()`/`implementationCodeHash()` and the code at the
|
|
114
|
+
* implementation's address, then the same three conditions. The chain is injected - `read` is
|
|
115
|
+
* `eth_call({to, data}) -> hex` and `code` is `eth_getCode(address) -> hex` - so the module performs no call of
|
|
116
|
+
* its own. An unreadable pair or code is a named refusal, because an old factory without these getters reverts.
|
|
117
|
+
*/
|
|
118
|
+
export type FactoryImplementationOnChainVerdict =
|
|
119
|
+
| {
|
|
120
|
+
readonly ok: false;
|
|
121
|
+
readonly why: string;
|
|
122
|
+
readonly factory?: string;
|
|
123
|
+
readonly implementation?: string;
|
|
124
|
+
readonly codeHash?: string;
|
|
125
|
+
readonly got?: string;
|
|
126
|
+
readonly want?: string;
|
|
127
|
+
}
|
|
128
|
+
| { readonly ok: true; readonly codeHash: string; readonly implementation: string; readonly implementationCodeHash: string };
|
|
129
|
+
|
|
130
|
+
export interface FactoryImplementationOnChainRequest {
|
|
131
|
+
readonly factory?: string;
|
|
132
|
+
readonly read?: (request: { readonly to: string; readonly data: string }) => Promise<string>;
|
|
133
|
+
readonly code?: (address: string) => Promise<string>;
|
|
134
|
+
readonly policy?: readonly string[] | null;
|
|
135
|
+
readonly hashCode?: ((code: string) => string) | null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The pair read at the factory, with the same verdict the factory applies. */
|
|
139
|
+
export declare function verifyFactoryImplementationOnChain(
|
|
140
|
+
request?: FactoryImplementationOnChainRequest,
|
|
141
|
+
): Promise<FactoryImplementationOnChainVerdict>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The claim gas limit, MEASURED on a live chain with a margin: an honest claim costs 120 721 gas. The same
|
|
145
|
+
* number on both sides of the reverse swap.
|
|
146
|
+
*/
|
|
147
|
+
export declare const CLAIM_GAS_LIMIT: bigint;
|
|
148
|
+
|
|
149
|
+
/** The network's tip floor. Zero where the network needs no tip, which is policy and not a constant here. */
|
|
150
|
+
export declare const CLAIM_TIP_FLOOR_WEI: bigint;
|
|
151
|
+
|
|
152
|
+
/** Margin for base-fee growth between the ticket and the claim, in basis points, as a node setting. */
|
|
153
|
+
export declare const DEFAULT_GIFT_RESERVE_BPS: bigint;
|
|
154
|
+
|
|
155
|
+
/** The fee of a claim in EIP-1559 terms: `2 x baseFee + tip`, tip not below the floor. */
|
|
156
|
+
export interface ClaimMaxFeeRequest {
|
|
157
|
+
readonly baseFeeWei?: string | number | bigint | null;
|
|
158
|
+
readonly priorityFeeWei?: string | number | bigint | null;
|
|
159
|
+
readonly minTipWei?: string | number | bigint | null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** `maxFeePerGas`, or null when the base fee is unreadable or zero (no fee fields is not a zero fee). */
|
|
163
|
+
export declare function claimMaxFeePerGasWei(request?: ClaimMaxFeeRequest): bigint | null;
|
|
164
|
+
|
|
165
|
+
/** What the balance needs for the claim. */
|
|
166
|
+
export interface ClaimRequiredRequest {
|
|
167
|
+
readonly gasLimit?: string | number | bigint | null;
|
|
168
|
+
readonly maxFeePerGasWei?: string | number | bigint | null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** `gasLimit x maxFeePerGas`, or null when either factor is unmeasured. */
|
|
172
|
+
export declare function claimRequiredWei(request?: ClaimRequiredRequest): bigint | null;
|
|
173
|
+
|
|
174
|
+
/** The gift the node sends to a claimer who holds no native coin. */
|
|
175
|
+
export interface ClaimGasGiftRequest {
|
|
176
|
+
readonly baseFeeWei?: string | number | bigint | null;
|
|
177
|
+
readonly priorityFeeWei?: string | number | bigint | null;
|
|
178
|
+
readonly minTipWei?: string | number | bigint | null;
|
|
179
|
+
readonly gasLimit?: string | number | bigint | null;
|
|
180
|
+
readonly reserveBps?: string | number | bigint | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The claim cost the client requires, plus the reserve margin. The ONE computation of the gift size. */
|
|
184
|
+
export declare function claimGasGiftWei(request?: ClaimGasGiftRequest): bigint | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// THE EVM SIDE: factory calldata and the arithmetic of a claim.
|
|
2
|
+
//
|
|
3
|
+
// A node verifies a deployed factory against the implementation it was told about, and prices the gas gift for
|
|
4
|
+
// a claim. Both used to be reachable only through the page. Names only.
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
FACTORY_METHODS,
|
|
8
|
+
FACTORY_READS,
|
|
9
|
+
ORDER_CREATED_TOPIC,
|
|
10
|
+
QUOTE_FIELDS,
|
|
11
|
+
decodeAddress,
|
|
12
|
+
decodeImplementation,
|
|
13
|
+
decodeImplementationCodeHash,
|
|
14
|
+
encodeCreateOrderAndFund,
|
|
15
|
+
encodeFactoryRead,
|
|
16
|
+
encodePredict,
|
|
17
|
+
verifyFactoryImplementation,
|
|
18
|
+
verifyFactoryImplementationOnChain,
|
|
19
|
+
word,
|
|
20
|
+
} from "../../engine/evm/factory.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
CLAIM_GAS_LIMIT,
|
|
24
|
+
CLAIM_TIP_FLOOR_WEI,
|
|
25
|
+
DEFAULT_GIFT_RESERVE_BPS,
|
|
26
|
+
claimGasGiftWei,
|
|
27
|
+
claimMaxFeePerGasWei,
|
|
28
|
+
claimRequiredWei,
|
|
29
|
+
} from "../../engine/evm/claimGas.js";
|