@ninsei-labs/sdk 0.39.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 +866 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/engine/atomic/dleq2.js +330 -0
- package/engine/atomic/halfenc.js +205 -0
- package/engine/atomic/halves.js +119 -0
- package/engine/atomic/order-client.js +57 -0
- package/engine/atomic/order.js +132 -0
- package/engine/atomic/orderContext.js +70 -0
- package/engine/atomic/orderHalfCommit.js +29 -0
- package/engine/core/chainSource.js +306 -0
- package/engine/core/clock.js +40 -0
- package/engine/core/config.js +885 -0
- package/engine/core/finality.js +93 -0
- package/engine/core/format.js +168 -0
- package/engine/core/preSignGuards.js +495 -0
- package/engine/core/rfqSource.js +193 -0
- package/engine/core/sellWindow.js +43 -0
- package/engine/core/store.js +100 -0
- package/engine/core/swap.js +1153 -0
- package/engine/evm/amounts.js +131 -0
- package/engine/evm/auth.js +156 -0
- package/engine/evm/claimGas.js +87 -0
- package/engine/evm/depositor.js +127 -0
- package/engine/evm/dex.js +836 -0
- package/engine/evm/escrow.js +208 -0
- package/engine/evm/factory.js +149 -0
- package/engine/evm/fees.js +90 -0
- package/engine/evm/funding.js +309 -0
- package/engine/evm/gasReserve.js +88 -0
- package/engine/evm/index.js +123 -0
- package/engine/evm/injected.js +175 -0
- package/engine/evm/permit.js +125 -0
- package/engine/evm/prices.js +179 -0
- package/engine/evm/session.js +777 -0
- package/engine/mock/chain.js +98 -0
- package/engine/monero/address.js +265 -0
- package/engine/monero/halvesSweep.js +68 -0
- package/engine/monero/node.js +110 -0
- package/engine/monero/scan.js +325 -0
- package/engine/monero/swapKeys.js +69 -0
- package/engine/monero/unlock.js +46 -0
- package/engine/monero/wallet.js +168 -0
- package/engine/recovery/recoveryFile.js +363 -0
- package/engine/recovery/restore.js +176 -0
- package/engine/sdk/bridge.js +609 -0
- package/engine/ui/dom.js +90 -0
- package/engine/ui/walletModal.js +132 -0
- package/index.d.ts +1417 -0
- package/package.json +74 -0
- package/src/actions.mjs +72 -0
- package/src/adapters/evm-wallet.mjs +126 -0
- package/src/adapters/index.mjs +21 -0
- package/src/adapters/local-storage.mjs +32 -0
- package/src/adapters/monero-wallet.mjs +185 -0
- package/src/adapters/monero.mjs +51 -0
- package/src/adapters/node-test-evm-wallet.mjs +345 -0
- package/src/codes.mjs +188 -0
- package/src/config.mjs +277 -0
- package/src/engine.mjs +53 -0
- package/src/errors.mjs +24 -0
- package/src/http.mjs +72 -0
- package/src/index.mjs +256 -0
- package/src/legs/evm.mjs +118 -0
- package/src/legs/index.mjs +50 -0
- package/src/levelsWalkSpec.mjs +183 -0
- package/src/lock.mjs +140 -0
- package/src/order.mjs +212 -0
- package/src/preflight.mjs +137 -0
- package/src/primitives.mjs +47 -0
- package/src/quoteEip712.mjs +35 -0
- package/src/quoteEip712Spec.mjs +214 -0
- package/src/quotes.mjs +465 -0
- package/src/record.mjs +65 -0
- package/src/recovery.mjs +40 -0
- package/src/sim.mjs +37 -0
- package/src/swap-flow.mjs +492 -0
- package/src/swaps.mjs +935 -0
- package/src/sweep.mjs +222 -0
- package/src/wallet.mjs +164 -0
- package/src/xmr-arrival.mjs +289 -0
package/src/lock.mjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// LOCKING THE FUNDS: TERMS FIRST, THEN SENDING.
|
|
2
|
+
//
|
|
3
|
+
// The first half of the step is here - assembling and CHECKING the order terms BEFORE anything goes on-chain. The
|
|
4
|
+
// rules are not ours: the contract sets them, and the engine already checks them (`www/js/evm/escrow.js`,
|
|
5
|
+
// `orderTerms`). The core will not hold a copy of the rules: a copy would drift from the contract right where the
|
|
6
|
+
// error shows up only as a rejected transaction.
|
|
7
|
+
//
|
|
8
|
+
// WHAT IS HERE: both the assembly of the terms and the sending itself - it goes through the engine's EVM layer
|
|
9
|
+
// (`fundOrder`), and the EVM wallet (the `send`/`receipt` role) comes from outside. What reads the chain is also a
|
|
10
|
+
// seam: by default the engine's read, but the caller can substitute its own (as in actions.mjs).
|
|
11
|
+
import { SdkError } from "./errors.mjs";
|
|
12
|
+
import * as engine from "./engine.mjs";
|
|
13
|
+
// THE DEAL FLOW IS A PACKAGE MODULE NOW (#32, wave 3): the registry address is read through it, exactly as
|
|
14
|
+
// the actions read the order flow.
|
|
15
|
+
import * as flow from "./swap-flow.mjs";
|
|
16
|
+
|
|
17
|
+
// THE ORDER'S WORD-ADDRESSES ARE BROUGHT TO 0x HERE, AT THE BOUNDARY, NOT AT EVERY CALLER.
|
|
18
|
+
//
|
|
19
|
+
// The engine returns the points and commitments WITHOUT the 0x prefix (that is how www/js/atomic/halves.js stores
|
|
20
|
+
// them), while the calldata encoder (www/js/evm/factory.js, word) REQUIRES the prefix and refuses "not a number
|
|
21
|
+
// and not hex" on 64 digits without one. The page normalises them on its side (sdk/src/swap-flow.mjs, with0x)
|
|
22
|
+
// for exactly this reason. The core must do the same ITSELF: otherwise every caller would repeat the
|
|
23
|
+
// normalisation, and one forgetful one would break the signature with the encoder's raw error. The list is
|
|
24
|
+
// exactly the fields encoded as bytes32 that do NOT enter the order context string (otherwise the normalisation
|
|
25
|
+
// would drift from the proof, which is bound to the context): two half commitments and three ed25519 points.
|
|
26
|
+
const HEX_POLICY_FIELDS = ["commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "edViewPointLocker"];
|
|
27
|
+
const with0x = (v) => (typeof v === "string" && v && !/^0x/i.test(v) ? "0x" + v : v);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The order terms in canonical form: points and commitments - with the 0x prefix. One form for the check and for
|
|
31
|
+
* sending - otherwise one thing would be checked and another encoded.
|
|
32
|
+
*/
|
|
33
|
+
export function canonicalTerms(request) {
|
|
34
|
+
if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
|
|
35
|
+
const out = { ...request };
|
|
36
|
+
for (const key of HEX_POLICY_FIELDS) out[key] = with0x(out[key]);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The order terms for the contract. They are assembled by the engine, and its refusal (the deadlines the wrong
|
|
42
|
+
* way, readyBy in the past) is turned into a CODE: texts are the interface's business, the core speaks in codes.
|
|
43
|
+
*/
|
|
44
|
+
export function terms(request) {
|
|
45
|
+
if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
|
|
46
|
+
const need = ["locker", "claimer", "commitHalfLocker", "commitHalfClaimer", "edPointLocker", "edPointClaimer", "amount", "readyBy", "t1"];
|
|
47
|
+
const missing = need.filter((k) => request[k] === undefined || request[k] === null || request[k] === "");
|
|
48
|
+
if (missing.length) throw new SdkError("bad-input", { field: "terms", missing });
|
|
49
|
+
try {
|
|
50
|
+
return engine.escrow.orderTerms(canonicalTerms(request));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
// We will not parse ANOTHER's text: we name what the contract's rules check, and only if the deadlines are
|
|
53
|
+
// actually violated. Otherwise - a general refusal on the terms.
|
|
54
|
+
const seconds = Math.floor(Date.now() / 1000);
|
|
55
|
+
if (Number(request.readyBy) <= seconds) throw new SdkError("bad-input", { field: "readyBy", why: "not-in-future" });
|
|
56
|
+
if (Number(request.t1) <= Number(request.readyBy)) throw new SdkError("bad-input", { field: "t1", why: "not-after-readyBy" });
|
|
57
|
+
throw new SdkError("bad-input", { field: "terms" });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// SANITISING THE REASON. The engine's messages carry no keys, but sanitising costs one line and covers the case
|
|
62
|
+
// where the engine starts printing a value: 0x + 64 hex is a private key, and it never goes out.
|
|
63
|
+
const sanitizeWhy = (value) =>
|
|
64
|
+
String(value === undefined || value === null ? "" : value).replace(/0x[0-9a-fA-F]{64}/g, "0x<redacted>").slice(0, 200);
|
|
65
|
+
// A NETWORK REFUSAL IS CALLED A NETWORK ONE, rather than being fitted to a data error: transport and order
|
|
66
|
+
// validation are different things, and the caller needs an answer to "retry or fix".
|
|
67
|
+
const TRANSPORT_WHY = /fetch failed|network|ECONN|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket|timeout|HTTP \d|status \d|non-ok/i;
|
|
68
|
+
const transportish = (why) => TRANSPORT_WHY.test(why);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* SEND THE LOCK. The deadlines and the composition are checked BEFORE sending (see `terms`), the chain work is
|
|
72
|
+
* done by the engine (`fundOrder`), and the wallet arrives as an adapter: signing is possible only in it, and the
|
|
73
|
+
* core keeps no path into the chain of its own.
|
|
74
|
+
*
|
|
75
|
+
* TWO THINGS ARE DELIBERATELY NOT OURS: reading from the chain is taken from the engine, and the escrow address -
|
|
76
|
+
* FROM THE RECEIPT, not from a prediction. A prediction could diverge from what was actually created, and it
|
|
77
|
+
* would diverge silently.
|
|
78
|
+
*/
|
|
79
|
+
export async function send(request, wallet, deps = {}) {
|
|
80
|
+
if (!request || typeof request !== "object") throw new SdkError("bad-input", { field: "request" });
|
|
81
|
+
if (!wallet || typeof wallet.send !== "function" || typeof wallet.receipt !== "function") {
|
|
82
|
+
throw new SdkError("bad-input", { field: "wallet", missing: ["send", "receipt"] });
|
|
83
|
+
}
|
|
84
|
+
// THE TERMS ARE BROUGHT TO CANONICAL FORM BEFORE EVERYTHING, and go in one form both into the check and into
|
|
85
|
+
// sending.
|
|
86
|
+
// THE DEADLINES - HERE, BEFORE ANY SENDING, and on the form funding expects (`amountWei`, not `amount`: the
|
|
87
|
+
// order terms and the sending have different names for the same field, and they must not be confused).
|
|
88
|
+
const canonical = canonicalTerms(request);
|
|
89
|
+
terms({ ...canonical, amount: canonical.amountWei });
|
|
90
|
+
const evm = engine.evm;
|
|
91
|
+
// WHAT READS THE CHAIN IS ALSO A SEAM (as in actions.mjs): by default it is the engine's read, but the caller
|
|
92
|
+
// can substitute its own. Without this capability the lock step could not be checked beyond predicting the
|
|
93
|
+
// address - and then "the address from the receipt" would remain an argument on trust, not a verified property.
|
|
94
|
+
const reader = typeof deps.call === "function" ? deps.call : evm.readContract;
|
|
95
|
+
try {
|
|
96
|
+
return await evm.fundOrder({
|
|
97
|
+
...canonical,
|
|
98
|
+
call: reader,
|
|
99
|
+
send: (tx) => wallet.send(tx),
|
|
100
|
+
// NORMALISING THE RECEIPT ARGUMENT'S SHAPE - HERE, AT THE CORE'S BOUNDARY, NOT AT EVERY CALLER.
|
|
101
|
+
//
|
|
102
|
+
// www/js/evm/funding.js calls the receipt with an OBJECT { hash } (waitReceipt), while the adapter contract
|
|
103
|
+
// declares receipt(txHash: string). A strict adapter (sdk/src/adapters/evm-wallet.mjs) answers bad-input to
|
|
104
|
+
// an object, the refusal is SWALLOWED by the receipt wait, and the escrow address is silently replaced with
|
|
105
|
+
// the PREDICTED one - that is, the "address from the receipt" check stops working without saying so. We
|
|
106
|
+
// normalise the argument to a string here: funding.js is foreign code and must not be edited, and every
|
|
107
|
+
// adapter should not repeat the normalisation.
|
|
108
|
+
receipt: (arg) => wallet.receipt(typeof arg === "string" ? arg : (arg && typeof arg.hash === "string" ? arg.hash : arg)),
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// AN ALREADY-NAMED REASON PASSES AS IS: the adapter's and the reader's codes (wrong-chain, server-unavailable,
|
|
112
|
+
// contract-reverted, bad-input) ARE the answer, and it must not be overridden.
|
|
113
|
+
if (error && typeof error.code === "string") throw error;
|
|
114
|
+
// THE REST USED TO HIDE BEHIND server-unavailable { step: "fund-order" }, and the real reason (the first live
|
|
115
|
+
// run: "locker and claimer cannot coincide") never reached the caller. Now the reason is NAMED as a string
|
|
116
|
+
// (briefly, without keys), and the code is chosen by it: transport - server-unavailable, order/data
|
|
117
|
+
// validation - bad-input.
|
|
118
|
+
const why = sanitizeWhy((error && error.message) || error);
|
|
119
|
+
throw new SdkError(transportish(why) ? "server-unavailable" : "bad-input", { step: "fund-order", why });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The escrow factory address for a network: FROM THE NETWORK REGISTRY, by the same path as the rest of the core.
|
|
125
|
+
* The registry is read through the deal-flow module (sdk/src/swap-flow.mjs, escrowFactoryAddress) - the core
|
|
126
|
+
* keeps no SECOND table of addresses.
|
|
127
|
+
*
|
|
128
|
+
* PREVIOUSLY THE FUNCTION LOOKED IN THE WRONG MODULE (www/js/evm/escrow.js, which has no such function) and
|
|
129
|
+
* ALWAYS returned null - that is, the seam stayed silent about the factory's absence, and the caller took the
|
|
130
|
+
* address elsewhere. Now the absence of a factory for a network is a REFUSAL WITH A CODE naming the network, not
|
|
131
|
+
* null.
|
|
132
|
+
*/
|
|
133
|
+
export function factoryOf(chainSlug) {
|
|
134
|
+
let address = null;
|
|
135
|
+
try { address = flow.escrowFactoryAddress(chainSlug); } catch { address = null; }
|
|
136
|
+
if (typeof address !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
137
|
+
throw new SdkError("bad-input", { field: "factory", chain: typeof chainSlug === "string" ? chainSlug : null });
|
|
138
|
+
}
|
|
139
|
+
return address;
|
|
140
|
+
}
|
package/src/order.mjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// THE COUNTERPARTY'S SIDE AND THE JOINT ADDRESS.
|
|
2
|
+
//
|
|
3
|
+
// TWO CHECKS, BOTH MANDATORY BEFORE SIGNING - after it there is nothing to fix:
|
|
4
|
+
//
|
|
5
|
+
// 1) the DLEQ proof stands in the CONTEXT OF THIS order: the counterparty proved for our terms, not for others.
|
|
6
|
+
// The context is the same one the quote signature is bound to (see quotes.firm);
|
|
7
|
+
// 2) the proven ed25519 point is THE VERY ONE that will go into the contract and from which the address is
|
|
8
|
+
// assembled. Without this check the counterparty could prove one thing and substitute another: on our side
|
|
9
|
+
// everything would look sound, and the discrepancy would surface when claiming the XMR, with nothing left to
|
|
10
|
+
// fix.
|
|
11
|
+
//
|
|
12
|
+
// Both are done by the engine (order.js: the verifying code is the same for us and for the counterparty). The
|
|
13
|
+
// core has no cryptography of its own and should not: a copy would silently drift from the original.
|
|
14
|
+
import { SdkError } from "./errors.mjs";
|
|
15
|
+
import { primitives } from "./primitives.mjs";
|
|
16
|
+
import * as engine from "./engine.mjs";
|
|
17
|
+
|
|
18
|
+
const builder = () => engine.order.createOrderBuilder(primitives);
|
|
19
|
+
|
|
20
|
+
/** The order context: the string the proof and the quote signature are both bound to. */
|
|
21
|
+
export function contextOf(order) {
|
|
22
|
+
if (!order || typeof order !== "object") throw new SdkError("bad-input", { field: "order" });
|
|
23
|
+
try { return engine.orderContext.orderContextString(order); }
|
|
24
|
+
catch { throw new SdkError("bad-input", { field: "order" }); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check the counterparty's side in the order context. It passed - return the side itself; it did not - a refusal
|
|
29
|
+
* with a CODE (`quote-refused { step: "proof" }`). "Probably fine" does not exist here: the side is either proven
|
|
30
|
+
* or it is not.
|
|
31
|
+
*/
|
|
32
|
+
export function verifyCounterparty({ side, order, sealed = null, ownEncPriv = null }) {
|
|
33
|
+
if (!side || typeof side !== "object") throw new SdkError("bad-input", { field: "side" });
|
|
34
|
+
const context = contextOf(order);
|
|
35
|
+
let verdict = null;
|
|
36
|
+
try { verdict = builder().verifySide(side, context, sealed, ownEncPriv); }
|
|
37
|
+
catch { throw new SdkError("bad-input", { field: "side" }); }
|
|
38
|
+
if (!verdict || verdict.ok !== true) {
|
|
39
|
+
throw new SdkError("quote-refused", { step: "proof", why: verdict && verdict.reason ? "rejected" : "unknown" });
|
|
40
|
+
}
|
|
41
|
+
return side;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The shared Monero address: your own spend half and the other's POINT (the same for the view key). We take the
|
|
46
|
+
* same encoder the wallet uses: a second one would drift from the first where the error is visible only by
|
|
47
|
+
* sending XMR.
|
|
48
|
+
*/
|
|
49
|
+
export function jointAddress({ ownSpendHalf, ownViewHalf, otherSpendPoint, otherViewPoint, network }) {
|
|
50
|
+
const need = { ownSpendHalf, ownViewHalf, otherSpendPoint, otherViewPoint };
|
|
51
|
+
const missing = Object.keys(need).filter((k) => need[k] === undefined || need[k] === null || need[k] === "");
|
|
52
|
+
if (missing.length) throw new SdkError("bad-input", { field: "jointAddress", missing });
|
|
53
|
+
try {
|
|
54
|
+
return builder().jointAddress({ ownSpendHalf, ownViewHalf, otherSpendPoint, otherViewPoint, network });
|
|
55
|
+
} catch { throw new SdkError("bad-input", { field: "jointAddress" }); }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- THE ORDER STEP: FROM OUR SIDE TO THE JOINT ADDRESS -----------------------------------------
|
|
59
|
+
//
|
|
60
|
+
// WHY THIS IS A SINGLE CALL AND NOT THREE SEPARATE ONES. The order here IS the rule: your own side -> the
|
|
61
|
+
// counterparty's side (accepted and verified in THIS SAME context) -> the joint address. The address is assembled
|
|
62
|
+
// from YOUR half and the OTHER's point, so before the counterparty's side appears it simply does not exist - and
|
|
63
|
+
// refusing here is cheaper than discovering the discrepancy when claiming the XMR, with nothing left to fix.
|
|
64
|
+
//
|
|
65
|
+
// THERE IS NOT A SINGLE FORMULA OF OUR OWN HERE. The halves, the points, the DLEQ proof, the encryption key and
|
|
66
|
+
// the address are computed by the engine's builder (www/js/atomic/order.js) on the core's primitives. The core
|
|
67
|
+
// names the order and translates the result into the record's fields - exactly the names the engine reads
|
|
68
|
+
// (core/swap.js, halvesWalletOf).
|
|
69
|
+
|
|
70
|
+
export const norm = (h) => "0x" + String(h).replace(/^0x/i, "").toLowerCase();
|
|
71
|
+
// A HALF AT THE BOUNDARY IS A HEX STRING: a BigInt survives neither storage nor the recovery file
|
|
72
|
+
// (www/js/recovery/recoveryFile.js, bigintSafe). One form at every boundary, otherwise JSON silently loses the key.
|
|
73
|
+
export const halfHex = (v) => "0x" + BigInt(v).toString(16).padStart(64, "0");
|
|
74
|
+
const isPoint = (h) => /^0x[0-9a-f]{64}$/.test(String(h));
|
|
75
|
+
// THE PROVEN POINT AND THE POINT IN THE ORDER MUST BE WRITTEN IDENTICALLY. The engine compares them AS STRINGS
|
|
76
|
+
// (order.js, verifySide), so the record's form is part of the check, not cosmetics: without bringing both to one
|
|
77
|
+
// form, a correct proof would look like a discrepancy. The page does the same
|
|
78
|
+
// (sdk/src/swap-flow.mjs: it normalises both proof.XB and the point). One normalisation for both sides.
|
|
79
|
+
const sameFormAsPoint = (proof) => (proof && typeof proof === "object" && proof.XB ? { ...proof, XB: norm(proof.XB) } : proof);
|
|
80
|
+
// A POINT AT THE BOUNDARY IS IN CANONICAL FORM (0x + lowercase). The engine's points arrive WITHOUT the 0x prefix
|
|
81
|
+
// (that is how www/js/atomic/halves.js stores them), while the proof check compares points AS STRINGS. The
|
|
82
|
+
// normalisation must be SYMMETRIC: the proven point of the proof is already normalised by sameFormAsPoint, so the
|
|
83
|
+
// counterparty's declared point is normalised the same way - otherwise the same number written once with 0x and
|
|
84
|
+
// once without would give a false quote-refused { step: "proof" } on a matching proof. The page does the same
|
|
85
|
+
// (sdk/src/swap-flow.mjs, with0x). Not a string - then there is no point: we leave it as is, this is a refusal
|
|
86
|
+
// "no point", not "point in another form".
|
|
87
|
+
const pointForm = (v) => (typeof v === "string" && v ? norm(v) : v);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Our side of the order. It is built by the engine's builder: two halves, their points, the DLEQ PROOF (it is
|
|
91
|
+
* bound to the order terms and so is not computed without the context) and the side's encryption key.
|
|
92
|
+
* The long proof computation lives here - one side, one computation.
|
|
93
|
+
*/
|
|
94
|
+
export function buildOwnSide(order, onBit) {
|
|
95
|
+
const context = contextOf(order);
|
|
96
|
+
const side = builder().newSide(context, false, onBit);
|
|
97
|
+
return { context, side, spendPoint: norm(side.pub.ed), viewPoint: norm(side.viewPub), encPub: norm(side.enc.pub) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The counterparty's side: accept and verify. The check is ONE for both sides and is done by the engine
|
|
102
|
+
* (order.js, verifySide): the proof stands in the CONTEXT OF THIS order, the proven point matches the one that
|
|
103
|
+
* will go into the contract, and if a sealed package arrived it is opened with OUR encryption key and checked
|
|
104
|
+
* against its point. The sandbox stand-in is built here too and marked: it must not be passed off as a real
|
|
105
|
+
* provider.
|
|
106
|
+
*/
|
|
107
|
+
export function acceptCounterparty({ context, side = null, counterparty = null, standIn = false }) {
|
|
108
|
+
let accepted = null;
|
|
109
|
+
if (counterparty && typeof counterparty === "object") {
|
|
110
|
+
const pub = counterparty.pub && typeof counterparty.pub === "object" ? counterparty.pub : {};
|
|
111
|
+
// THE ORDER-QUOTE FIELD NAMES ARE THE ONES THE CORE ITSELF RETURNS (a defect found). The counterparty's side
|
|
112
|
+
// comes from `quotes.firm`, and there the point is called `edPointClaimer`, the view half - `viewHalfClaimer`
|
|
113
|
+
// (rfq/orderQuote.mjs). While only `edPub`/`viewPub` were accepted, the facade's own side was NOT accepted:
|
|
114
|
+
// start answered bad-input { field: "counterparty", missing: ["edPub"] } to its own answer. The engine knew
|
|
115
|
+
// this mapping (sdk/src/swap-flow.mjs: edPub: quote.edPointClaimer). We accept BOTH names.
|
|
116
|
+
accepted = {
|
|
117
|
+
proof: sameFormAsPoint(counterparty.proof),
|
|
118
|
+
edPub: pointForm(counterparty.edPub || counterparty.edPoint || counterparty.edPointClaimer || pub.ed || null),
|
|
119
|
+
viewPoint: pointForm(counterparty.viewPoint || counterparty.viewPub || null),
|
|
120
|
+
viewHalf: counterparty.viewHalf || counterparty.viewHalfClaimer || null,
|
|
121
|
+
// THE COUNTERPARTY'S SPEND HALF: HIS commitment (commitHalfClaimer) is computed from it where it did not
|
|
122
|
+
// arrive ready-made. Without the half the commitment cannot be obtained - and that is named null, not invented.
|
|
123
|
+
half: counterparty.half !== undefined ? counterparty.half : null,
|
|
124
|
+
commitHalfClaimer: counterparty.commitHalfClaimer || null,
|
|
125
|
+
sealed: counterparty.sealed || null,
|
|
126
|
+
providerId: counterparty.providerId || null,
|
|
127
|
+
standIn: false,
|
|
128
|
+
};
|
|
129
|
+
} else if (standIn === true) {
|
|
130
|
+
// THE STAND-IN IS COMPUTED IN THE SAME CONTEXT AS THE ORDER: the string gets nothing appended. The same path
|
|
131
|
+
// as the page (sdk/src/swap-flow.mjs: worker.buildSide) and the flow check - otherwise what would be checked is
|
|
132
|
+
// not what the page runs on.
|
|
133
|
+
const mock = builder().newSide(context, true);
|
|
134
|
+
accepted = { proof: sameFormAsPoint(mock.proof), edPub: norm(mock.pub.ed), viewPoint: norm(mock.viewPub),
|
|
135
|
+
viewHalf: mock.viewHalf, half: mock.half, commitHalfClaimer: null, sealed: null, providerId: null, standIn: true, mock };
|
|
136
|
+
} else {
|
|
137
|
+
throw new SdkError("bad-input", { field: "counterparty" });
|
|
138
|
+
}
|
|
139
|
+
const missing = [];
|
|
140
|
+
if (!accepted.proof) missing.push("proof");
|
|
141
|
+
if (!accepted.edPub) missing.push("edPub");
|
|
142
|
+
if (missing.length) throw new SdkError("bad-input", { field: "counterparty", missing });
|
|
143
|
+
// OPENING THE SEALED PACKAGE - BY THE SAME ENGINE CALL, and the key is OURS (the package is addressed to us).
|
|
144
|
+
// The envelope's composition is locked separately (www/js/atomic/halfenc.js, HALFENC_LOCK_REASON): in the live
|
|
145
|
+
// flow the half arrives from the chain via the commit/reveal mechanism, so a locked package honestly answers
|
|
146
|
+
// with a refusal rather than passing itself off as a working key handover.
|
|
147
|
+
const ownEncPriv = accepted.sealed && side && side.enc ? side.enc.priv : null;
|
|
148
|
+
let verdict = null;
|
|
149
|
+
try { verdict = builder().verifySide({ proof: accepted.proof, pub: { ed: accepted.edPub } }, context, accepted.sealed, ownEncPriv); }
|
|
150
|
+
catch { throw new SdkError("bad-input", { field: "counterparty" }); }
|
|
151
|
+
if (!verdict || verdict.ok !== true) {
|
|
152
|
+
throw new SdkError("quote-refused", { step: "proof", why: verdict && verdict.reason ? "rejected" : "unknown" });
|
|
153
|
+
}
|
|
154
|
+
return accepted;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* THE ORDER STEP AS A WHOLE: your own side (or one already built by the record), the counterparty's side and the
|
|
159
|
+
* joint address. It hands out the fields the record and the recovery file are assembled from; NOT A SINGLE empty
|
|
160
|
+
* value: an address is not assembled without the other's point, and that is a refusal, not an empty string.
|
|
161
|
+
*/
|
|
162
|
+
export function orderStep({ order, side = null, counterparty = null, standIn = false, network = null }) {
|
|
163
|
+
const context = contextOf(order);
|
|
164
|
+
const own = side
|
|
165
|
+
? { context, side, spendPoint: norm(side.pub && side.pub.ed), viewPoint: norm(side.viewPub),
|
|
166
|
+
encPub: norm(side.enc && side.enc.pub ? side.enc.pub : null) }
|
|
167
|
+
: buildOwnSide(order);
|
|
168
|
+
const lack = [];
|
|
169
|
+
if (own.side.half === undefined || own.side.half === null) lack.push("half");
|
|
170
|
+
if (own.side.viewHalf === undefined || own.side.viewHalf === null) lack.push("viewHalf");
|
|
171
|
+
if (!isPoint(own.spendPoint)) lack.push("spendPoint");
|
|
172
|
+
if (!isPoint(own.viewPoint)) lack.push("viewPoint");
|
|
173
|
+
if (lack.length) throw new SdkError("bad-input", { field: "side", missing: lack });
|
|
174
|
+
|
|
175
|
+
const other = acceptCounterparty({ context: own.context, side: own.side, counterparty, standIn });
|
|
176
|
+
const otherViewPoint = other.viewPoint || (other.viewHalf ? norm(builder().pointOf(other.viewHalf)) : null);
|
|
177
|
+
if (!otherViewPoint) throw new SdkError("bad-input", { field: "counterparty", missing: ["viewPoint"] });
|
|
178
|
+
|
|
179
|
+
let joint = null;
|
|
180
|
+
try {
|
|
181
|
+
joint = builder().jointAddress({
|
|
182
|
+
ownSpendHalf: own.side.half, ownViewHalf: own.side.viewHalf,
|
|
183
|
+
otherSpendPoint: other.edPub, otherViewPoint, network,
|
|
184
|
+
});
|
|
185
|
+
} catch { throw new SdkError("bad-input", { field: "network" }); }
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
context: own.context,
|
|
189
|
+
own: { spendPoint: own.spendPoint, viewPoint: own.viewPoint, encPub: own.encPub, proof: own.side.proof },
|
|
190
|
+
side: own.side,
|
|
191
|
+
counterparty: other,
|
|
192
|
+
// READY-MADE RECORD AND FILE FIELDS: the names the engine reads (core/swap.js, halvesWalletOf) and the ones
|
|
193
|
+
// recovery file version 3 requires. A file without the other's view half will not be assembled - and that is
|
|
194
|
+
// named by the otherViewHalf field with the value null, not by an invented half.
|
|
195
|
+
wallet: {
|
|
196
|
+
spendHalf: halfHex(own.side.half), viewHalf: halfHex(own.side.viewHalf),
|
|
197
|
+
otherSpendPoint: other.edPub, otherViewPoint: norm(otherViewPoint),
|
|
198
|
+
otherViewHalf: other.viewHalf ? halfHex(other.viewHalf) : null,
|
|
199
|
+
address: joint.address, spendPub: norm(joint.spendPub), viewPub: norm(joint.viewPub),
|
|
200
|
+
otherCommitHalf: other.commitHalfClaimer || null,
|
|
201
|
+
// THE COMMITMENTS FOR THE HALVES - FROM THESE SAME SIDES, by the same call as the contract
|
|
202
|
+
// (keccak256(abi.encodePacked(bytes32 half))). One built side -> one commitment: there is no second half
|
|
203
|
+
// computation here. The other's half may not arrive (the order brings it) - then the counterparty's
|
|
204
|
+
// commitment is taken ready-made or stays null, not invented.
|
|
205
|
+
commitHalfLocker: norm(builder().halfCommitment(own.side.half)),
|
|
206
|
+
commitHalfClaimer: other.half !== undefined && other.half !== null
|
|
207
|
+
? norm(builder().halfCommitment(other.half))
|
|
208
|
+
: (other.commitHalfClaimer || null),
|
|
209
|
+
network: network || null,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// PRE-SIGNATURE CHECKS: the same engine guards, but the answer is codes, not texts.
|
|
2
|
+
//
|
|
3
|
+
// WHAT MATTERS HERE and why this is not a "wrapper": the unverified must be named as unverified. The engine guards
|
|
4
|
+
// already distinguish "good" from "not measured" (`checked: false`, the `*-unchecked` kinds), so the SDK does not
|
|
5
|
+
// discard such checks and does not pass them off as green: it calls the guard with what it has and passes the
|
|
6
|
+
// verdict on.
|
|
7
|
+
import * as engine from "./engine.mjs";
|
|
8
|
+
import { checkCodeFor } from "./codes.mjs";
|
|
9
|
+
|
|
10
|
+
// The engine's refusal reason is a phrase for a person; the SDK does not hand it out: the interface translates the code itself.
|
|
11
|
+
const toCheck = (verdict) => {
|
|
12
|
+
const v = verdict && typeof verdict === "object" ? verdict : {};
|
|
13
|
+
const { reason, kind, ok, checked, blocks, ...params } = v;
|
|
14
|
+
return {
|
|
15
|
+
code: checkCodeFor(kind),
|
|
16
|
+
ok: ok === true,
|
|
17
|
+
checked: checked !== false,
|
|
18
|
+
blocks: blocks === true,
|
|
19
|
+
kind: typeof kind === "string" ? kind : "unknown",
|
|
20
|
+
params,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// IS THERE ANYTHING TO COMPARE AGAINST. An empty value means "there was no check", and a check with nothing to
|
|
25
|
+
// apply to is not run at all: otherwise it locks the signature where there is NOTHING to check against by
|
|
26
|
+
// construction.
|
|
27
|
+
const hasComparison = (...values) => values.some((v) => v !== undefined && v !== null && v !== "");
|
|
28
|
+
|
|
29
|
+
const call = (fn, input) => {
|
|
30
|
+
try {
|
|
31
|
+
return fn(input);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// A guard that could not compute is "not checked", not "passed".
|
|
34
|
+
return { ok: false, checked: false, blocks: false, kind: "unchecked", error: String(error && error.message ? error.message : error) };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function createPreflight({ config, wallet, limits = {} }) {
|
|
39
|
+
return async function preflight(offer = {}, options = {}) {
|
|
40
|
+
const guards = engine.config.SIGNING_GUARDS || {};
|
|
41
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
42
|
+
const quote = offer && typeof offer === "object" ? offer : {};
|
|
43
|
+
const tokenIsNative = Boolean(options.tokenIsNative);
|
|
44
|
+
const chain = config.evmNetwork;
|
|
45
|
+
|
|
46
|
+
const checks = [
|
|
47
|
+
// 1. THE QUOTE IS ALIVE. The deadline is taken from the quote: an expired one must not be signed, even if its numbers are correct.
|
|
48
|
+
call(engine.guards.quoteFreshnessVerdict, {
|
|
49
|
+
at: Number.isFinite(quote.at) ? quote.at : null,
|
|
50
|
+
ttlMs: Number.isFinite(quote.ttlMs) ? quote.ttlMs : null,
|
|
51
|
+
expiresAt: Number.isFinite(quote.expiresAt) ? quote.expiresAt : null,
|
|
52
|
+
nowMs,
|
|
53
|
+
}),
|
|
54
|
+
// 2. DEADLINES FROM THE CHAIN: the mark window and the claim window must not overlap.
|
|
55
|
+
call(engine.guards.deadlineVerdict, {
|
|
56
|
+
nowSec: Math.floor(nowMs / 1000),
|
|
57
|
+
readyBy: Number.isFinite(quote.readyBy) ? quote.readyBy : null,
|
|
58
|
+
t1: Number.isFinite(quote.t1) ? quote.t1 : null,
|
|
59
|
+
minReadyLeadSec: guards.minReadyLeadSec ?? null,
|
|
60
|
+
minClaimWindowSec: guards.minClaimWindowSec ?? null,
|
|
61
|
+
}),
|
|
62
|
+
// 3. SIZE. The grid is declared by the provider node, so the sizes come with the quote, not from a file.
|
|
63
|
+
call(engine.guards.rangeVerdict, {
|
|
64
|
+
size: Number.isFinite(quote.amount) ? quote.amount : null,
|
|
65
|
+
unit: quote.token || null,
|
|
66
|
+
min: Number.isFinite(quote.min) ? quote.min : (limits.minAmount ?? null),
|
|
67
|
+
max: Number.isFinite(quote.max) ? quote.max : (limits.maxAmount ?? null),
|
|
68
|
+
step: Number.isFinite(quote.step) ? quote.step : (limits.stepAmount ?? null),
|
|
69
|
+
quoteUnit: quote.token || null,
|
|
70
|
+
}),
|
|
71
|
+
// 4. MONEY: is there enough to pay and does anything remain for gas.
|
|
72
|
+
call(engine.guards.payBalanceVerdict, {
|
|
73
|
+
payBalanceWei: options.payBalanceWei ?? null,
|
|
74
|
+
payAmountWei: options.payAmountWei ?? null,
|
|
75
|
+
symbol: tokenIsNative ? chain.nativeSymbol : (quote.token || null),
|
|
76
|
+
decimals: Number.isFinite(options.decimals) ? options.decimals : 18,
|
|
77
|
+
}),
|
|
78
|
+
call(engine.guards.gasReserveVerdict, {
|
|
79
|
+
nativeBalanceWei: options.nativeBalanceWei ?? null,
|
|
80
|
+
requiredNativeWei: options.requiredNativeWei ?? null,
|
|
81
|
+
gasReserveWei: options.gasReserveWei ?? null,
|
|
82
|
+
symbol: chain.nativeSymbol,
|
|
83
|
+
decimals: 18,
|
|
84
|
+
}),
|
|
85
|
+
// 5. TOKEN ALLOWANCE. Without the person's consent no allowance is granted - that is a separate code.
|
|
86
|
+
call(engine.guards.allowanceVerdict, {
|
|
87
|
+
tokenIsNative,
|
|
88
|
+
allowanceWei: options.allowanceWei ?? null,
|
|
89
|
+
amountWei: options.payAmountWei ?? null,
|
|
90
|
+
consent: options.allowanceConsent === true,
|
|
91
|
+
symbol: quote.token || null,
|
|
92
|
+
decimals: Number.isFinite(options.decimals) ? options.decimals : 18,
|
|
93
|
+
}),
|
|
94
|
+
// 6. THE WALLET NETWORK against the settlement network.
|
|
95
|
+
call(engine.guards.networkGateVerdict, {
|
|
96
|
+
walletChainId: wallet.status().chainId,
|
|
97
|
+
selectedChainId: chain.chainId,
|
|
98
|
+
settlementChainId: chain.chainId,
|
|
99
|
+
selectedChainName: chain.name,
|
|
100
|
+
}),
|
|
101
|
+
// 7. THE COUNTERPARTY and 8. THE ESCROW STATE, 9. THE POINTS, 10. THE ORDER-BOOK SLOTS. These are chain
|
|
102
|
+
// reads: while they are absent, the guards must say "not checked" rather than stay silent (stages 2 and 3).
|
|
103
|
+
call(engine.guards.counterpartyVerdict, {
|
|
104
|
+
standIn: options.standIn === true,
|
|
105
|
+
liveChain: options.liveChain === true,
|
|
106
|
+
network: config.evmNetwork.id,
|
|
107
|
+
chainId: chain.chainId,
|
|
108
|
+
}),
|
|
109
|
+
call(engine.guards.escrowStateVerdict, { status: options.escrowStatus ?? null, error: null, address: options.escrowAddress ?? null }),
|
|
110
|
+
// THE POINTS AND SLOTS ARE TURNED ON ONLY WHEN THERE IS SOMETHING TO COMPARE AGAINST. Both compare an order
|
|
111
|
+
// ALREADY LYING ON THE CHAIN, while at the swap-start step the order does not exist YET: it is created by
|
|
112
|
+
// the very signature the guards protect, so there is nothing to compare against BY CONSTRUCTION. While both
|
|
113
|
+
// stood in the list unconditionally, on empty data each answered "not checked" (blocks: true), and the swap
|
|
114
|
+
// start ran into its own guard without ever reaching the signature. The check APPEARS when its data is
|
|
115
|
+
// brought (the core does this on mark/claim/refund - assertOrderSlots), and then incompleteness still locks:
|
|
116
|
+
// this is removing a check with nothing to apply to, not weakening one that compares.
|
|
117
|
+
...(hasComparison(options.point, options.commit)
|
|
118
|
+
? [call(engine.guards.escrowPointsVerdict, {
|
|
119
|
+
role: options.pointsRole ?? null,
|
|
120
|
+
what: options.pointsWhat ?? null,
|
|
121
|
+
point: options.point ?? null,
|
|
122
|
+
onchainPoint: options.onchainPoint ?? null,
|
|
123
|
+
commit: options.commit ?? null,
|
|
124
|
+
onchainCommit: options.onchainCommit ?? null,
|
|
125
|
+
readError: options.pointsReadError ?? null,
|
|
126
|
+
})]
|
|
127
|
+
: []),
|
|
128
|
+
...(hasComparison(options.slots)
|
|
129
|
+
? [call(engine.guards.liveOrderSlotsVerdict, { slots: options.slots ?? null, expect: options.slotsExpect ?? null, readError: options.slotsReadError ?? null })]
|
|
130
|
+
: []),
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const mapped = checks.map(toCheck);
|
|
134
|
+
const blockedBy = [...new Set(mapped.filter((c) => c.blocks).map((c) => c.code))];
|
|
135
|
+
return { ok: blockedBy.length === 0, blockedBy, checks: mapped };
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// ATOMIC SWAP PRIMITIVES - FROM THE SAME npm PACKAGES AS THE PAGE BUNDLE.
|
|
2
|
+
//
|
|
3
|
+
// The bundle entry point (tools/atomic-browser-entry.mjs) hands the page exactly five names: ed25519, secp256k1,
|
|
4
|
+
// keccak256, sha3_512, randomBytes. The order-side build modules (www/js/atomic/order.js) do NOT import them
|
|
5
|
+
// themselves - they arrive as parameters. That is what lets the SDK expose the same primitives from the same
|
|
6
|
+
// packages instead of dragging a hashed build artefact into the core: a package never has a "version with a
|
|
7
|
+
// different cache".
|
|
8
|
+
//
|
|
9
|
+
// WHY EXACTLY THESE PACKAGES, AND NOT JUST ANY. keccak256 and sha3_512 are DIFFERENT functions, and they must
|
|
10
|
+
// not be confused: the commitment for a half is computed with keccak256 (exactly as Solidity does it), while the
|
|
11
|
+
// DLEQ proof uses SHA3-512 with wide reduction. Swapping one for the other looks like a typo and costs a whole
|
|
12
|
+
// swap, so the difference is verified by a run on known values, not by eye.
|
|
13
|
+
|
|
14
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
15
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
16
|
+
// keccak_256 is taken from here, and NOT from the separate keccak256 package: that one accepts only a Buffer of
|
|
17
|
+
// ITS OWN instance and refuses bytes from another module with "invalid type" (verified by a run). The popular
|
|
18
|
+
// package of the family is already present for the curves and SHA3-512, and keccak_256 from it gives the same
|
|
19
|
+
// thing - this is confirmed by the known value of the empty string in the check, which also shows it is not
|
|
20
|
+
// sha3_256.
|
|
21
|
+
import { keccak_256, sha3_512 } from "@noble/hashes/sha3.js";
|
|
22
|
+
// The core speaks in CODES, not in phrases: a message here would become UI text by accident (check-sdk
|
|
23
|
+
// forbids user-facing strings in sdk/src).
|
|
24
|
+
import { fail } from "./errors.mjs";
|
|
25
|
+
export { ed25519, secp256k1, sha3_512 };
|
|
26
|
+
|
|
27
|
+
// The engine passes a Uint8Array, a Buffer and a string alike. We normalise on input so the caller need not think about it.
|
|
28
|
+
export const keccak256 = (data) => {
|
|
29
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
30
|
+
return keccak_256(bytes);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Bytes: the same representation as keccak256, so the caller need not tell the source apart. */
|
|
34
|
+
export const keccak256Bytes = (data) => keccak256(data);
|
|
35
|
+
|
|
36
|
+
// Random bytes. We take them from the environment (both the browser and Node provide crypto.getRandomValues),
|
|
37
|
+
// not from a third-party library: the source of randomness is the last thing you want to shuffle around.
|
|
38
|
+
export const randomBytes = (length) => {
|
|
39
|
+
const out = new Uint8Array(length);
|
|
40
|
+
const source = globalThis.crypto;
|
|
41
|
+
if (!source || typeof source.getRandomValues !== "function") fail("bad-input", { field: "random-bytes", why: "no-source" });
|
|
42
|
+
source.getRandomValues(out);
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** The five primitives as one set - exactly the shape createOrderBuilder expects. */
|
|
47
|
+
export const primitives = { ed25519, secp256k1, keccak256, sha3_512, randomBytes };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// EIP-712 ORDER-QUOTE SIGNATURE - THE SDK SIDE (VERIFICATION ONLY).
|
|
2
|
+
//
|
|
3
|
+
// The SDK verifies, it does not sign: the maker's wallet key never reaches the customer's machine. The spec
|
|
4
|
+
// (types, domain, digest, recovery) lives in quoteEip712Spec.mjs, byte-identical to the node's copy and pinned
|
|
5
|
+
// by tools/check-quote-eip712.mjs. Here it is only bound to the SDK's own crypto primitives.
|
|
6
|
+
//
|
|
7
|
+
// The rule the old flag carried is kept: a quote that cannot be verified is REFUSED. A missing field, a
|
|
8
|
+
// missing signature or an unusable domain is not "probably fine" - the caller names it with a code.
|
|
9
|
+
import { keccak256, secp256k1 } from "./primitives.mjs";
|
|
10
|
+
import {
|
|
11
|
+
recoverQuoteKey as recover,
|
|
12
|
+
quoteDigest as digest,
|
|
13
|
+
quoteMissingFields,
|
|
14
|
+
ORDER_QUOTE_FIELDS as FIELDS,
|
|
15
|
+
ORDER_QUOTE_TYPE as TYPE,
|
|
16
|
+
EIP712_DOMAIN_TYPE,
|
|
17
|
+
ORDER_QUOTE_DOMAIN_NAME,
|
|
18
|
+
ORDER_QUOTE_DOMAIN_VERSION,
|
|
19
|
+
ORDER_QUOTE_VERSION,
|
|
20
|
+
} from "./quoteEip712Spec.mjs";
|
|
21
|
+
|
|
22
|
+
const crypto = { keccak256, secp256k1 };
|
|
23
|
+
|
|
24
|
+
export const ORDER_QUOTE_FIELDS = FIELDS;
|
|
25
|
+
export const ORDER_QUOTE_TYPE = TYPE;
|
|
26
|
+
export { EIP712_DOMAIN_TYPE, ORDER_QUOTE_DOMAIN_NAME, ORDER_QUOTE_DOMAIN_VERSION, ORDER_QUOTE_VERSION };
|
|
27
|
+
|
|
28
|
+
/** Names of the signed-set fields the quote does not carry. Not empty - the quote is not verifiable. */
|
|
29
|
+
export const orderQuoteMissingFields = (quote) => quoteMissingFields(quote);
|
|
30
|
+
|
|
31
|
+
/** The digest the provider signed, from the quote's own declared fields. */
|
|
32
|
+
export const orderQuoteDigest = (quote) => digest({ crypto, quote });
|
|
33
|
+
|
|
34
|
+
/** The address that signed the quote, or null when it cannot be recovered. */
|
|
35
|
+
export const recoverOrderQuoteKey = (quote) => recover({ crypto, quote });
|