@ninsei-labs/sdk 0.40.0 → 0.41.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 +11 -0
- package/engine/core/config.js +1 -1
- package/engine/evm/factory.js +0 -12
- package/engine/evm/funding.js +6 -22
- package/engine/evm/index.js +3 -7
- package/engine/evm/permit.js +3 -3
- package/engine/evm/session.js +3 -5
- package/engine/sdk/bridge.js +1 -1
- package/package.json +1 -1
- package/src/quoteEip712Spec.mjs +1 -1
- package/src/swap-flow.mjs +0 -7
- package/engine/evm/depositor.js +0 -123
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 0.41.0 — 2026-10-11
|
|
2
|
+
|
|
3
|
+
THE DEPOSITOR PATH IS GONE. The factory no longer offers `createOrderAndFundByDepositor`, so the package must
|
|
4
|
+
stop building a call to a selector that does not exist in the deployed factory.
|
|
5
|
+
|
|
6
|
+
- `src/swap-flow.mjs` no longer carries or relays `depositorSignature`: the funding path is the one where the
|
|
7
|
+
payer and the refund recipient are always the same address.
|
|
8
|
+
- The engine mirror loses `evm/depositor.js` (47 modules instead of 48) and the EIP-712 spec comment now names
|
|
9
|
+
the order-quote form.
|
|
10
|
+
- A consumer that meant to fund someone else's quote funds through `createOrderAndFund`.
|
|
11
|
+
|
|
1
12
|
## 0.40.0 — 2026-10-11
|
|
2
13
|
|
|
3
14
|
THE ASYNCHRONOUS ROUTE PROVIDERS REACH `latest`. This release moves the tag from 0.39.3; the demo pins this exact
|
package/engine/core/config.js
CHANGED
|
@@ -46,7 +46,7 @@ export const ENDPOINTS = Object.freeze({
|
|
|
46
46
|
// with the page's actual origin, so a hard-coded host breaks the session on any other host.
|
|
47
47
|
const PAGE_ORIGIN = typeof location !== "undefined" ? location.origin : "";
|
|
48
48
|
|
|
49
|
-
// THE EIP-712 SIGNING DOMAIN (name/version) for
|
|
49
|
+
// THE EIP-712 SIGNING DOMAIN (name/version) for a signed order quote. It is NOT a brand literal in the signer: it
|
|
50
50
|
// belongs to the registry contract (it returns DOMAIN_SEPARATOR()), so it is taken from the config here. The defaults
|
|
51
51
|
// reproduce today's result byte for byte.
|
|
52
52
|
export const SIGNING_DOMAIN = Object.freeze({ name: "NinseiEscrowFactory", version: "1" });
|
package/engine/evm/factory.js
CHANGED
|
@@ -19,9 +19,6 @@ export const FACTORY_METHODS = {
|
|
|
19
19
|
predict: "0x0824d205",
|
|
20
20
|
// create the escrow AND deposit funds in ONE transaction (a value transfer with the call).
|
|
21
21
|
createOrderAndFund: "0x4703a9d3",
|
|
22
|
-
// create the escrow and deposit when the DEPOSITOR is recovered FROM THE SIGNATURE over the quote digest:
|
|
23
|
-
// anyone may send the transaction, and `locker` is set to the signer.
|
|
24
|
-
createOrderAndFundByDepositor: "0x2acd187b",
|
|
25
22
|
};
|
|
26
23
|
|
|
27
24
|
// The OrderCreated event topic. It grew the registry address, the provider and the signing key:
|
|
@@ -129,15 +126,6 @@ export function encodeCreateOrderAndFund(quote) {
|
|
|
129
126
|
return FACTORY_METHODS.createOrderAndFund + quoteCallArgs(quote);
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
// CREATE AND DEPOSIT when the depositor is recovered FROM ITS SIGNATURE over the quote digest. Anyone may send
|
|
133
|
-
// the transaction, so there are TWO signers: quote.signature (the provider, checked by the escrow) and
|
|
134
|
-
// depositorSignature (the depositor, checked by the factory, which requires the recovered address to equal
|
|
135
|
-
// quote.locker). Both over ONE digest.
|
|
136
|
-
export function encodeCreateOrderAndFundByDepositor(quote, depositorSignature) {
|
|
137
|
-
return FACTORY_METHODS.createOrderAndFundByDepositor + quoteWords(quote) +
|
|
138
|
-
signatureWords(quote.signature) + signatureWords(depositorSignature) + lockerHalfWords(quote);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
129
|
// predict() answer - one address in a 32-byte word.
|
|
142
130
|
export function decodeAddress(returnedHex) {
|
|
143
131
|
const hex = String(returnedHex || "").replace(/^0x/, "");
|
package/engine/evm/funding.js
CHANGED
|
@@ -14,10 +14,7 @@
|
|
|
14
14
|
// keccak256 is passed in as a dependency, as in the address module: the caller loads the key library, not this
|
|
15
15
|
// module. Monero and Ethereum share Keccak-256, so the hash computed by the bundle matches what Solidity checks.
|
|
16
16
|
|
|
17
|
-
import { encodeCreateOrderAndFund,
|
|
18
|
-
// THE DEPOSITOR BY SIGNATURE: the signature shape comes from one place (evm/depositor.js), next to the protocol
|
|
19
|
-
// shape. Checking the shape here would be a second record of the same rule.
|
|
20
|
-
import { depositorSignatureShape } from "./depositor.js";
|
|
17
|
+
import { encodeCreateOrderAndFund, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
|
|
21
18
|
import { encodeStatus, decodeStatus } from "./escrow.js";
|
|
22
19
|
// THE FEE: rate, amount and recipient are PART OF THE SIGNED QUOTE. The factory no longer has them (the router was
|
|
23
20
|
// removed), so the amount comes from the quote and is cross-checked by the formula mirror. Without a quote there
|
|
@@ -152,7 +149,7 @@ async function waitReceipt(receiptReader, hash, { tries = 12, delayMs = 1500 } =
|
|
|
152
149
|
|
|
153
150
|
// The full funding path. Steps are returned outward so the interface can show each one: predict is a free call,
|
|
154
151
|
// createOrder and lock are transactions in the user's wallet.
|
|
155
|
-
export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote,
|
|
152
|
+
export async function fundOrder({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt: providedSalt, amountWei, amountLabel, claimWindowSeconds, readyWindowSeconds, readyBy, t1, quote, call, send, receipt, onStep }) {
|
|
156
153
|
if (!factory) throw new Error("no escrow factory address set for this network");
|
|
157
154
|
if (!locker) throw new Error("no EVM wallet connected: there is nobody to deposit");
|
|
158
155
|
if (!claimer) throw new Error("the order recipient (claimer) is not set");
|
|
@@ -243,20 +240,10 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
|
|
|
243
240
|
? " (order " + amountWei + " + fee " + fee.feeWei + ")"
|
|
244
241
|
: "";
|
|
245
242
|
onStep?.("locking " + (amountLabel || "funds") + feeWords + " in one transaction - confirm in wallet");
|
|
246
|
-
// WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
// deposits and sends, and the direct swap works as before. On neither path may the sender change the order terms
|
|
251
|
-
// - both signatures cover THE SAME quote digest.
|
|
252
|
-
if (depositorSignature !== null && depositorSignature !== undefined && depositorSignature !== "") {
|
|
253
|
-
const shape = depositorSignatureShape(depositorSignature);
|
|
254
|
-
if (!shape.ok) throw new Error("the depositor signature is unusable: " + shape.reason);
|
|
255
|
-
}
|
|
256
|
-
const relayedByDepositor = Boolean(depositorSignature);
|
|
257
|
-
const createData = relayedByDepositor
|
|
258
|
-
? encodeCreateOrderAndFundByDepositor(quoteEnc, depositorSignature)
|
|
259
|
-
: encodeCreateOrderAndFund(quoteEnc);
|
|
243
|
+
// WHICH FACTORY DOOR THE DEPOSIT GOES THROUGH. There is ONE path: createOrderAndFund, where the wallet that
|
|
244
|
+
// sends the transaction becomes the locker. The sender may not change the order terms - the quote signature
|
|
245
|
+
// covers THE SAME digest the escrow checks.
|
|
246
|
+
const createData = encodeCreateOrderAndFund(quoteEnc);
|
|
260
247
|
const createHash = await send({
|
|
261
248
|
to: factory,
|
|
262
249
|
data: createData,
|
|
@@ -300,8 +287,5 @@ export async function fundOrder({ factory, locker, claimer, commitHalfLocker, co
|
|
|
300
287
|
// THE FEE GOES OUTWARD: it went into the transaction and the deal record must keep it - otherwise the record
|
|
301
288
|
// cannot tell how much the person deposited, and it would diverge from what the chain shows.
|
|
302
289
|
return { feeWei: fee.feeWei, feeTotalWei: totalValueWei, feeBps: fee.bps, feeWasLegacyFactory: Boolean(fee.legacy),
|
|
303
|
-
// WHICH FACTORY PATH RAN: visible outside, not only in the wallet's calldata. By it the checks and the
|
|
304
|
-
// deal record know whether the ETH returns to the signer and not to the sender.
|
|
305
|
-
depositorPath: relayedByDepositor,
|
|
306
290
|
escrow: escrowToUse, predictedEscrow: escrow, escrowMismatch: Boolean(actualEscrow && actualEscrow !== String(escrow).toLowerCase()), salt, birthBlock, terms, createHash, lockHash };
|
|
307
291
|
}
|
package/engine/evm/index.js
CHANGED
|
@@ -32,9 +32,8 @@ import { fundOrder } from "./funding.js";
|
|
|
32
32
|
export * from "./factory.js";
|
|
33
33
|
export * from "./escrow.js";
|
|
34
34
|
export * from "./funding.js";
|
|
35
|
-
// The
|
|
36
|
-
//
|
|
37
|
-
export * from "./depositor.js";
|
|
35
|
+
// The permit verdict (evm/permit.js) goes through the SAME engine seam: the page and the SDK share one
|
|
36
|
+
// protocol code, not two lookalikes.
|
|
38
37
|
export * from "./permit.js";
|
|
39
38
|
// THE ASYNCHRONOUS ROUTE, EXECUTED - through the same engine seam as the decision that chooses it. The page reaches
|
|
40
39
|
// it through the bridge; the SDK carries the same module in its engine mirror (tools/build-sdk-engine.mjs).
|
|
@@ -87,13 +86,10 @@ export async function simulateEscrowFunding(tx) {
|
|
|
87
86
|
// could not pass even the first step. Neither syntax nor checks saw this, because the live path is never run.
|
|
88
87
|
// FOUR FIELDS AND TWO DEADLINES EXPLICITLY. The deadlines come ready (readyBy, t1), not computed from now:
|
|
89
88
|
// they go into the context the proof binds to, so they must be fixed before it.
|
|
90
|
-
export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote,
|
|
89
|
+
export async function fundOrderLive({ factory, locker, claimer, commitHalfLocker, commitHalfClaimer, edPointLocker, edPointClaimer, edViewPointLocker, salt, amountWei, amountLabel, claimWindowSeconds = 3600, readyWindowSeconds = 14400, readyBy, t1, quote, onStep }) {
|
|
91
90
|
return fundOrder({
|
|
92
91
|
factory,
|
|
93
92
|
quote, // signed quote: carries the fee, registry address and provider
|
|
94
|
-
// THE DEPOSITOR SIGNATURE OVER THE QUOTE DIGEST. Present: funding goes via
|
|
95
|
-
// createOrderAndFundByDepositor and anyone may send the transaction. Absent: the old path.
|
|
96
|
-
depositorSignature,
|
|
97
93
|
locker,
|
|
98
94
|
claimer,
|
|
99
95
|
commitHalfLocker,
|
package/engine/evm/permit.js
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
// cannot cover the gas - the swap settles first and the escrow is funded after it. See usdcWithoutEthVerdict below
|
|
23
23
|
// and www/js/evm/asyncRoute.js - and it is a NAMED refusal only when no provider serves the network.
|
|
24
24
|
//
|
|
25
|
-
//
|
|
26
|
-
// not pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
|
|
25
|
+
// A RELAYED ORDER NEEDS A SEPARATE TOKEN ALLOWANCE. Letting a relayer SEND the order-creation transaction does
|
|
26
|
+
// not let it pull the token. Pulling USDC needs a separate TOKEN allowance (approve or permit) addressed to the puller.
|
|
27
27
|
|
|
28
28
|
// THE BALANCE-DRIVEN PATH CHOICE. Whether a provider serves a network ASYNCHRONOUSLY (an intent auction whose
|
|
29
29
|
// settlement arrives later - see www/js/evm/asyncRoute.js) is what decides the path for a person with no own
|
|
@@ -179,7 +179,7 @@ export function usdcWithoutEthVerdict({
|
|
|
179
179
|
if (!isAddr(orchestrator)) {
|
|
180
180
|
return {
|
|
181
181
|
ok: false, blocked: true, kind: "no-orchestrator", path: null,
|
|
182
|
-
reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none
|
|
182
|
+
reason: "no executor contract: only a contract could pull a token by the user signature and deposit native into the escrow in one transaction, and this build has none",
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
if (permitSupported === false) {
|
package/engine/evm/session.js
CHANGED
|
@@ -551,12 +551,10 @@ export async function signMessage(message) {
|
|
|
551
551
|
}
|
|
552
552
|
|
|
553
553
|
// SIGNING TYPED DATA with the wallet (EIP-712, eth_signTypedData_v4). This is also NOT a transaction: the
|
|
554
|
-
// signature moves no money and costs no gas, so a
|
|
555
|
-
// depositor lets the factory record his address in the order, and anyone may send the transaction for him
|
|
556
|
-
// (the createOrderAndFundByDepositor path).
|
|
554
|
+
// signature moves no money and costs no gas, so a wallet without its own ETH can give it.
|
|
557
555
|
//
|
|
558
|
-
// WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here
|
|
559
|
-
//
|
|
556
|
+
// WHAT EXACTLY IS SIGNED IS DECIDED BY THE CALLER: ready typed data arrives here as an object, not a string.
|
|
557
|
+
// Assembling the shape here would be a second record of the protocol next to the real one.
|
|
560
558
|
export async function signTypedData(typedData) {
|
|
561
559
|
if (!provider || state.status !== "connected") {
|
|
562
560
|
throw new Error("wallet not connected: typed-data signing is impossible");
|
package/engine/sdk/bridge.js
CHANGED
|
@@ -68,7 +68,7 @@ import { height as nodeHeightValue } from "../monero/node.js";
|
|
|
68
68
|
|
|
69
69
|
// THE CORE ADDRESS AS A STRING - THE BUILT BUNDLE UNDER www/: the page gets the core as ONE module, not a pile
|
|
70
70
|
// of sdk/ sources; ?v=<hash> keeps the cache fresh.
|
|
71
|
-
const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=
|
|
71
|
+
const SDK_BUNDLE = "/assets/vendors/sdk/sdk-browser.js?v=69d79c13";
|
|
72
72
|
|
|
73
73
|
// A NAMED SEAM FOR CHECKS - THE SAME TECHNIQUE AS sdkBookUse BELOW. The asynchronous path is driven against a
|
|
74
74
|
// stand-in order book (the demo network has no provider), and the ordinary escrow funding that follows a settled
|
package/package.json
CHANGED
package/src/quoteEip712Spec.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// signature covers exactly the XMR the chain verifies at order birth. Earlier this field
|
|
23
23
|
// rode in the quote object OUTSIDE the digest on purpose - a field the chain could not
|
|
24
24
|
// see must not be signed; once the contract learned it, it moved in here together with
|
|
25
|
-
// the typehash, the
|
|
25
|
+
// the typehash, the order-quote form and the independent vector.
|
|
26
26
|
// commitHalfClaimer, - the provider's own commitment and ed25519 spend point: both are inputs to the
|
|
27
27
|
// edPointClaimer escrow's termsHash, so they are bound and cannot be swapped for another order's.
|
|
28
28
|
// chainId - bound in the domain AND in the message. A quote signed for one network does not
|
package/src/swap-flow.mjs
CHANGED
|
@@ -66,10 +66,6 @@ export function escrowCashierAddress(chainSlug) {
|
|
|
66
66
|
// The full flow. onStage(stage, detail) is called on every step - the interface must show what is going on.
|
|
67
67
|
export async function runAtomicOrder({
|
|
68
68
|
chainSlug, locker, claimer, amountWei, amountLabel, providerId = null,
|
|
69
|
-
// THE DEPOSITOR'S SIGNATURE (issue #97, parts A/C): when given, the order is placed through
|
|
70
|
-
// createOrderAndFundByDepositor - any wallet may send the transaction, while the signer is recorded as the
|
|
71
|
-
// depositor.
|
|
72
|
-
depositorSignature = null,
|
|
73
69
|
claimWindowSeconds = 3600, readyWindowSeconds = 14400, salt: providedSalt, onStage,
|
|
74
70
|
// THE SUBSTITUTION POINTS FOR THE SAKE OF THE CHECK, and they are not decoration. The live flow could be
|
|
75
71
|
// neither run in a check nor covered otherwise: it requires a browser Worker and a signature in the wallet.
|
|
@@ -261,9 +257,6 @@ export async function runAtomicOrder({
|
|
|
261
257
|
// (or from the caller in a simulation) and already participates in the context over which our half was
|
|
262
258
|
// computed.
|
|
263
259
|
factory, locker, claimer: order.claimer,
|
|
264
|
-
// WE PASS THE DEPOSITOR'S SIGNATURE FURTHER instead of losing it halfway: without it the funding would go
|
|
265
|
-
// the previous way, and the relayer would get a NotLocker refusal only after the person had signed.
|
|
266
|
-
depositorSignature,
|
|
267
260
|
// THE DEADLINES GO INTO THE CONTRACT EXACTLY THE ONES THAT ENTERED THE CONTEXT. This is not duplication but
|
|
268
261
|
// a condition: diverge by even a second - and the proof by which the provider confirms the half stops
|
|
269
262
|
// agreeing.
|
package/engine/evm/depositor.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// GENERATED FILE - a byte-for-byte copy of the engine module www/js/evm/depositor.js for the SDK package.
|
|
2
|
-
// Edit the source under www/js and regenerate the mirror; a drift guard reddens on any difference.
|
|
3
|
-
// Everything below this header is identical to the source.
|
|
4
|
-
|
|
5
|
-
// THE DEPOSITOR BY SIGNATURE, CLIENT SIDE: EIP-712 typed data over the QUOTE DIGEST.
|
|
6
|
-
//
|
|
7
|
-
// WHY. The factory has a path createOrderAndFundByDepositor: ANYONE may send the transaction (a relayer, our
|
|
8
|
-
// service), but the depositor is whoever signed the quote digest, so the depositor must sign first. A wallet
|
|
9
|
-
// signature is typed data, not a transaction: it costs no gas and moves no money, so a person without ETH can give it.
|
|
10
|
-
//
|
|
11
|
-
// WHY THIS FORM, NOT AN INVENTED ONE. The field set and domain are PART OF THE PROTOCOL - the same OrderQuote
|
|
12
|
-
// the escrow verifies and the node signs. Two swapped fields give a DIFFERENT digest and the factory refuses
|
|
13
|
-
// BadDepositorSignature. So the form is ONE list below, and a check computes the digest with it and compares.
|
|
14
|
-
//
|
|
15
|
-
// DOMAIN. verifyingContract is the FACTORY ADDRESS FROM THE QUOTE (field `factory`), chainId is live: a signature
|
|
16
|
-
// made for another factory or network will not recover here. There is no second source of the domain.
|
|
17
|
-
//
|
|
18
|
-
// NO CRYPTOGRAPHY HERE: the wallet computes the digest (eth_signTypedData_v4). Here is only the form and its check.
|
|
19
|
-
|
|
20
|
-
import { SIGNING_DOMAIN } from "../core/config.js";
|
|
21
|
-
|
|
22
|
-
// The domain name and version are quote protocol v8. They are NOT brand literals here: they belong to the registry
|
|
23
|
-
// contract (it returns DOMAIN_SEPARATOR()), so they are read from the config. The defaults equal today's values.
|
|
24
|
-
export const DEPOSITOR_DOMAIN_NAME = SIGNING_DOMAIN.name;
|
|
25
|
-
export const DEPOSITOR_DOMAIN_VERSION = SIGNING_DOMAIN.version;
|
|
26
|
-
|
|
27
|
-
// FIELD SET IN CONTRACT ORDER. The order is part of the ABI: a swap gives another digest. One list feeds both
|
|
28
|
-
// the type string and the message, so they cannot diverge.
|
|
29
|
-
export const ORDER_QUOTE_TYPED_FIELDS = [
|
|
30
|
-
["provider", "address"],
|
|
31
|
-
["quoteKey", "address"],
|
|
32
|
-
["registry", "address"],
|
|
33
|
-
["cashier", "address"],
|
|
34
|
-
["locker", "address"],
|
|
35
|
-
["claimer", "address"],
|
|
36
|
-
["amount", "uint256"],
|
|
37
|
-
["readyBy", "uint64"],
|
|
38
|
-
["t1", "uint64"],
|
|
39
|
-
["salt", "bytes32"],
|
|
40
|
-
["commitHalfClaimer", "bytes32"],
|
|
41
|
-
["edPointClaimer", "bytes32"],
|
|
42
|
-
["chainId", "uint256"],
|
|
43
|
-
["feeBps", "uint256"],
|
|
44
|
-
["fee", "uint256"],
|
|
45
|
-
["feeRecipient", "address"],
|
|
46
|
-
["validUntil", "uint64"],
|
|
47
|
-
["nonce", "uint256"],
|
|
48
|
-
// THE XMR SIDE OF THE DEAL, in atomic units: it joined the signed set together with the contract
|
|
49
|
-
// (OrderQuote.sol), the node/SDK spec and the vector, so the depositor's signature covers the same XMR the chain
|
|
50
|
-
// verifies. A field the chain could not see used to stay OUT of this list on purpose - that is no longer the case.
|
|
51
|
-
["xmrAmount", "uint256"],
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// The message type. Built from the list: editing the list changes both the type and the signature, so it cannot
|
|
55
|
-
// go unnoticed.
|
|
56
|
-
export const ORDER_QUOTE_TYPED_TYPE = "OrderQuote(" + ORDER_QUOTE_TYPED_FIELDS.map(([n, t]) => t + " " + n).join(",") + ")";
|
|
57
|
-
|
|
58
|
-
const isHex = (v, bytes) => new RegExp("^(0x)?[0-9a-fA-F]{" + bytes * 2 + "}$").test(String(v === undefined || v === null ? "" : v).trim());
|
|
59
|
-
const isNum = (v) => /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(String(v === undefined || v === null ? "" : v).trim());
|
|
60
|
-
|
|
61
|
-
// FORM IS NORMALISED, NOT DEMANDED. The quote carries ed25519 points WITHOUT the 0x prefix, while the wallet and
|
|
62
|
-
// contract expect the same BYTES. So we normalise here and reject anything that is not address/bytes32/number.
|
|
63
|
-
function normaliseField(name, type, value) {
|
|
64
|
-
const raw = String(value === undefined || value === null ? "" : value).trim();
|
|
65
|
-
if (raw === "") throw new Error("the quote carries no depositor-signature field: " + name);
|
|
66
|
-
if (type === "address") {
|
|
67
|
-
if (!isHex(raw, 20)) throw new Error("field " + name + " is not an address: " + raw);
|
|
68
|
-
return "0x" + raw.replace(/^0x/, "").toLowerCase();
|
|
69
|
-
}
|
|
70
|
-
if (type === "bytes32") {
|
|
71
|
-
if (!isHex(raw, 32)) throw new Error("field " + name + " is not bytes32: " + raw);
|
|
72
|
-
return "0x" + raw.replace(/^0x/, "").toLowerCase();
|
|
73
|
-
}
|
|
74
|
-
// uint64 / uint256: the wallet accepts a string; decimal and hex are parsed the same.
|
|
75
|
-
if (!isNum(raw)) throw new Error("field " + name + " is not a number: " + raw);
|
|
76
|
-
return String(BigInt(raw));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* EIP-712 typed data that the depositor signs (eth_signTypedData_v4).
|
|
81
|
-
* The domain is built FROM THE QUOTE: verifyingContract = quote.factory, chainId = quote.chainId.
|
|
82
|
-
* @returns {{ domain: object, types: object, primaryType: string, message: object }}
|
|
83
|
-
*/
|
|
84
|
-
export function orderQuoteTypedData(quote) {
|
|
85
|
-
if (!quote || typeof quote !== "object") throw new Error("no signed quote: nothing to sign");
|
|
86
|
-
if (!isHex(quote.factory, 20)) throw new Error("the quote has no factory address - cannot build the signing domain");
|
|
87
|
-
if (!isNum(quote.chainId)) throw new Error("the quote has no chainId - cannot build the signing domain");
|
|
88
|
-
const message = {};
|
|
89
|
-
for (const [name, type] of ORDER_QUOTE_TYPED_FIELDS) {
|
|
90
|
-
message[name] = normaliseField(name, type, quote[name]);
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
domain: {
|
|
94
|
-
name: DEPOSITOR_DOMAIN_NAME,
|
|
95
|
-
version: DEPOSITOR_DOMAIN_VERSION,
|
|
96
|
-
chainId: Number(BigInt(quote.chainId)),
|
|
97
|
-
verifyingContract: "0x" + String(quote.factory).replace(/^0x/, "").toLowerCase(),
|
|
98
|
-
},
|
|
99
|
-
types: {
|
|
100
|
-
// EIP712Domain explicit: wallets show it to the person, and it must match what the chain computes.
|
|
101
|
-
EIP712Domain: [
|
|
102
|
-
{ name: "name", type: "string" },
|
|
103
|
-
{ name: "version", type: "string" },
|
|
104
|
-
{ name: "chainId", type: "uint256" },
|
|
105
|
-
{ name: "verifyingContract", type: "address" },
|
|
106
|
-
],
|
|
107
|
-
OrderQuote: ORDER_QUOTE_TYPED_FIELDS.map(([name, type]) => ({ name, type })),
|
|
108
|
-
},
|
|
109
|
-
primaryType: "OrderQuote",
|
|
110
|
-
message,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// SIGNATURE ON THE WIRE - 65 BYTES r || s || v. The form is checked HERE: the factory takes the signature as
|
|
115
|
-
// three words, and a "signature-like" string must not reach the contract as consent.
|
|
116
|
-
export function depositorSignatureShape(signature) {
|
|
117
|
-
const hex = String(signature === undefined || signature === null ? "" : signature).replace(/^0x/, "");
|
|
118
|
-
if (!/^[0-9a-fA-F]{130}$/.test(hex)) return { ok: false, reason: "the depositor signature must be 65 bytes (r || s || v), got " + hex.length / 2 + " bytes" };
|
|
119
|
-
let v = parseInt(hex.slice(128, 130), 16);
|
|
120
|
-
if (v < 27) v += 27;
|
|
121
|
-
if (v !== 27 && v !== 28) return { ok: false, reason: "depositor signature v outside {27,28}: " + v };
|
|
122
|
-
return { ok: true, r: "0x" + hex.slice(0, 64).toLowerCase(), s: "0x" + hex.slice(64, 128).toLowerCase(), v: 27 + (v - 27) };
|
|
123
|
-
}
|