@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 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
@@ -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 the depositor signature. It is NOT a brand literal in the signer: it
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" });
@@ -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/, "");
@@ -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, encodeCreateOrderAndFundByDepositor, encodePredict, decodeAddress, ORDER_CREATED_TOPIC } from "./factory.js";
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, depositorSignature = null, call, send, receipt, onStep }) {
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, AND IT IS NOT DECORATION. If a depositor signature is present, we
247
- // deposit via part A: createOrderAndFundByDepositor recovers the depositor FROM THE SIGNATURE over the quote
248
- // digest, so anyone may send the transaction (a relayer, our service) while the `locker` role (right to refund,
249
- // right to mark readiness) stays with the signer. Without a signature, the old createOrderAndFund: one wallet
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
  }
@@ -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 depositor signature form (evm/depositor.js) and the permit verdict (evm/permit.js) go through the SAME
36
- // engine seam: the page and the SDK share one protocol code, not two lookalikes.
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, depositorSignature, onStep }) {
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,
@@ -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
- // THE DEPOSITOR SIGNATURE IS A DIFFERENT ALLOWANCE. It lets a relayer SEND the order-creation transaction, but
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. The depositor signature without it lets a relayer SEND the order creation, but not pull the token",
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) {
@@ -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 person WITHOUT their own ETH can give it. With it the
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 (www/js/evm/depositor.js),
559
- // not a string. Assembling the shape here would be a second record of the protocol next to the real one.
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");
@@ -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=3ce64c53";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninsei-labs/sdk",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -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 depositor form and the independent vector.
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.
@@ -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
- }