@ninsei-labs/sdk 0.39.3 → 0.40.0-develop.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/engine/atomic/dleq2.js +86 -111
- package/engine/atomic/halfenc.js +58 -72
- package/engine/atomic/halves.js +31 -53
- package/engine/atomic/order-client.js +15 -16
- package/engine/atomic/order.js +44 -62
- package/engine/atomic/orderContext.js +26 -50
- package/engine/atomic/orderHalfCommit.js +17 -18
- package/engine/core/chainSource.js +54 -69
- package/engine/core/clock.js +11 -12
- package/engine/core/config.js +554 -449
- package/engine/core/finality.js +34 -53
- package/engine/core/format.js +35 -36
- package/engine/core/preSignGuards.js +109 -117
- package/engine/core/rfqSource.js +57 -75
- package/engine/core/sellWindow.js +23 -24
- package/engine/core/store.js +20 -21
- package/engine/core/swap.js +242 -243
- package/engine/evm/amounts.js +50 -51
- package/engine/evm/asyncExec.js +577 -0
- package/engine/evm/asyncLegs.js +308 -0
- package/engine/evm/asyncRoute.js +179 -0
- package/engine/evm/auth.js +41 -48
- package/engine/evm/claimGas.js +28 -47
- package/engine/evm/depositor.js +39 -43
- package/engine/evm/dex.js +266 -267
- package/engine/evm/escrow.js +113 -129
- package/engine/evm/factory.js +71 -77
- package/engine/evm/fees.js +41 -42
- package/engine/evm/funding.js +160 -162
- package/engine/evm/gasReserve.js +67 -36
- package/engine/evm/index.js +46 -53
- package/engine/evm/injected.js +34 -42
- package/engine/evm/permit.js +129 -56
- package/engine/evm/prices.js +30 -45
- package/engine/evm/session.js +188 -189
- package/engine/mock/chain.js +17 -18
- package/engine/monero/address.js +33 -48
- package/engine/monero/halvesSweep.js +25 -43
- package/engine/monero/node.js +14 -15
- package/engine/monero/scan.js +82 -95
- package/engine/monero/swapKeys.js +30 -33
- package/engine/monero/unlock.js +17 -20
- package/engine/monero/wallet.js +32 -42
- package/engine/recovery/recoveryFile.js +154 -161
- package/engine/recovery/restore.js +50 -61
- package/engine/sdk/bridge.js +244 -234
- package/engine/ui/dom.js +4 -5
- package/engine/ui/walletModal.js +20 -31
- package/index.d.ts +8 -0
- package/package.json +1 -1
- package/src/adapters/evm-wallet.mjs +16 -0
- package/src/adapters/monero-wallet.mjs +1 -1
- package/src/adapters/monero.mjs +1 -1
- package/src/adapters/node-test-evm-wallet.mjs +5 -5
- package/src/config.mjs +1 -1
- package/src/engine.mjs +4 -0
- package/src/legs/cow-spec.mjs +254 -0
- package/src/legs/cow.mjs +315 -0
- package/src/legs/evm.mjs +86 -2
- package/src/legs/kyber-spec.mjs +265 -0
- package/src/legs/kyber.mjs +425 -0
- package/src/legs/shape.mjs +35 -0
- package/src/legs/uniswaprelay-spec.mjs +541 -0
- package/src/legs/uniswaprelay.mjs +422 -0
- package/src/legs/uniswapx-spec.mjs +329 -0
- package/src/legs/uniswapx.mjs +362 -0
- package/src/quotes.mjs +1 -1
- package/src/swaps.mjs +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// EIP-712 TYPED DATA FOR A KYBERSWAP LIMIT ORDER - THE SPEC, IN THE SDK'S OWN STYLE.
|
|
2
|
+
//
|
|
3
|
+
// WHY A SEPARATE SPEC AND NOT A NEW LIBRARY. This repository builds EIP-712 typed data itself (the same pattern as
|
|
4
|
+
// src/legs/cow-spec.mjs for the CoW order and src/quoteEip712Spec.mjs for the order quote): the domain separator,
|
|
5
|
+
// the struct hash and the digest are assembled here from the repository's own crypto primitives (src/primitives.mjs),
|
|
6
|
+
// and the SIGNING reuses the existing signer (signQuoteDigest from src/quoteEip712Spec.mjs, which signs ANY 32-byte
|
|
7
|
+
// digest). No dependency is added.
|
|
8
|
+
//
|
|
9
|
+
// EVERY EXTERNAL FACT IS PINNED TO ITS SOURCE, and where a value can be recomputed it is (the check does it):
|
|
10
|
+
// * the ORDER type, its field types and their ORDER - the Limit Order contract, KyberNetwork DSLOProtocol, abstract
|
|
11
|
+
// contract DSOrderMixin (verified source on Etherscan, e.g. the deployment on Arbitrum One
|
|
12
|
+
// 0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C, and the same DSLOProtocol on Ethereum/BSC/Polygon):
|
|
13
|
+
// "bytes32 public constant LIMIT_ORDER_TYPEHASH = keccak256('Order(uint256 salt,address makerAsset,address
|
|
14
|
+
// takerAsset,address maker,address receiver,address allowedSender,uint256 makingAmount,uint256 takingAmount,
|
|
15
|
+
// uint256 feeConfig,bytes makerAssetData,bytes takerAssetData,bytes getMakerAmount,bytes getTakerAmount,bytes
|
|
16
|
+
// predicate,bytes interaction)')". The SAME field names and order are what the order book returns from
|
|
17
|
+
// POST /write/api/v1/orders/sign-message (docs.kyberswap.com, Limit Order API, Maker API, "Get Unsigned Create
|
|
18
|
+
// Order Message"): types.Order = [{salt,uint256},{makerAsset,address},{takerAsset,address},{maker,address},
|
|
19
|
+
// {receiver,address},{allowedSender,address},{makingAmount,uint256},{takingAmount,uint256},{feeConfig,uint256},
|
|
20
|
+
// {makerAssetData,bytes},{takerAssetData,bytes},{getMakerAmount,bytes},{getTakerAmount,bytes},{predicate,bytes},
|
|
21
|
+
// {interaction,bytes}];
|
|
22
|
+
// * the DSOrder type that the OPERATOR co-signature is over - the same contract source:
|
|
23
|
+
// "bytes32 public constant DS_LIMIT_ORDER_TYPEHASH = keccak256('DSOrder(bytes32 orderHash,uint32
|
|
24
|
+
// opExpireTime)')". The operator signature binds (orderHash, opExpireTime) - see the scope note in kyber.mjs;
|
|
25
|
+
// * the DOMAIN name "Kyber DSLO Protocol", version "1", and the domain separator shape
|
|
26
|
+
// keccak256(abi.encode(DOMAIN_TYPE_HASH, name, version, chainId, verifyingContract)) - the order book's own
|
|
27
|
+
// sign-message response (docs.kyberswap.com, Maker API) returns exactly domain {name:"Kyber DSLO Protocol",
|
|
28
|
+
// version:"1", chainId, verifyingContract} with EIP712Domain = name/version/chainId/verifyingContract, and the
|
|
29
|
+
// contract is EIP712 (OZ) with the same four fields;
|
|
30
|
+
// * the contract address 0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C (identical on every supported chain) -
|
|
31
|
+
// docs.kyberswap.com, Limit Order API, "Contracts & Addresses";
|
|
32
|
+
// * the ORDER IDENTITY. There is no CoW-style packed UID here: the contract's hashOrder() is
|
|
33
|
+
// _hashTypedDataV4(keccak256(abi.encode(LIMIT_ORDER_TYPEHASH, StaticOrder{...}, keccak256(makerAssetData),
|
|
34
|
+
// keccak256(takerAssetData), keccak256(getMakerAmount), keccak256(getTakerAmount), keccak256(predicate),
|
|
35
|
+
// keccak256(interaction)))) - i.e. EXACTLY this digest - and the order book addresses an order by a numeric id
|
|
36
|
+
// it assigns on creation (POST /write/api/v1/orders -> data.id). So `chainId` + this digest is the order's
|
|
37
|
+
// identity on chain, and the numeric id is the off-chain handle for reading/cancelling.
|
|
38
|
+
//
|
|
39
|
+
// THE ONE FACT THAT IS NOT TAKEN FROM MEMORY is the RESULT: the check recomputes the digest of a REAL order returned
|
|
40
|
+
// by KyberSwap's own Taker API and demands the order's OWN signature recover its maker address - no amount of
|
|
41
|
+
// reasoning can fake that.
|
|
42
|
+
//
|
|
43
|
+
// The domain name is assembled from parts ON PURPOSE, exactly as in cow-spec.mjs: a single literal of it is a phrase
|
|
44
|
+
// of several space-separated words, and the SDK reads such literals as user-facing text. Do not "simplify" it.
|
|
45
|
+
import { keccak256, secp256k1 } from "../primitives.mjs";
|
|
46
|
+
import { signQuoteDigest } from "../quoteEip712Spec.mjs";
|
|
47
|
+
|
|
48
|
+
// The domain name and version, from the order book's own sign-message response (docs.kyberswap.com, Maker API).
|
|
49
|
+
// Built from parts so no literal here is a multi-word phrase (see the header).
|
|
50
|
+
export const KYBER_DOMAIN_NAME = ["Kyber", "DSLO", "Protocol"].join(" ");
|
|
51
|
+
export const KYBER_DOMAIN_VERSION = "1";
|
|
52
|
+
|
|
53
|
+
// The limit order contract that verifies the signature; identical on every network KyberSwap serves
|
|
54
|
+
// (docs.kyberswap.com, "Contracts & Addresses": Ethereum, BSC, Arbitrum, Polygon, ... all DSLOProtocol 0xcab2...).
|
|
55
|
+
export const KYBER_LIMIT_ORDER_CONTRACT = "0xcab2FA2eeab7065B45CBcF6E3936dDE2506b4f6C";
|
|
56
|
+
|
|
57
|
+
// The type strings, assembled from their fields so the order is the single source of truth here (as in DSOrderMixin).
|
|
58
|
+
const DOMAIN_TYPE_PARTS = ["EIP712Domain(", "string name,", "string version,", "uint256 chainId,", "address verifyingContract)"];
|
|
59
|
+
export const KYBER_DOMAIN_TYPE = DOMAIN_TYPE_PARTS.join("");
|
|
60
|
+
|
|
61
|
+
// The message, field by field and IN THIS ORDER - the order is the protocol (DSOrderMixin.LIMIT_ORDER_TYPEHASH).
|
|
62
|
+
export const KYBER_ORDER_FIELDS = [
|
|
63
|
+
["salt", "uint256"],
|
|
64
|
+
["makerAsset", "address"],
|
|
65
|
+
["takerAsset", "address"],
|
|
66
|
+
["maker", "address"],
|
|
67
|
+
["receiver", "address"],
|
|
68
|
+
["allowedSender", "address"],
|
|
69
|
+
["makingAmount", "uint256"],
|
|
70
|
+
["takingAmount", "uint256"],
|
|
71
|
+
["feeConfig", "uint256"],
|
|
72
|
+
["makerAssetData", "bytes"],
|
|
73
|
+
["takerAssetData", "bytes"],
|
|
74
|
+
["getMakerAmount", "bytes"],
|
|
75
|
+
["getTakerAmount", "bytes"],
|
|
76
|
+
["predicate", "bytes"],
|
|
77
|
+
["interaction", "bytes"],
|
|
78
|
+
];
|
|
79
|
+
export const KYBER_ORDER_TYPE = "Order(" + KYBER_ORDER_FIELDS.map((f) => f[1] + " " + f[0]).join(",") + ")";
|
|
80
|
+
|
|
81
|
+
// The contract's OWN type-hash constant (DSOrderMixin.LIMIT_ORDER_TYPEHASH). It is NOT trusted: the check recomputes
|
|
82
|
+
// keccak256(KYBER_ORDER_TYPE) and demands it equal this - so a typo in the field list above reddens rather than
|
|
83
|
+
// silently signing orders the contract would reject.
|
|
84
|
+
export const KYBER_ORDER_TYPE_HASH = "0x346e0660be2aef32794b25da30f6733303978cb6c10c395480eb5d2c56bcd956";
|
|
85
|
+
|
|
86
|
+
// The OPERATOR co-signature type (DSOrderMixin.DS_LIMIT_ORDER_TYPEHASH). A fill carries a second signature - the
|
|
87
|
+
// KyberSwap operator signs DSOrder(orderHash, opExpireTime) - which is why a Maker order alone cannot be filled
|
|
88
|
+
// without a fresh operator signature (see kyber.mjs). Kept here so the role is checkable, not just described.
|
|
89
|
+
export const KYBER_DS_ORDER_FIELDS = [
|
|
90
|
+
["orderHash", "bytes32"],
|
|
91
|
+
["opExpireTime", "uint32"],
|
|
92
|
+
];
|
|
93
|
+
export const KYBER_DS_ORDER_TYPE = "DSOrder(" + KYBER_DS_ORDER_FIELDS.map((f) => f[1] + " " + f[0]).join(",") + ")";
|
|
94
|
+
export const KYBER_DS_ORDER_TYPE_HASH = "0xd5012ad50b71ffd4515ff18ee49caf4bcd3d1e15ff26a40cab61bbb54200b063";
|
|
95
|
+
|
|
96
|
+
// Error tokens are SINGLE WORDS (the SDK forbids multi-word literals in sdk/src): the spec names a failure by token
|
|
97
|
+
// and the caller maps it to its own code.
|
|
98
|
+
const boom = (token) => { throw new Error(token); };
|
|
99
|
+
|
|
100
|
+
const utf8 = (s) => new TextEncoder().encode(String(s));
|
|
101
|
+
const concat = (...arrays) => {
|
|
102
|
+
const total = arrays.reduce((n, a) => n + a.length, 0);
|
|
103
|
+
const out = new Uint8Array(total);
|
|
104
|
+
let at = 0;
|
|
105
|
+
for (const a of arrays) { out.set(a, at); at += a.length; }
|
|
106
|
+
return out;
|
|
107
|
+
};
|
|
108
|
+
// BYTES ARE ACCEPTED IN EITHER FORM: the order book returns some hex fields WITHOUT a 0x prefix and others WITH one
|
|
109
|
+
// (docs.kyberswap.com, Taker API: signature, getMakerAmount, ... are returned without a 0x prefix; an empty value
|
|
110
|
+
// means empty bytes). Both are the same bytes, so both are accepted; an empty value is empty bytes.
|
|
111
|
+
const hexToBytes = (hex) => {
|
|
112
|
+
const h = String(hex === undefined || hex === null ? "" : hex).replace(/^0x/, "");
|
|
113
|
+
if (h.length % 2) boom("kyber-odd-hex");
|
|
114
|
+
const out = new Uint8Array(h.length / 2);
|
|
115
|
+
for (let i = 0; i < out.length; i++) {
|
|
116
|
+
const byte = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
117
|
+
if (Number.isNaN(byte)) boom("kyber-bad-hex");
|
|
118
|
+
out[i] = byte;
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
};
|
|
122
|
+
const bytesToHex = (bytes) => {
|
|
123
|
+
let s = "0x";
|
|
124
|
+
for (const b of bytes) s += b.toString(16).padStart(2, "0");
|
|
125
|
+
return s;
|
|
126
|
+
};
|
|
127
|
+
const asBigInt = (v) => {
|
|
128
|
+
if (typeof v === "bigint") return v;
|
|
129
|
+
const s = String(v === undefined || v === null ? "" : v).trim();
|
|
130
|
+
if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
|
|
131
|
+
if (/^[0-9]+$/.test(s)) return BigInt(s);
|
|
132
|
+
boom("kyber-bad-number");
|
|
133
|
+
};
|
|
134
|
+
const wordUint = (v, bits) => {
|
|
135
|
+
const n = asBigInt(v);
|
|
136
|
+
if (n < 0n) boom("kyber-negative");
|
|
137
|
+
if (n >= (1n << BigInt(bits))) boom("kyber-overflow");
|
|
138
|
+
const out = new Uint8Array(32);
|
|
139
|
+
let x = n;
|
|
140
|
+
for (let i = 31; i >= 0 && x > 0n; i--) { out[i] = Number(x & 0xffn); x >>= 8n; }
|
|
141
|
+
return out;
|
|
142
|
+
};
|
|
143
|
+
const wordAddress = (v) => {
|
|
144
|
+
const s = String(v === undefined || v === null ? "" : v).trim();
|
|
145
|
+
if (!/^(0x)?[0-9a-fA-F]{40}$/.test(s)) boom("kyber-bad-address");
|
|
146
|
+
const out = new Uint8Array(32);
|
|
147
|
+
out.set(hexToBytes(s.replace(/^0x/, "")), 12);
|
|
148
|
+
return out;
|
|
149
|
+
};
|
|
150
|
+
const wordBytes32 = (v) => {
|
|
151
|
+
const s = String(v === undefined || v === null ? "" : v).trim();
|
|
152
|
+
if (!/^(0x)?[0-9a-fA-F]{64}$/.test(s)) boom("kyber-bad-bytes32");
|
|
153
|
+
return hexToBytes(s.replace(/^0x/, ""));
|
|
154
|
+
};
|
|
155
|
+
// A `bytes` field in EIP-712 is encoded as the keccak256 of its bytes - empty bytes hash to keccak256("").
|
|
156
|
+
const wordBytes = (v) => keccak256(hexToBytes(v));
|
|
157
|
+
|
|
158
|
+
const encodeField = (type, value) => {
|
|
159
|
+
if (type === "address") return wordAddress(value);
|
|
160
|
+
if (type === "bytes32") return wordBytes32(value);
|
|
161
|
+
if (type === "uint32") return wordUint(value, 32);
|
|
162
|
+
if (type === "uint256") return wordUint(value, 256);
|
|
163
|
+
if (type === "bytes") return wordBytes(value);
|
|
164
|
+
boom("kyber-bad-type");
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// The fields an order cannot do without. An absent field is not "probably zero": the caller refuses, not signs.
|
|
168
|
+
// A `bytes` field is DIFFERENT: EMPTY BYTES are a legitimate value (a public order carries makerAssetData "0x" and an
|
|
169
|
+
// empty interaction), so for those only undefined/null is missing - an empty string is a real, allowed value.
|
|
170
|
+
export const kyberOrderMissingFields = (order) => {
|
|
171
|
+
const missing = [];
|
|
172
|
+
for (const [name, type] of KYBER_ORDER_FIELDS) {
|
|
173
|
+
const v = order ? order[name] : undefined;
|
|
174
|
+
if (v === undefined || v === null) missing.push(name);
|
|
175
|
+
else if (type !== "bytes" && v === "") missing.push(name);
|
|
176
|
+
}
|
|
177
|
+
return missing;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// The EIP-712 domain. chainId and verifyingContract come LIVE from the order's context, not cached: the digest must
|
|
181
|
+
// change when either does, else an order could move between networks or contracts.
|
|
182
|
+
export const kyberDomainSeparator = ({ chainId, contract = KYBER_LIMIT_ORDER_CONTRACT }) => keccak256(concat(
|
|
183
|
+
keccak256(utf8(KYBER_DOMAIN_TYPE)),
|
|
184
|
+
keccak256(utf8(KYBER_DOMAIN_NAME)),
|
|
185
|
+
keccak256(utf8(KYBER_DOMAIN_VERSION)),
|
|
186
|
+
wordUint(chainId, 256),
|
|
187
|
+
wordAddress(contract),
|
|
188
|
+
));
|
|
189
|
+
|
|
190
|
+
export const kyberStructHash = (order) => {
|
|
191
|
+
const parts = [keccak256(utf8(KYBER_ORDER_TYPE))];
|
|
192
|
+
for (const [name, type] of KYBER_ORDER_FIELDS) parts.push(encodeField(type, order ? order[name] : undefined));
|
|
193
|
+
return keccak256(concat(...parts));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// The digest the wallet key signs - and the contract's hashOrder() result. Anything that changes here changes the
|
|
197
|
+
// recovered address (and the order the operator co-signature binds).
|
|
198
|
+
export const kyberOrderDigest = ({ order, chainId, contract = KYBER_LIMIT_ORDER_CONTRACT }) =>
|
|
199
|
+
keccak256(concat(Uint8Array.from([0x19, 0x01]), kyberDomainSeparator({ chainId, contract }), kyberStructHash(order)));
|
|
200
|
+
|
|
201
|
+
export const kyberOrderDigestHex = (ctx) => bytesToHex(kyberOrderDigest(ctx));
|
|
202
|
+
|
|
203
|
+
// The digest the OPERATOR co-signature is over: EIP-712 DSOrder(orderHash, opExpireTime) in the same domain. Kept so
|
|
204
|
+
// the operator role is a value the check can recompute, not only prose. A fill without this signature reverts on
|
|
205
|
+
// chain ("LOP: bad op signature", DSOrderMixin.fillOrderTo).
|
|
206
|
+
export const kyberOperatorDigest = ({ orderHash, opExpireTime, chainId, contract = KYBER_LIMIT_ORDER_CONTRACT }) => {
|
|
207
|
+
const struct = keccak256(concat(keccak256(utf8(KYBER_DS_ORDER_TYPE)), wordBytes32(orderHash), wordUint(opExpireTime, 32)));
|
|
208
|
+
return keccak256(concat(Uint8Array.from([0x19, 0x01]), kyberDomainSeparator({ chainId, contract }), struct));
|
|
209
|
+
};
|
|
210
|
+
export const kyberOperatorDigestHex = (ctx) => bytesToHex(kyberOperatorDigest(ctx));
|
|
211
|
+
|
|
212
|
+
// The typed data a wallet signs (eth_signTypedData_v4). The message carries every Order field as the order book
|
|
213
|
+
// returns them (strings), so the wallet shows them readably and the contract hashes them identically.
|
|
214
|
+
export const kyberOrderTypedData = ({ order, chainId, contract = KYBER_LIMIT_ORDER_CONTRACT }) => ({
|
|
215
|
+
domain: { name: KYBER_DOMAIN_NAME, version: KYBER_DOMAIN_VERSION, chainId: Number(chainId), verifyingContract: contract },
|
|
216
|
+
types: {
|
|
217
|
+
EIP712Domain: [
|
|
218
|
+
{ name: "name", type: "string" },
|
|
219
|
+
{ name: "version", type: "string" },
|
|
220
|
+
{ name: "chainId", type: "uint256" },
|
|
221
|
+
{ name: "verifyingContract", type: "address" },
|
|
222
|
+
],
|
|
223
|
+
Order: KYBER_ORDER_FIELDS.map(([name, type]) => ({ name, type })),
|
|
224
|
+
},
|
|
225
|
+
primaryType: "Order",
|
|
226
|
+
message: {
|
|
227
|
+
salt: String(order.salt),
|
|
228
|
+
makerAsset: order.makerAsset,
|
|
229
|
+
takerAsset: order.takerAsset,
|
|
230
|
+
maker: order.maker,
|
|
231
|
+
receiver: order.receiver,
|
|
232
|
+
allowedSender: order.allowedSender,
|
|
233
|
+
makingAmount: String(order.makingAmount),
|
|
234
|
+
takingAmount: String(order.takingAmount),
|
|
235
|
+
feeConfig: String(order.feeConfig),
|
|
236
|
+
makerAssetData: order.makerAssetData,
|
|
237
|
+
takerAssetData: order.takerAssetData,
|
|
238
|
+
getMakerAmount: order.getMakerAmount,
|
|
239
|
+
getTakerAmount: order.getTakerAmount,
|
|
240
|
+
predicate: order.predicate,
|
|
241
|
+
interaction: order.interaction,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// SIGN THE DIGEST THROUGH THE REPOSITORY'S EXISTING SIGNER: signQuoteDigest signs any 32-byte digest and returns
|
|
246
|
+
// r || s || v with v in {27, 28}. No second signer is written here.
|
|
247
|
+
export const signKyberOrderDigest = ({ digest, privateKey }) =>
|
|
248
|
+
signQuoteDigest({ crypto: { secp256k1 }, digest, privateKey });
|
|
249
|
+
|
|
250
|
+
// Recover the signer of a digest from a signature (the same rule the CoW check uses): lowercase 0x address, or null
|
|
251
|
+
// when the signature is not usable. The caller turns null into a NAMED refusal, never into consent.
|
|
252
|
+
export const recoverKyberOrderSigner = ({ digest, signature }) => {
|
|
253
|
+
try {
|
|
254
|
+
const sig = hexToBytes(String(signature).replace(/^0x/, ""));
|
|
255
|
+
if (sig.length !== 65) return null;
|
|
256
|
+
let v = sig[64];
|
|
257
|
+
if (v >= 27) v -= 27;
|
|
258
|
+
if (v !== 0 && v !== 1) return null;
|
|
259
|
+
const pub = secp256k1.Signature.fromBytes(concat(sig.slice(0, 32), sig.slice(32, 64)))
|
|
260
|
+
.addRecoveryBit(v).recoverPublicKey(digest);
|
|
261
|
+
return bytesToHex(keccak256(pub.toBytes(false).slice(1)).slice(12));
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
};
|