@nerochain/mpc-sdk 0.1.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/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/aa.d.mts +185 -0
- package/dist/aa.d.ts +185 -0
- package/dist/aa.js +520 -0
- package/dist/aa.js.map +1 -0
- package/dist/aa.mjs +511 -0
- package/dist/aa.mjs.map +1 -0
- package/dist/chain-manager-C3eHsVt9.d.mts +98 -0
- package/dist/chain-manager-C3eHsVt9.d.ts +98 -0
- package/dist/chains.d.mts +17 -0
- package/dist/chains.d.ts +17 -0
- package/dist/chains.js +331 -0
- package/dist/chains.js.map +1 -0
- package/dist/chains.mjs +315 -0
- package/dist/chains.mjs.map +1 -0
- package/dist/index.d.mts +656 -0
- package/dist/index.d.ts +656 -0
- package/dist/index.js +6627 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6502 -0
- package/dist/index.mjs.map +1 -0
- package/dist/modal.d.mts +68 -0
- package/dist/modal.d.ts +68 -0
- package/dist/modal.js +4867 -0
- package/dist/modal.js.map +1 -0
- package/dist/modal.mjs +4850 -0
- package/dist/modal.mjs.map +1 -0
- package/dist/nero-sdk-Cm8gzHZJ.d.mts +684 -0
- package/dist/nero-sdk-IhuTBrXZ.d.ts +684 -0
- package/dist/no-modal.d.mts +56 -0
- package/dist/no-modal.d.ts +56 -0
- package/dist/no-modal.js +4060 -0
- package/dist/no-modal.js.map +1 -0
- package/dist/no-modal.mjs +4041 -0
- package/dist/no-modal.mjs.map +1 -0
- package/dist/react.d.mts +28 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +4033 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +4016 -0
- package/dist/react.mjs.map +1 -0
- package/dist/useNeroWallet-PZh940vV.d.ts +164 -0
- package/dist/useNeroWallet-awIYqM6e.d.mts +164 -0
- package/package.json +126 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,4033 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
4
|
+
var utils = require('@noble/hashes/utils');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var secp256k1 = require('@noble/curves/secp256k1');
|
|
7
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
13
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
14
|
+
}) : x)(function(x) {
|
|
15
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
16
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
17
|
+
});
|
|
18
|
+
var __esm = (fn, res) => function __init() {
|
|
19
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/core/crypto-primitives.ts
|
|
27
|
+
var crypto_primitives_exports = {};
|
|
28
|
+
__export(crypto_primitives_exports, {
|
|
29
|
+
bytesToHex: () => utils.bytesToHex,
|
|
30
|
+
computeCommitment: () => computeCommitment,
|
|
31
|
+
decryptWithKey: () => decryptWithKey,
|
|
32
|
+
decryptWithPassword: () => decryptWithPassword,
|
|
33
|
+
deriveKeyFromDeviceInfo: () => deriveKeyFromDeviceInfo,
|
|
34
|
+
deriveKeyFromPassword: () => deriveKeyFromPassword,
|
|
35
|
+
encryptWithKey: () => encryptWithKey,
|
|
36
|
+
encryptWithPassword: () => encryptWithPassword,
|
|
37
|
+
generateRandomBytes: () => generateRandomBytes,
|
|
38
|
+
generateRandomHex: () => generateRandomHex,
|
|
39
|
+
hashSha256: () => hashSha256,
|
|
40
|
+
hexToBytes: () => utils.hexToBytes,
|
|
41
|
+
utf8ToBytes: () => utils.utf8ToBytes
|
|
42
|
+
});
|
|
43
|
+
function getSubtleCrypto() {
|
|
44
|
+
if (typeof globalThis.crypto?.subtle !== "undefined") {
|
|
45
|
+
return globalThis.crypto.subtle;
|
|
46
|
+
}
|
|
47
|
+
throw new Error("WebCrypto API not available in this environment");
|
|
48
|
+
}
|
|
49
|
+
function getRandomValues(length) {
|
|
50
|
+
const buffer = new Uint8Array(length);
|
|
51
|
+
if (typeof globalThis.crypto?.getRandomValues !== "undefined") {
|
|
52
|
+
globalThis.crypto.getRandomValues(buffer);
|
|
53
|
+
return buffer;
|
|
54
|
+
}
|
|
55
|
+
throw new Error("Secure random not available in this environment");
|
|
56
|
+
}
|
|
57
|
+
function toBuffer(data) {
|
|
58
|
+
return data.buffer.slice(
|
|
59
|
+
data.byteOffset,
|
|
60
|
+
data.byteOffset + data.byteLength
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
async function deriveKeyFromPassword(password, salt) {
|
|
64
|
+
const subtle = getSubtleCrypto();
|
|
65
|
+
const passwordKey = await subtle.importKey(
|
|
66
|
+
"raw",
|
|
67
|
+
toBuffer(utils.utf8ToBytes(password)),
|
|
68
|
+
"PBKDF2",
|
|
69
|
+
false,
|
|
70
|
+
["deriveKey"]
|
|
71
|
+
);
|
|
72
|
+
return subtle.deriveKey(
|
|
73
|
+
{
|
|
74
|
+
name: "PBKDF2",
|
|
75
|
+
salt: toBuffer(salt),
|
|
76
|
+
iterations: PBKDF2_ITERATIONS,
|
|
77
|
+
hash: "SHA-256"
|
|
78
|
+
},
|
|
79
|
+
passwordKey,
|
|
80
|
+
{ name: "AES-GCM", length: KEY_LENGTH },
|
|
81
|
+
false,
|
|
82
|
+
["encrypt", "decrypt"]
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
async function deriveKeyFromDeviceInfo(deviceId, userId) {
|
|
86
|
+
const combined = `${deviceId}:${userId}`;
|
|
87
|
+
const hash = sha256.sha256(utils.utf8ToBytes(combined));
|
|
88
|
+
const subtle = getSubtleCrypto();
|
|
89
|
+
return subtle.importKey(
|
|
90
|
+
"raw",
|
|
91
|
+
toBuffer(hash),
|
|
92
|
+
{ name: "AES-GCM", length: KEY_LENGTH },
|
|
93
|
+
false,
|
|
94
|
+
["encrypt", "decrypt"]
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
async function encryptWithPassword(plaintext, password) {
|
|
98
|
+
const subtle = getSubtleCrypto();
|
|
99
|
+
const salt = getRandomValues(SALT_LENGTH);
|
|
100
|
+
const iv = getRandomValues(IV_LENGTH);
|
|
101
|
+
const key = await deriveKeyFromPassword(password, salt);
|
|
102
|
+
const ciphertext = await subtle.encrypt(
|
|
103
|
+
{ name: "AES-GCM", iv: toBuffer(iv) },
|
|
104
|
+
key,
|
|
105
|
+
toBuffer(utils.utf8ToBytes(plaintext))
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
ciphertext: utils.bytesToHex(new Uint8Array(ciphertext)),
|
|
109
|
+
iv: utils.bytesToHex(iv),
|
|
110
|
+
salt: utils.bytesToHex(salt)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function decryptWithPassword(encrypted, password) {
|
|
114
|
+
const subtle = getSubtleCrypto();
|
|
115
|
+
const salt = utils.hexToBytes(encrypted.salt);
|
|
116
|
+
const iv = utils.hexToBytes(encrypted.iv);
|
|
117
|
+
const ciphertext = utils.hexToBytes(encrypted.ciphertext);
|
|
118
|
+
const key = await deriveKeyFromPassword(password, salt);
|
|
119
|
+
const plaintext = await subtle.decrypt(
|
|
120
|
+
{ name: "AES-GCM", iv: toBuffer(iv) },
|
|
121
|
+
key,
|
|
122
|
+
toBuffer(ciphertext)
|
|
123
|
+
);
|
|
124
|
+
return new TextDecoder().decode(plaintext);
|
|
125
|
+
}
|
|
126
|
+
async function encryptWithKey(plaintext, key) {
|
|
127
|
+
const subtle = getSubtleCrypto();
|
|
128
|
+
const iv = getRandomValues(IV_LENGTH);
|
|
129
|
+
const ciphertext = await subtle.encrypt(
|
|
130
|
+
{ name: "AES-GCM", iv: toBuffer(iv) },
|
|
131
|
+
key,
|
|
132
|
+
toBuffer(utils.utf8ToBytes(plaintext))
|
|
133
|
+
);
|
|
134
|
+
return {
|
|
135
|
+
ciphertext: utils.bytesToHex(new Uint8Array(ciphertext)),
|
|
136
|
+
iv: utils.bytesToHex(iv)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function decryptWithKey(ciphertext, iv, key) {
|
|
140
|
+
const subtle = getSubtleCrypto();
|
|
141
|
+
const plaintext = await subtle.decrypt(
|
|
142
|
+
{ name: "AES-GCM", iv: toBuffer(utils.hexToBytes(iv)) },
|
|
143
|
+
key,
|
|
144
|
+
toBuffer(utils.hexToBytes(ciphertext))
|
|
145
|
+
);
|
|
146
|
+
return new TextDecoder().decode(plaintext);
|
|
147
|
+
}
|
|
148
|
+
function generateRandomBytes(length) {
|
|
149
|
+
return getRandomValues(length);
|
|
150
|
+
}
|
|
151
|
+
function generateRandomHex(length) {
|
|
152
|
+
return utils.bytesToHex(getRandomValues(length));
|
|
153
|
+
}
|
|
154
|
+
function hashSha256(data) {
|
|
155
|
+
const input = typeof data === "string" ? utils.utf8ToBytes(data) : data;
|
|
156
|
+
return utils.bytesToHex(sha256.sha256(input));
|
|
157
|
+
}
|
|
158
|
+
function computeCommitment(value, blinding) {
|
|
159
|
+
const combined = `${value}:${blinding}`;
|
|
160
|
+
return hashSha256(combined);
|
|
161
|
+
}
|
|
162
|
+
var PBKDF2_ITERATIONS, SALT_LENGTH, IV_LENGTH, KEY_LENGTH;
|
|
163
|
+
var init_crypto_primitives = __esm({
|
|
164
|
+
"src/core/crypto-primitives.ts"() {
|
|
165
|
+
PBKDF2_ITERATIONS = 1e5;
|
|
166
|
+
SALT_LENGTH = 16;
|
|
167
|
+
IV_LENGTH = 12;
|
|
168
|
+
KEY_LENGTH = 256;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/chains/configs.ts
|
|
173
|
+
var NERO_TESTNET = {
|
|
174
|
+
chainId: 689,
|
|
175
|
+
chainName: "nero-testnet",
|
|
176
|
+
displayName: "NERO Testnet",
|
|
177
|
+
nativeCurrency: {
|
|
178
|
+
name: "NERO",
|
|
179
|
+
symbol: "NERO",
|
|
180
|
+
decimals: 18
|
|
181
|
+
},
|
|
182
|
+
rpcUrls: ["https://testnet.nerochain.io"],
|
|
183
|
+
blockExplorerUrls: ["https://testnetscan.nerochain.io"],
|
|
184
|
+
isTestnet: true,
|
|
185
|
+
bundlerUrl: "https://bundler.testnet.nerochain.io",
|
|
186
|
+
paymasterUrl: "https://paymaster.testnet.nerochain.io",
|
|
187
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
188
|
+
simpleAccountFactoryAddress: "0x9406Cc6185a346906296840746125a0E44976454"
|
|
189
|
+
};
|
|
190
|
+
var NERO_MAINNET = {
|
|
191
|
+
chainId: 1689,
|
|
192
|
+
chainName: "nero-mainnet",
|
|
193
|
+
displayName: "NERO Mainnet",
|
|
194
|
+
nativeCurrency: {
|
|
195
|
+
name: "NERO",
|
|
196
|
+
symbol: "NERO",
|
|
197
|
+
decimals: 18
|
|
198
|
+
},
|
|
199
|
+
rpcUrls: ["https://rpc.nerochain.io"],
|
|
200
|
+
blockExplorerUrls: ["https://scan.nerochain.io"],
|
|
201
|
+
isTestnet: false,
|
|
202
|
+
bundlerUrl: "https://bundler.nerochain.io",
|
|
203
|
+
paymasterUrl: "https://paymaster.nerochain.io",
|
|
204
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
205
|
+
simpleAccountFactoryAddress: "0x9406Cc6185a346906296840746125a0E44976454"
|
|
206
|
+
};
|
|
207
|
+
var ETHEREUM_MAINNET = {
|
|
208
|
+
chainId: 1,
|
|
209
|
+
chainName: "ethereum",
|
|
210
|
+
displayName: "Ethereum",
|
|
211
|
+
nativeCurrency: {
|
|
212
|
+
name: "Ether",
|
|
213
|
+
symbol: "ETH",
|
|
214
|
+
decimals: 18
|
|
215
|
+
},
|
|
216
|
+
rpcUrls: ["https://eth.llamarpc.com", "https://rpc.ankr.com/eth"],
|
|
217
|
+
blockExplorerUrls: ["https://etherscan.io"],
|
|
218
|
+
isTestnet: false,
|
|
219
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
220
|
+
};
|
|
221
|
+
var ETHEREUM_SEPOLIA = {
|
|
222
|
+
chainId: 11155111,
|
|
223
|
+
chainName: "sepolia",
|
|
224
|
+
displayName: "Sepolia Testnet",
|
|
225
|
+
nativeCurrency: {
|
|
226
|
+
name: "Sepolia Ether",
|
|
227
|
+
symbol: "ETH",
|
|
228
|
+
decimals: 18
|
|
229
|
+
},
|
|
230
|
+
rpcUrls: ["https://rpc.sepolia.org", "https://rpc.ankr.com/eth_sepolia"],
|
|
231
|
+
blockExplorerUrls: ["https://sepolia.etherscan.io"],
|
|
232
|
+
isTestnet: true,
|
|
233
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
234
|
+
};
|
|
235
|
+
var POLYGON_MAINNET = {
|
|
236
|
+
chainId: 137,
|
|
237
|
+
chainName: "polygon",
|
|
238
|
+
displayName: "Polygon",
|
|
239
|
+
nativeCurrency: {
|
|
240
|
+
name: "MATIC",
|
|
241
|
+
symbol: "MATIC",
|
|
242
|
+
decimals: 18
|
|
243
|
+
},
|
|
244
|
+
rpcUrls: ["https://polygon-rpc.com", "https://rpc.ankr.com/polygon"],
|
|
245
|
+
blockExplorerUrls: ["https://polygonscan.com"],
|
|
246
|
+
isTestnet: false,
|
|
247
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
248
|
+
};
|
|
249
|
+
var ARBITRUM_ONE = {
|
|
250
|
+
chainId: 42161,
|
|
251
|
+
chainName: "arbitrum",
|
|
252
|
+
displayName: "Arbitrum One",
|
|
253
|
+
nativeCurrency: {
|
|
254
|
+
name: "Ether",
|
|
255
|
+
symbol: "ETH",
|
|
256
|
+
decimals: 18
|
|
257
|
+
},
|
|
258
|
+
rpcUrls: ["https://arb1.arbitrum.io/rpc", "https://rpc.ankr.com/arbitrum"],
|
|
259
|
+
blockExplorerUrls: ["https://arbiscan.io"],
|
|
260
|
+
isTestnet: false,
|
|
261
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
262
|
+
};
|
|
263
|
+
var BASE_MAINNET = {
|
|
264
|
+
chainId: 8453,
|
|
265
|
+
chainName: "base",
|
|
266
|
+
displayName: "Base",
|
|
267
|
+
nativeCurrency: {
|
|
268
|
+
name: "Ether",
|
|
269
|
+
symbol: "ETH",
|
|
270
|
+
decimals: 18
|
|
271
|
+
},
|
|
272
|
+
rpcUrls: ["https://mainnet.base.org", "https://base.llamarpc.com"],
|
|
273
|
+
blockExplorerUrls: ["https://basescan.org"],
|
|
274
|
+
isTestnet: false,
|
|
275
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
|
|
276
|
+
};
|
|
277
|
+
var BUILTIN_CHAINS = /* @__PURE__ */ new Map([
|
|
278
|
+
[NERO_TESTNET.chainId, NERO_TESTNET],
|
|
279
|
+
[NERO_MAINNET.chainId, NERO_MAINNET],
|
|
280
|
+
[ETHEREUM_MAINNET.chainId, ETHEREUM_MAINNET],
|
|
281
|
+
[ETHEREUM_SEPOLIA.chainId, ETHEREUM_SEPOLIA],
|
|
282
|
+
[POLYGON_MAINNET.chainId, POLYGON_MAINNET],
|
|
283
|
+
[ARBITRUM_ONE.chainId, ARBITRUM_ONE],
|
|
284
|
+
[BASE_MAINNET.chainId, BASE_MAINNET]
|
|
285
|
+
]);
|
|
286
|
+
function getChainConfig(chainId) {
|
|
287
|
+
return BUILTIN_CHAINS.get(chainId);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/chains/chain-manager.ts
|
|
291
|
+
var ChainManager = class {
|
|
292
|
+
constructor(initialChainId = 689) {
|
|
293
|
+
this.customChains = /* @__PURE__ */ new Map();
|
|
294
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
295
|
+
this.rpcConnections = /* @__PURE__ */ new Map();
|
|
296
|
+
this.currentChainId = initialChainId;
|
|
297
|
+
}
|
|
298
|
+
get chainId() {
|
|
299
|
+
return this.currentChainId;
|
|
300
|
+
}
|
|
301
|
+
get chainConfig() {
|
|
302
|
+
return this.getConfig(this.currentChainId);
|
|
303
|
+
}
|
|
304
|
+
getConfig(chainId) {
|
|
305
|
+
return getChainConfig(chainId) ?? this.customChains.get(chainId);
|
|
306
|
+
}
|
|
307
|
+
getSupportedChains() {
|
|
308
|
+
const builtins = Array.from(BUILTIN_CHAINS.values());
|
|
309
|
+
const custom = Array.from(this.customChains.values());
|
|
310
|
+
return [...builtins, ...custom];
|
|
311
|
+
}
|
|
312
|
+
getSupportedChainIds() {
|
|
313
|
+
return this.getSupportedChains().map((c) => c.chainId);
|
|
314
|
+
}
|
|
315
|
+
isChainSupported(chainId) {
|
|
316
|
+
return this.getConfig(chainId) !== void 0;
|
|
317
|
+
}
|
|
318
|
+
addChain(config) {
|
|
319
|
+
this.customChains.set(config.chainId, config);
|
|
320
|
+
}
|
|
321
|
+
removeChain(chainId) {
|
|
322
|
+
if (BUILTIN_CHAINS.has(chainId)) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return this.customChains.delete(chainId);
|
|
326
|
+
}
|
|
327
|
+
async switchChain(chainId) {
|
|
328
|
+
const config = this.getConfig(chainId);
|
|
329
|
+
if (!config) {
|
|
330
|
+
throw new Error(`Chain ${chainId} is not supported`);
|
|
331
|
+
}
|
|
332
|
+
const previousChainId = this.currentChainId;
|
|
333
|
+
this.currentChainId = chainId;
|
|
334
|
+
if (previousChainId !== chainId) {
|
|
335
|
+
this.notifyListeners(chainId, config);
|
|
336
|
+
}
|
|
337
|
+
return config;
|
|
338
|
+
}
|
|
339
|
+
onChainChange(listener) {
|
|
340
|
+
this.listeners.add(listener);
|
|
341
|
+
return () => this.listeners.delete(listener);
|
|
342
|
+
}
|
|
343
|
+
notifyListeners(chainId, config) {
|
|
344
|
+
for (const listener of this.listeners) {
|
|
345
|
+
try {
|
|
346
|
+
listener(chainId, config);
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
getRpcConnection(chainId) {
|
|
352
|
+
const targetChainId = chainId ?? this.currentChainId;
|
|
353
|
+
let connection = this.rpcConnections.get(targetChainId);
|
|
354
|
+
if (!connection) {
|
|
355
|
+
const config = this.getConfig(targetChainId);
|
|
356
|
+
if (!config) {
|
|
357
|
+
throw new Error(`Chain ${targetChainId} is not supported`);
|
|
358
|
+
}
|
|
359
|
+
connection = new RpcConnection(config);
|
|
360
|
+
this.rpcConnections.set(targetChainId, connection);
|
|
361
|
+
}
|
|
362
|
+
return connection;
|
|
363
|
+
}
|
|
364
|
+
getTestnets() {
|
|
365
|
+
return this.getSupportedChains().filter((c) => c.isTestnet);
|
|
366
|
+
}
|
|
367
|
+
getMainnets() {
|
|
368
|
+
return this.getSupportedChains().filter((c) => !c.isTestnet);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
var RpcConnection = class {
|
|
372
|
+
constructor(config) {
|
|
373
|
+
this.currentRpcIndex = 0;
|
|
374
|
+
this.config = config;
|
|
375
|
+
}
|
|
376
|
+
get chainId() {
|
|
377
|
+
return this.config.chainId;
|
|
378
|
+
}
|
|
379
|
+
get rpcUrl() {
|
|
380
|
+
return this.config.rpcUrls[this.currentRpcIndex];
|
|
381
|
+
}
|
|
382
|
+
async call(method, params = []) {
|
|
383
|
+
const maxRetries = this.config.rpcUrls.length;
|
|
384
|
+
let lastError = null;
|
|
385
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
386
|
+
try {
|
|
387
|
+
return await this.executeCall(method, params);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
lastError = error;
|
|
390
|
+
this.rotateRpc();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
throw lastError ?? new Error("RPC call failed");
|
|
394
|
+
}
|
|
395
|
+
async executeCall(method, params) {
|
|
396
|
+
const response = await fetch(this.rpcUrl, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "Content-Type": "application/json" },
|
|
399
|
+
body: JSON.stringify({
|
|
400
|
+
jsonrpc: "2.0",
|
|
401
|
+
id: Date.now(),
|
|
402
|
+
method,
|
|
403
|
+
params
|
|
404
|
+
})
|
|
405
|
+
});
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
throw new Error(`RPC request failed: ${response.status}`);
|
|
408
|
+
}
|
|
409
|
+
const data = await response.json();
|
|
410
|
+
if (data.error) {
|
|
411
|
+
throw new Error(data.error.message ?? "RPC error");
|
|
412
|
+
}
|
|
413
|
+
return data.result;
|
|
414
|
+
}
|
|
415
|
+
rotateRpc() {
|
|
416
|
+
this.currentRpcIndex = (this.currentRpcIndex + 1) % this.config.rpcUrls.length;
|
|
417
|
+
}
|
|
418
|
+
async getBlockNumber() {
|
|
419
|
+
const result = await this.call("eth_blockNumber");
|
|
420
|
+
return BigInt(result);
|
|
421
|
+
}
|
|
422
|
+
async getBalance(address) {
|
|
423
|
+
const result = await this.call("eth_getBalance", [
|
|
424
|
+
address,
|
|
425
|
+
"latest"
|
|
426
|
+
]);
|
|
427
|
+
return BigInt(result);
|
|
428
|
+
}
|
|
429
|
+
async getTransactionCount(address) {
|
|
430
|
+
const result = await this.call("eth_getTransactionCount", [
|
|
431
|
+
address,
|
|
432
|
+
"latest"
|
|
433
|
+
]);
|
|
434
|
+
return BigInt(result);
|
|
435
|
+
}
|
|
436
|
+
async getGasPrice() {
|
|
437
|
+
const result = await this.call("eth_gasPrice");
|
|
438
|
+
return BigInt(result);
|
|
439
|
+
}
|
|
440
|
+
async estimateGas(tx) {
|
|
441
|
+
const result = await this.call("eth_estimateGas", [tx]);
|
|
442
|
+
return BigInt(result);
|
|
443
|
+
}
|
|
444
|
+
async getCode(address) {
|
|
445
|
+
return this.call("eth_getCode", [address, "latest"]);
|
|
446
|
+
}
|
|
447
|
+
async sendRawTransaction(signedTx) {
|
|
448
|
+
return this.call("eth_sendRawTransaction", [signedTx]);
|
|
449
|
+
}
|
|
450
|
+
async getTransactionReceipt(hash) {
|
|
451
|
+
return this.call("eth_getTransactionReceipt", [
|
|
452
|
+
hash
|
|
453
|
+
]);
|
|
454
|
+
}
|
|
455
|
+
async waitForTransaction(hash, confirmations = 1, timeout = 6e4) {
|
|
456
|
+
const startTime = Date.now();
|
|
457
|
+
while (Date.now() - startTime < timeout) {
|
|
458
|
+
const receipt = await this.getTransactionReceipt(hash);
|
|
459
|
+
if (receipt?.blockNumber) {
|
|
460
|
+
const currentBlock = await this.getBlockNumber();
|
|
461
|
+
const txBlock = BigInt(receipt.blockNumber);
|
|
462
|
+
if (currentBlock - txBlock >= BigInt(confirmations - 1)) {
|
|
463
|
+
return receipt;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
467
|
+
}
|
|
468
|
+
throw new Error(`Transaction ${hash} not confirmed within timeout`);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/types/index.ts
|
|
473
|
+
var SDKError = class extends Error {
|
|
474
|
+
constructor(message, code, statusCode) {
|
|
475
|
+
super(message);
|
|
476
|
+
this.code = code;
|
|
477
|
+
this.statusCode = statusCode;
|
|
478
|
+
this.name = "SDKError";
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// src/core/client-key-manager.ts
|
|
483
|
+
init_crypto_primitives();
|
|
484
|
+
|
|
485
|
+
// src/core/secure-storage.ts
|
|
486
|
+
init_crypto_primitives();
|
|
487
|
+
var DB_NAME = "nero-mpc-sdk";
|
|
488
|
+
var DB_VERSION = 1;
|
|
489
|
+
var STORE_NAME = "encrypted-data";
|
|
490
|
+
function openDatabase() {
|
|
491
|
+
return new Promise((resolve, reject) => {
|
|
492
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
493
|
+
request.onerror = () => {
|
|
494
|
+
reject(new Error(`Failed to open IndexedDB: ${request.error?.message}`));
|
|
495
|
+
};
|
|
496
|
+
request.onsuccess = () => {
|
|
497
|
+
resolve(request.result);
|
|
498
|
+
};
|
|
499
|
+
request.onupgradeneeded = (event) => {
|
|
500
|
+
const db = event.target.result;
|
|
501
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
502
|
+
db.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
var IndexedDBStorage = class {
|
|
508
|
+
constructor(prefix = "nero") {
|
|
509
|
+
this.prefix = prefix;
|
|
510
|
+
}
|
|
511
|
+
prefixKey(key) {
|
|
512
|
+
return `${this.prefix}:${key}`;
|
|
513
|
+
}
|
|
514
|
+
async get(key) {
|
|
515
|
+
const db = await openDatabase();
|
|
516
|
+
return new Promise((resolve, reject) => {
|
|
517
|
+
const transaction = db.transaction(STORE_NAME, "readonly");
|
|
518
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
519
|
+
const request = store.get(this.prefixKey(key));
|
|
520
|
+
request.onerror = () => reject(request.error);
|
|
521
|
+
request.onsuccess = () => {
|
|
522
|
+
const item = request.result;
|
|
523
|
+
resolve(item?.value ?? null);
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async set(key, value) {
|
|
528
|
+
const db = await openDatabase();
|
|
529
|
+
return new Promise((resolve, reject) => {
|
|
530
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
531
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
532
|
+
const now = Date.now();
|
|
533
|
+
const item = {
|
|
534
|
+
key: this.prefixKey(key),
|
|
535
|
+
value,
|
|
536
|
+
createdAt: now,
|
|
537
|
+
updatedAt: now
|
|
538
|
+
};
|
|
539
|
+
const request = store.put(item);
|
|
540
|
+
request.onerror = () => reject(request.error);
|
|
541
|
+
request.onsuccess = () => resolve();
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
async delete(key) {
|
|
545
|
+
const db = await openDatabase();
|
|
546
|
+
return new Promise((resolve, reject) => {
|
|
547
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
548
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
549
|
+
const request = store.delete(this.prefixKey(key));
|
|
550
|
+
request.onerror = () => reject(request.error);
|
|
551
|
+
request.onsuccess = () => resolve();
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
async clear() {
|
|
555
|
+
const db = await openDatabase();
|
|
556
|
+
return new Promise((resolve, reject) => {
|
|
557
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
558
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
559
|
+
const cursorRequest = store.openCursor();
|
|
560
|
+
cursorRequest.onerror = () => reject(cursorRequest.error);
|
|
561
|
+
cursorRequest.onsuccess = (event) => {
|
|
562
|
+
const cursor = event.target.result;
|
|
563
|
+
if (cursor) {
|
|
564
|
+
const key = cursor.key;
|
|
565
|
+
if (key.startsWith(`${this.prefix}:`)) {
|
|
566
|
+
cursor.delete();
|
|
567
|
+
}
|
|
568
|
+
cursor.continue();
|
|
569
|
+
} else {
|
|
570
|
+
resolve();
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
var MemoryStorage = class {
|
|
577
|
+
constructor(prefix = "nero") {
|
|
578
|
+
this.store = /* @__PURE__ */ new Map();
|
|
579
|
+
this.prefix = prefix;
|
|
580
|
+
}
|
|
581
|
+
prefixKey(key) {
|
|
582
|
+
return `${this.prefix}:${key}`;
|
|
583
|
+
}
|
|
584
|
+
async get(key) {
|
|
585
|
+
return this.store.get(this.prefixKey(key)) ?? null;
|
|
586
|
+
}
|
|
587
|
+
async set(key, value) {
|
|
588
|
+
this.store.set(this.prefixKey(key), value);
|
|
589
|
+
}
|
|
590
|
+
async delete(key) {
|
|
591
|
+
this.store.delete(this.prefixKey(key));
|
|
592
|
+
}
|
|
593
|
+
async clear() {
|
|
594
|
+
const keysToDelete = [];
|
|
595
|
+
for (const key of this.store.keys()) {
|
|
596
|
+
if (key.startsWith(`${this.prefix}:`)) {
|
|
597
|
+
keysToDelete.push(key);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (const key of keysToDelete) {
|
|
601
|
+
this.store.delete(key);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
var ENCRYPTION_VERSION = 1;
|
|
606
|
+
var SecureKeyStorage = class {
|
|
607
|
+
constructor(storage, deviceKey) {
|
|
608
|
+
this.storage = storage;
|
|
609
|
+
this.deviceKey = deviceKey;
|
|
610
|
+
}
|
|
611
|
+
getStorageKey(userId) {
|
|
612
|
+
return `keyshare:${hashSha256(userId)}`;
|
|
613
|
+
}
|
|
614
|
+
getPartySharesKey(userId) {
|
|
615
|
+
return `partyshares:${hashSha256(userId)}`;
|
|
616
|
+
}
|
|
617
|
+
async storeKeyShare(userId, keyShare) {
|
|
618
|
+
const plaintext = JSON.stringify(keyShare);
|
|
619
|
+
const encrypted = await encryptWithPassword(plaintext, this.deviceKey);
|
|
620
|
+
const storedData = {
|
|
621
|
+
ciphertext: encrypted.ciphertext,
|
|
622
|
+
iv: encrypted.iv,
|
|
623
|
+
salt: encrypted.salt,
|
|
624
|
+
version: ENCRYPTION_VERSION
|
|
625
|
+
};
|
|
626
|
+
await this.storage.set(
|
|
627
|
+
this.getStorageKey(userId),
|
|
628
|
+
JSON.stringify(storedData)
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
async getKeyShare(userId) {
|
|
632
|
+
const stored = await this.storage.get(this.getStorageKey(userId));
|
|
633
|
+
if (!stored) {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
const encryptedData = JSON.parse(stored);
|
|
637
|
+
if (encryptedData.version !== ENCRYPTION_VERSION) {
|
|
638
|
+
throw new Error(
|
|
639
|
+
`Unsupported encryption version: ${encryptedData.version}`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const encrypted = {
|
|
643
|
+
ciphertext: encryptedData.ciphertext,
|
|
644
|
+
iv: encryptedData.iv,
|
|
645
|
+
salt: encryptedData.salt
|
|
646
|
+
};
|
|
647
|
+
const plaintext = await decryptWithPassword(encrypted, this.deviceKey);
|
|
648
|
+
return JSON.parse(plaintext);
|
|
649
|
+
}
|
|
650
|
+
async hasKeyShare(userId) {
|
|
651
|
+
const stored = await this.storage.get(this.getStorageKey(userId));
|
|
652
|
+
return stored !== null;
|
|
653
|
+
}
|
|
654
|
+
async deleteKeyShare(userId) {
|
|
655
|
+
await this.storage.delete(this.getStorageKey(userId));
|
|
656
|
+
}
|
|
657
|
+
async storePartyPublicShares(userId, shares) {
|
|
658
|
+
const sharesArray = Array.from(shares.entries());
|
|
659
|
+
const plaintext = JSON.stringify(sharesArray);
|
|
660
|
+
const encrypted = await encryptWithPassword(plaintext, this.deviceKey);
|
|
661
|
+
const storedData = {
|
|
662
|
+
ciphertext: encrypted.ciphertext,
|
|
663
|
+
iv: encrypted.iv,
|
|
664
|
+
salt: encrypted.salt,
|
|
665
|
+
version: ENCRYPTION_VERSION
|
|
666
|
+
};
|
|
667
|
+
await this.storage.set(
|
|
668
|
+
this.getPartySharesKey(userId),
|
|
669
|
+
JSON.stringify(storedData)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
async getPartyPublicShares(userId) {
|
|
673
|
+
const stored = await this.storage.get(this.getPartySharesKey(userId));
|
|
674
|
+
if (!stored) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
const encryptedData = JSON.parse(stored);
|
|
678
|
+
if (encryptedData.version !== ENCRYPTION_VERSION) {
|
|
679
|
+
throw new Error(
|
|
680
|
+
`Unsupported encryption version: ${encryptedData.version}`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
const encrypted = {
|
|
684
|
+
ciphertext: encryptedData.ciphertext,
|
|
685
|
+
iv: encryptedData.iv,
|
|
686
|
+
salt: encryptedData.salt
|
|
687
|
+
};
|
|
688
|
+
const plaintext = await decryptWithPassword(encrypted, this.deviceKey);
|
|
689
|
+
const sharesArray = JSON.parse(plaintext);
|
|
690
|
+
return new Map(sharesArray);
|
|
691
|
+
}
|
|
692
|
+
async deletePartyPublicShares(userId) {
|
|
693
|
+
await this.storage.delete(this.getPartySharesKey(userId));
|
|
694
|
+
}
|
|
695
|
+
async clearAll() {
|
|
696
|
+
await this.storage.clear();
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
function createSecureStorage(deviceKey, prefix = "nero") {
|
|
700
|
+
const isIndexedDBAvailable = typeof indexedDB !== "undefined" && indexedDB !== null;
|
|
701
|
+
const adapter = isIndexedDBAvailable ? new IndexedDBStorage(prefix) : new MemoryStorage(prefix);
|
|
702
|
+
return new SecureKeyStorage(adapter, deviceKey);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/core/client-key-manager.ts
|
|
706
|
+
var ClientKeyManager = class {
|
|
707
|
+
constructor(deviceKey, config = {}) {
|
|
708
|
+
this.currentUserId = null;
|
|
709
|
+
this.cachedKeyShare = null;
|
|
710
|
+
this.cachedPartyShares = null;
|
|
711
|
+
this.storage = createSecureStorage(
|
|
712
|
+
deviceKey,
|
|
713
|
+
config.storagePrefix ?? "nero"
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
async initialize(userId) {
|
|
717
|
+
this.currentUserId = userId;
|
|
718
|
+
this.cachedKeyShare = await this.storage.getKeyShare(userId);
|
|
719
|
+
}
|
|
720
|
+
async hasKeyShare() {
|
|
721
|
+
if (!this.currentUserId) {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
return this.storage.hasKeyShare(this.currentUserId);
|
|
725
|
+
}
|
|
726
|
+
async getKeyShare() {
|
|
727
|
+
if (this.cachedKeyShare) {
|
|
728
|
+
return this.cachedKeyShare;
|
|
729
|
+
}
|
|
730
|
+
if (!this.currentUserId) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
this.cachedKeyShare = await this.storage.getKeyShare(this.currentUserId);
|
|
734
|
+
return this.cachedKeyShare;
|
|
735
|
+
}
|
|
736
|
+
async storeKeyShare(keyShare) {
|
|
737
|
+
if (!this.currentUserId) {
|
|
738
|
+
throw new SDKError("User not initialized", "USER_NOT_INITIALIZED");
|
|
739
|
+
}
|
|
740
|
+
await this.storage.storeKeyShare(this.currentUserId, keyShare);
|
|
741
|
+
this.cachedKeyShare = keyShare;
|
|
742
|
+
}
|
|
743
|
+
async getPartyPublicShares() {
|
|
744
|
+
if (this.cachedPartyShares) {
|
|
745
|
+
return this.cachedPartyShares;
|
|
746
|
+
}
|
|
747
|
+
if (!this.currentUserId) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
this.cachedPartyShares = await this.storage.getPartyPublicShares(
|
|
751
|
+
this.currentUserId
|
|
752
|
+
);
|
|
753
|
+
return this.cachedPartyShares;
|
|
754
|
+
}
|
|
755
|
+
async storePartyPublicShares(shares) {
|
|
756
|
+
if (!this.currentUserId) {
|
|
757
|
+
throw new SDKError("User not initialized", "USER_NOT_INITIALIZED");
|
|
758
|
+
}
|
|
759
|
+
await this.storage.storePartyPublicShares(this.currentUserId, shares);
|
|
760
|
+
this.cachedPartyShares = shares;
|
|
761
|
+
}
|
|
762
|
+
async deleteKeyShare() {
|
|
763
|
+
if (!this.currentUserId) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
await this.storage.deleteKeyShare(this.currentUserId);
|
|
767
|
+
this.cachedKeyShare = null;
|
|
768
|
+
}
|
|
769
|
+
async rotateKeyShare(newKeyShare) {
|
|
770
|
+
if (!this.currentUserId) {
|
|
771
|
+
throw new SDKError("User not initialized", "USER_NOT_INITIALIZED");
|
|
772
|
+
}
|
|
773
|
+
const existingShare = await this.getKeyShare();
|
|
774
|
+
if (!existingShare) {
|
|
775
|
+
throw new SDKError("No existing key share to rotate", "NO_KEY_SHARE");
|
|
776
|
+
}
|
|
777
|
+
if (existingShare.partyId !== newKeyShare.partyId) {
|
|
778
|
+
throw new SDKError(
|
|
779
|
+
"Party ID mismatch during rotation",
|
|
780
|
+
"PARTY_ID_MISMATCH"
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
await this.storeKeyShare(newKeyShare);
|
|
784
|
+
}
|
|
785
|
+
async exportBackup(password) {
|
|
786
|
+
const keyShare = await this.getKeyShare();
|
|
787
|
+
if (!keyShare) {
|
|
788
|
+
throw new SDKError("No key share to export", "NO_KEY_SHARE");
|
|
789
|
+
}
|
|
790
|
+
const { encryptWithPassword: encryptWithPassword2 } = await Promise.resolve().then(() => (init_crypto_primitives(), crypto_primitives_exports));
|
|
791
|
+
const encrypted = await encryptWithPassword2(
|
|
792
|
+
JSON.stringify(keyShare),
|
|
793
|
+
password
|
|
794
|
+
);
|
|
795
|
+
const backup = {
|
|
796
|
+
version: 1,
|
|
797
|
+
type: "nero-mpc-backup",
|
|
798
|
+
data: encrypted,
|
|
799
|
+
createdAt: Date.now()
|
|
800
|
+
};
|
|
801
|
+
return btoa(JSON.stringify(backup));
|
|
802
|
+
}
|
|
803
|
+
async importBackup(backupString, password) {
|
|
804
|
+
const backup = JSON.parse(atob(backupString));
|
|
805
|
+
if (backup.type !== "nero-mpc-backup") {
|
|
806
|
+
throw new SDKError("Invalid backup format", "INVALID_BACKUP");
|
|
807
|
+
}
|
|
808
|
+
if (backup.version !== 1) {
|
|
809
|
+
throw new SDKError(
|
|
810
|
+
`Unsupported backup version: ${backup.version}`,
|
|
811
|
+
"UNSUPPORTED_VERSION"
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const { decryptWithPassword: decryptWithPassword2 } = await Promise.resolve().then(() => (init_crypto_primitives(), crypto_primitives_exports));
|
|
815
|
+
const plaintext = await decryptWithPassword2(backup.data, password);
|
|
816
|
+
const keyShare = JSON.parse(plaintext);
|
|
817
|
+
return keyShare;
|
|
818
|
+
}
|
|
819
|
+
getWalletInfo(publicKey, chainId) {
|
|
820
|
+
const eoaAddress = this.deriveEOAAddress(publicKey);
|
|
821
|
+
return {
|
|
822
|
+
eoaAddress,
|
|
823
|
+
publicKey,
|
|
824
|
+
chainId
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
deriveEOAAddress(publicKey) {
|
|
828
|
+
let pubKeyBytes;
|
|
829
|
+
const pubKeyHex = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
|
|
830
|
+
if (pubKeyHex.length === 130) {
|
|
831
|
+
pubKeyBytes = hexToBytes2(pubKeyHex.slice(2));
|
|
832
|
+
} else if (pubKeyHex.length === 128) {
|
|
833
|
+
pubKeyBytes = hexToBytes2(pubKeyHex);
|
|
834
|
+
} else {
|
|
835
|
+
throw new SDKError("Invalid public key format", "INVALID_PUBLIC_KEY");
|
|
836
|
+
}
|
|
837
|
+
const hash = keccak256(pubKeyBytes);
|
|
838
|
+
const addressBytes = hash.slice(-20);
|
|
839
|
+
return `0x${bytesToHex2(addressBytes)}`;
|
|
840
|
+
}
|
|
841
|
+
async clear() {
|
|
842
|
+
await this.storage.clearAll();
|
|
843
|
+
this.currentUserId = null;
|
|
844
|
+
this.cachedKeyShare = null;
|
|
845
|
+
this.cachedPartyShares = null;
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
function hexToBytes2(hex) {
|
|
849
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
850
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
851
|
+
bytes[i] = Number.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
852
|
+
}
|
|
853
|
+
return bytes;
|
|
854
|
+
}
|
|
855
|
+
function bytesToHex2(bytes) {
|
|
856
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
857
|
+
}
|
|
858
|
+
function keccak256(data) {
|
|
859
|
+
const { keccak_256: keccak_2564 } = __require("@noble/hashes/sha3");
|
|
860
|
+
return keccak_2564(data);
|
|
861
|
+
}
|
|
862
|
+
function generateDeviceKey() {
|
|
863
|
+
return generateRandomHex(32);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/core/index.ts
|
|
867
|
+
init_crypto_primitives();
|
|
868
|
+
|
|
869
|
+
// src/core/provider-types.ts
|
|
870
|
+
var EIP1193_ERROR_CODES = {
|
|
871
|
+
UNAUTHORIZED: 4100,
|
|
872
|
+
UNSUPPORTED_METHOD: 4200,
|
|
873
|
+
DISCONNECTED: 4900,
|
|
874
|
+
CHAIN_DISCONNECTED: 4901,
|
|
875
|
+
INTERNAL_ERROR: -32603,
|
|
876
|
+
CHAIN_NOT_ADDED: 4902
|
|
877
|
+
};
|
|
878
|
+
function createProviderRpcError(code, message, data) {
|
|
879
|
+
const error = new Error(message);
|
|
880
|
+
error.code = code;
|
|
881
|
+
error.data = data;
|
|
882
|
+
return error;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/core/provider.ts
|
|
886
|
+
var NeroProvider = class {
|
|
887
|
+
constructor(config) {
|
|
888
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
889
|
+
this.connected = false;
|
|
890
|
+
this.customChains = /* @__PURE__ */ new Map();
|
|
891
|
+
this.config = config;
|
|
892
|
+
this.chainId = config.chainId;
|
|
893
|
+
this.chainConfig = getChainConfig(config.chainId);
|
|
894
|
+
}
|
|
895
|
+
get isNero() {
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
get isConnected() {
|
|
899
|
+
return this.connected;
|
|
900
|
+
}
|
|
901
|
+
async request(args) {
|
|
902
|
+
const { method, params } = args;
|
|
903
|
+
switch (method) {
|
|
904
|
+
case "eth_chainId":
|
|
905
|
+
return this.toHexChainId(this.chainId);
|
|
906
|
+
case "net_version":
|
|
907
|
+
return String(this.chainId);
|
|
908
|
+
case "eth_accounts":
|
|
909
|
+
case "eth_requestAccounts":
|
|
910
|
+
return this.handleRequestAccounts();
|
|
911
|
+
case "eth_sign":
|
|
912
|
+
return this.handleEthSign(params);
|
|
913
|
+
case "personal_sign":
|
|
914
|
+
return this.handlePersonalSign(params);
|
|
915
|
+
case "eth_signTypedData":
|
|
916
|
+
case "eth_signTypedData_v3":
|
|
917
|
+
case "eth_signTypedData_v4":
|
|
918
|
+
return this.handleSignTypedData(params);
|
|
919
|
+
case "eth_sendTransaction":
|
|
920
|
+
return this.handleSendTransaction(params);
|
|
921
|
+
case "wallet_switchEthereumChain":
|
|
922
|
+
return this.handleSwitchChain(params);
|
|
923
|
+
case "wallet_addEthereumChain":
|
|
924
|
+
return this.handleAddChain(params);
|
|
925
|
+
case "wallet_watchAsset":
|
|
926
|
+
return true;
|
|
927
|
+
case "eth_call":
|
|
928
|
+
case "eth_estimateGas":
|
|
929
|
+
case "eth_getBalance":
|
|
930
|
+
case "eth_getBlockByNumber":
|
|
931
|
+
case "eth_getBlockByHash":
|
|
932
|
+
case "eth_getTransactionByHash":
|
|
933
|
+
case "eth_getTransactionReceipt":
|
|
934
|
+
case "eth_getCode":
|
|
935
|
+
case "eth_getStorageAt":
|
|
936
|
+
case "eth_blockNumber":
|
|
937
|
+
case "eth_gasPrice":
|
|
938
|
+
case "eth_getTransactionCount":
|
|
939
|
+
case "eth_getLogs":
|
|
940
|
+
case "eth_feeHistory":
|
|
941
|
+
case "eth_maxPriorityFeePerGas":
|
|
942
|
+
return this.forwardToRpc(method, params);
|
|
943
|
+
default:
|
|
944
|
+
throw createProviderRpcError(
|
|
945
|
+
EIP1193_ERROR_CODES.UNSUPPORTED_METHOD,
|
|
946
|
+
`Method ${method} is not supported`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
on(event, listener) {
|
|
951
|
+
if (!this.listeners.has(event)) {
|
|
952
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
953
|
+
}
|
|
954
|
+
this.listeners.get(event)?.add(listener);
|
|
955
|
+
return this;
|
|
956
|
+
}
|
|
957
|
+
removeListener(event, listener) {
|
|
958
|
+
this.listeners.get(event)?.delete(listener);
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
removeAllListeners(event) {
|
|
962
|
+
if (event) {
|
|
963
|
+
this.listeners.delete(event);
|
|
964
|
+
} else {
|
|
965
|
+
this.listeners.clear();
|
|
966
|
+
}
|
|
967
|
+
return this;
|
|
968
|
+
}
|
|
969
|
+
emit(event, ...args) {
|
|
970
|
+
const listeners = this.listeners.get(event);
|
|
971
|
+
if (!listeners || listeners.size === 0) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
for (const listener of listeners) {
|
|
975
|
+
try {
|
|
976
|
+
listener(...args);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
connect() {
|
|
983
|
+
if (this.connected) return;
|
|
984
|
+
this.connected = true;
|
|
985
|
+
const info = {
|
|
986
|
+
chainId: this.toHexChainId(this.chainId)
|
|
987
|
+
};
|
|
988
|
+
this.emit("connect", info);
|
|
989
|
+
}
|
|
990
|
+
disconnect(error) {
|
|
991
|
+
if (!this.connected) return;
|
|
992
|
+
this.connected = false;
|
|
993
|
+
this.emit(
|
|
994
|
+
"disconnect",
|
|
995
|
+
error ?? createProviderRpcError(
|
|
996
|
+
EIP1193_ERROR_CODES.DISCONNECTED,
|
|
997
|
+
"Provider disconnected"
|
|
998
|
+
)
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
async switchChain(chainId) {
|
|
1002
|
+
const config = this.getChainConfigById(chainId);
|
|
1003
|
+
if (!config) {
|
|
1004
|
+
throw createProviderRpcError(
|
|
1005
|
+
EIP1193_ERROR_CODES.CHAIN_NOT_ADDED,
|
|
1006
|
+
`Chain ${chainId} has not been added`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
const previousChainId = this.chainId;
|
|
1010
|
+
this.chainId = chainId;
|
|
1011
|
+
this.chainConfig = config;
|
|
1012
|
+
if (previousChainId !== chainId) {
|
|
1013
|
+
this.emit("chainChanged", this.toHexChainId(chainId));
|
|
1014
|
+
this.config.onChainChanged?.(chainId);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
addChain(config) {
|
|
1018
|
+
this.customChains.set(config.chainId, config);
|
|
1019
|
+
}
|
|
1020
|
+
getChainId() {
|
|
1021
|
+
return this.chainId;
|
|
1022
|
+
}
|
|
1023
|
+
getChainConfig() {
|
|
1024
|
+
return this.chainConfig;
|
|
1025
|
+
}
|
|
1026
|
+
getChainConfigById(chainId) {
|
|
1027
|
+
return getChainConfig(chainId) ?? this.customChains.get(chainId);
|
|
1028
|
+
}
|
|
1029
|
+
async handleRequestAccounts() {
|
|
1030
|
+
const accounts = this.config.getAccounts();
|
|
1031
|
+
if (accounts.length === 0) {
|
|
1032
|
+
throw createProviderRpcError(
|
|
1033
|
+
EIP1193_ERROR_CODES.UNAUTHORIZED,
|
|
1034
|
+
"No accounts available"
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
if (!this.connected) {
|
|
1038
|
+
this.connect();
|
|
1039
|
+
}
|
|
1040
|
+
return accounts;
|
|
1041
|
+
}
|
|
1042
|
+
async handleEthSign(params) {
|
|
1043
|
+
const [address, message] = params;
|
|
1044
|
+
this.validateAddress(address);
|
|
1045
|
+
return this.config.signMessage(utils.hexToBytes(message.slice(2)));
|
|
1046
|
+
}
|
|
1047
|
+
async handlePersonalSign(params) {
|
|
1048
|
+
const [message, address] = params;
|
|
1049
|
+
this.validateAddress(address);
|
|
1050
|
+
const messageBytes = message.startsWith("0x") ? utils.hexToBytes(message.slice(2)) : utils.utf8ToBytes(message);
|
|
1051
|
+
return this.config.signMessage(messageBytes);
|
|
1052
|
+
}
|
|
1053
|
+
async handleSignTypedData(params) {
|
|
1054
|
+
const [address, typedDataJson] = params;
|
|
1055
|
+
this.validateAddress(address);
|
|
1056
|
+
const typedData = JSON.parse(typedDataJson);
|
|
1057
|
+
const { domain, types, primaryType, message } = typedData;
|
|
1058
|
+
const typesWithoutEIP712Domain = { ...types };
|
|
1059
|
+
typesWithoutEIP712Domain.EIP712Domain = void 0;
|
|
1060
|
+
return this.config.signTypedData(
|
|
1061
|
+
domain,
|
|
1062
|
+
typesWithoutEIP712Domain,
|
|
1063
|
+
primaryType,
|
|
1064
|
+
message
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
async handleSendTransaction(params) {
|
|
1068
|
+
const [tx] = params;
|
|
1069
|
+
return this.config.sendTransaction(tx);
|
|
1070
|
+
}
|
|
1071
|
+
async handleSwitchChain(params) {
|
|
1072
|
+
const [{ chainId }] = params;
|
|
1073
|
+
const numericChainId = Number.parseInt(chainId, 16);
|
|
1074
|
+
await this.switchChain(numericChainId);
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
async handleAddChain(params) {
|
|
1078
|
+
const [chainParams] = params;
|
|
1079
|
+
const chainId = Number.parseInt(chainParams.chainId, 16);
|
|
1080
|
+
const config = {
|
|
1081
|
+
chainId,
|
|
1082
|
+
chainName: chainParams.chainName.toLowerCase().replace(/\s+/g, "-"),
|
|
1083
|
+
displayName: chainParams.chainName,
|
|
1084
|
+
nativeCurrency: chainParams.nativeCurrency,
|
|
1085
|
+
rpcUrls: chainParams.rpcUrls,
|
|
1086
|
+
blockExplorerUrls: chainParams.blockExplorerUrls,
|
|
1087
|
+
isTestnet: false
|
|
1088
|
+
};
|
|
1089
|
+
this.addChain(config);
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
async forwardToRpc(method, params) {
|
|
1093
|
+
if (!this.chainConfig?.rpcUrls?.[0]) {
|
|
1094
|
+
throw createProviderRpcError(
|
|
1095
|
+
EIP1193_ERROR_CODES.CHAIN_DISCONNECTED,
|
|
1096
|
+
"No RPC URL available for current chain"
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const response = await fetch(this.chainConfig.rpcUrls[0], {
|
|
1100
|
+
method: "POST",
|
|
1101
|
+
headers: { "Content-Type": "application/json" },
|
|
1102
|
+
body: JSON.stringify({
|
|
1103
|
+
jsonrpc: "2.0",
|
|
1104
|
+
id: Date.now(),
|
|
1105
|
+
method,
|
|
1106
|
+
params: params ?? []
|
|
1107
|
+
})
|
|
1108
|
+
});
|
|
1109
|
+
const data = await response.json();
|
|
1110
|
+
if (data.error) {
|
|
1111
|
+
throw createProviderRpcError(
|
|
1112
|
+
data.error.code ?? EIP1193_ERROR_CODES.INTERNAL_ERROR,
|
|
1113
|
+
data.error.message,
|
|
1114
|
+
data.error.data
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return data.result;
|
|
1118
|
+
}
|
|
1119
|
+
validateAddress(address) {
|
|
1120
|
+
const accounts = this.config.getAccounts();
|
|
1121
|
+
const normalizedAddress = address.toLowerCase();
|
|
1122
|
+
const hasAccount = accounts.some(
|
|
1123
|
+
(a) => a.toLowerCase() === normalizedAddress
|
|
1124
|
+
);
|
|
1125
|
+
if (!hasAccount) {
|
|
1126
|
+
throw createProviderRpcError(
|
|
1127
|
+
EIP1193_ERROR_CODES.UNAUTHORIZED,
|
|
1128
|
+
`Address ${address} is not authorized`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
toHexChainId(chainId) {
|
|
1133
|
+
return `0x${chainId.toString(16)}`;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
var CURVE_ORDER = secp256k1.secp256k1.CURVE.n;
|
|
1137
|
+
function generateRandomScalar() {
|
|
1138
|
+
const bytes = new Uint8Array(32);
|
|
1139
|
+
crypto.getRandomValues(bytes);
|
|
1140
|
+
const scalar = bytesToBigInt(bytes) % CURVE_ORDER;
|
|
1141
|
+
return scalar === 0n ? generateRandomScalar() : scalar;
|
|
1142
|
+
}
|
|
1143
|
+
function generatePolynomial(degree) {
|
|
1144
|
+
if (degree < 0 || !Number.isInteger(degree)) {
|
|
1145
|
+
throw new Error(`Invalid polynomial degree: ${degree}`);
|
|
1146
|
+
}
|
|
1147
|
+
const coefficients = [];
|
|
1148
|
+
for (let i = 0; i <= degree; i++) {
|
|
1149
|
+
coefficients.push(generateRandomScalar());
|
|
1150
|
+
}
|
|
1151
|
+
return coefficients;
|
|
1152
|
+
}
|
|
1153
|
+
function evaluatePolynomial(coefficients, x) {
|
|
1154
|
+
let result = 0n;
|
|
1155
|
+
let xPower = 1n;
|
|
1156
|
+
for (const coeff of coefficients) {
|
|
1157
|
+
result = mod(result + mod(coeff * xPower, CURVE_ORDER), CURVE_ORDER);
|
|
1158
|
+
xPower = mod(xPower * x, CURVE_ORDER);
|
|
1159
|
+
}
|
|
1160
|
+
return result;
|
|
1161
|
+
}
|
|
1162
|
+
function aggregateShares(receivedShares, ownShare) {
|
|
1163
|
+
let combined = ownShare;
|
|
1164
|
+
for (const share of receivedShares.values()) {
|
|
1165
|
+
combined = mod(combined + share, CURVE_ORDER);
|
|
1166
|
+
}
|
|
1167
|
+
return combined;
|
|
1168
|
+
}
|
|
1169
|
+
function scalarToHex(scalar) {
|
|
1170
|
+
return scalar.toString(16).padStart(64, "0");
|
|
1171
|
+
}
|
|
1172
|
+
function hexToScalar(hex) {
|
|
1173
|
+
return BigInt(`0x${hex}`);
|
|
1174
|
+
}
|
|
1175
|
+
function mod(n, m) {
|
|
1176
|
+
return (n % m + m) % m;
|
|
1177
|
+
}
|
|
1178
|
+
function bytesToBigInt(bytes) {
|
|
1179
|
+
let result = 0n;
|
|
1180
|
+
for (const byte of bytes) {
|
|
1181
|
+
result = (result << 8n) + BigInt(byte);
|
|
1182
|
+
}
|
|
1183
|
+
return result;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/protocols/dkg/commitments.ts
|
|
1187
|
+
function createVSSSCommitments(partyId, coefficients) {
|
|
1188
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
1189
|
+
const coefficientCommitments = coefficients.map(
|
|
1190
|
+
(coeff) => G2.multiply(coeff).toHex(true)
|
|
1191
|
+
);
|
|
1192
|
+
const proof = createProofOfKnowledge(
|
|
1193
|
+
partyId,
|
|
1194
|
+
coefficients[0],
|
|
1195
|
+
coefficientCommitments[0]
|
|
1196
|
+
);
|
|
1197
|
+
return {
|
|
1198
|
+
partyId,
|
|
1199
|
+
coefficientCommitments,
|
|
1200
|
+
proofOfKnowledge: proof
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function verifyVSSSCommitment(commitment, share, receiverPartyId) {
|
|
1204
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
1205
|
+
const sharePoint = G2.multiply(share);
|
|
1206
|
+
let expectedPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(
|
|
1207
|
+
commitment.coefficientCommitments[0]
|
|
1208
|
+
);
|
|
1209
|
+
const x = BigInt(receiverPartyId);
|
|
1210
|
+
let xPower = x;
|
|
1211
|
+
for (let i = 1; i < commitment.coefficientCommitments.length; i++) {
|
|
1212
|
+
const commitmentPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(
|
|
1213
|
+
commitment.coefficientCommitments[i]
|
|
1214
|
+
);
|
|
1215
|
+
const scaled = commitmentPoint.multiply(xPower);
|
|
1216
|
+
expectedPoint = expectedPoint.add(scaled);
|
|
1217
|
+
xPower = mod(xPower * x, CURVE_ORDER);
|
|
1218
|
+
}
|
|
1219
|
+
return sharePoint.equals(expectedPoint);
|
|
1220
|
+
}
|
|
1221
|
+
function createProofOfKnowledge(partyId, secret, publicCommitment) {
|
|
1222
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
1223
|
+
const k = generateRandomScalar();
|
|
1224
|
+
const R = G2.multiply(k);
|
|
1225
|
+
const challenge = computeChallenge(partyId, publicCommitment, R.toHex(true));
|
|
1226
|
+
const response = mod(k + secret * challenge, CURVE_ORDER);
|
|
1227
|
+
return JSON.stringify({
|
|
1228
|
+
R: R.toHex(true),
|
|
1229
|
+
s: scalarToHex(response)
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function verifyProofOfKnowledge(proof, partyId, publicCommitment) {
|
|
1233
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
1234
|
+
const { R, s } = JSON.parse(proof);
|
|
1235
|
+
const RPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(R);
|
|
1236
|
+
const response = BigInt(`0x${s}`);
|
|
1237
|
+
const challenge = computeChallenge(partyId, publicCommitment, R);
|
|
1238
|
+
const leftSide = G2.multiply(response);
|
|
1239
|
+
const commitmentPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(publicCommitment);
|
|
1240
|
+
const rightSide = RPoint.add(commitmentPoint.multiply(challenge));
|
|
1241
|
+
return leftSide.equals(rightSide);
|
|
1242
|
+
}
|
|
1243
|
+
function computeChallenge(partyId, commitment, R) {
|
|
1244
|
+
const input = `${partyId}:${commitment}:${R}`;
|
|
1245
|
+
const hash = sha256.sha256(utils.utf8ToBytes(input));
|
|
1246
|
+
return mod(BigInt(`0x${utils.bytesToHex(hash)}`), CURVE_ORDER);
|
|
1247
|
+
}
|
|
1248
|
+
function combineVSSSCommitments(commitments) {
|
|
1249
|
+
let combined = secp256k1.secp256k1.ProjectivePoint.fromHex(
|
|
1250
|
+
commitments[0].coefficientCommitments[0]
|
|
1251
|
+
);
|
|
1252
|
+
for (let i = 1; i < commitments.length; i++) {
|
|
1253
|
+
const point = secp256k1.secp256k1.ProjectivePoint.fromHex(
|
|
1254
|
+
commitments[i].coefficientCommitments[0]
|
|
1255
|
+
);
|
|
1256
|
+
combined = combined.add(point);
|
|
1257
|
+
}
|
|
1258
|
+
return combined.toHex(false);
|
|
1259
|
+
}
|
|
1260
|
+
function toBuffer2(data) {
|
|
1261
|
+
return data.buffer.slice(
|
|
1262
|
+
data.byteOffset,
|
|
1263
|
+
data.byteOffset + data.byteLength
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
async function deriveSharedSecret(privateKey, publicKey) {
|
|
1267
|
+
const pubPoint = secp256k1.secp256k1.ProjectivePoint.fromHex(publicKey);
|
|
1268
|
+
const sharedPoint = pubPoint.multiply(privateKey);
|
|
1269
|
+
const sharedBytes = utils.hexToBytes(sharedPoint.toHex(true));
|
|
1270
|
+
return sha256.sha256(sharedBytes);
|
|
1271
|
+
}
|
|
1272
|
+
async function aesGcmEncrypt(plaintext, key, nonce) {
|
|
1273
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1274
|
+
"raw",
|
|
1275
|
+
toBuffer2(key),
|
|
1276
|
+
{ name: "AES-GCM" },
|
|
1277
|
+
false,
|
|
1278
|
+
["encrypt"]
|
|
1279
|
+
);
|
|
1280
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1281
|
+
{ name: "AES-GCM", iv: toBuffer2(nonce), tagLength: 128 },
|
|
1282
|
+
cryptoKey,
|
|
1283
|
+
toBuffer2(plaintext)
|
|
1284
|
+
);
|
|
1285
|
+
const encryptedArray = new Uint8Array(encrypted);
|
|
1286
|
+
const ciphertext = encryptedArray.slice(0, -16);
|
|
1287
|
+
const tag = encryptedArray.slice(-16);
|
|
1288
|
+
return { ciphertext, tag };
|
|
1289
|
+
}
|
|
1290
|
+
async function aesGcmDecrypt(ciphertext, tag, key, nonce) {
|
|
1291
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1292
|
+
"raw",
|
|
1293
|
+
toBuffer2(key),
|
|
1294
|
+
{ name: "AES-GCM" },
|
|
1295
|
+
false,
|
|
1296
|
+
["decrypt"]
|
|
1297
|
+
);
|
|
1298
|
+
const combined = new Uint8Array(ciphertext.length + tag.length);
|
|
1299
|
+
combined.set(ciphertext);
|
|
1300
|
+
combined.set(tag, ciphertext.length);
|
|
1301
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1302
|
+
{ name: "AES-GCM", iv: toBuffer2(nonce), tagLength: 128 },
|
|
1303
|
+
cryptoKey,
|
|
1304
|
+
toBuffer2(combined)
|
|
1305
|
+
);
|
|
1306
|
+
return new Uint8Array(decrypted);
|
|
1307
|
+
}
|
|
1308
|
+
async function encryptShare(share, fromPartyId, toPartyId, recipientPublicKey) {
|
|
1309
|
+
const ephemeralPrivateKey = generateSecureScalar();
|
|
1310
|
+
const ephemeralPublicKey = secp256k1.secp256k1.ProjectivePoint.BASE.multiply(ephemeralPrivateKey).toHex(true);
|
|
1311
|
+
const sharedSecret = await deriveSharedSecret(
|
|
1312
|
+
ephemeralPrivateKey,
|
|
1313
|
+
recipientPublicKey
|
|
1314
|
+
);
|
|
1315
|
+
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
|
1316
|
+
const shareHex = scalarToHex(share);
|
|
1317
|
+
const plaintext = utils.utf8ToBytes(shareHex);
|
|
1318
|
+
const { ciphertext, tag } = await aesGcmEncrypt(
|
|
1319
|
+
plaintext,
|
|
1320
|
+
sharedSecret,
|
|
1321
|
+
nonce
|
|
1322
|
+
);
|
|
1323
|
+
return {
|
|
1324
|
+
fromPartyId,
|
|
1325
|
+
toPartyId,
|
|
1326
|
+
ephemeralPublicKey,
|
|
1327
|
+
ciphertext: utils.bytesToHex(ciphertext),
|
|
1328
|
+
nonce: utils.bytesToHex(nonce),
|
|
1329
|
+
tag: utils.bytesToHex(tag)
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
async function decryptShare(encryptedShare, recipientPrivateKey) {
|
|
1333
|
+
const sharedSecret = await deriveSharedSecret(
|
|
1334
|
+
recipientPrivateKey,
|
|
1335
|
+
encryptedShare.ephemeralPublicKey
|
|
1336
|
+
);
|
|
1337
|
+
const ciphertext = utils.hexToBytes(encryptedShare.ciphertext);
|
|
1338
|
+
const nonce = utils.hexToBytes(encryptedShare.nonce);
|
|
1339
|
+
const tag = utils.hexToBytes(encryptedShare.tag);
|
|
1340
|
+
const plaintext = await aesGcmDecrypt(ciphertext, tag, sharedSecret, nonce);
|
|
1341
|
+
const shareHex = new TextDecoder().decode(plaintext);
|
|
1342
|
+
const share = hexToScalar(shareHex);
|
|
1343
|
+
return {
|
|
1344
|
+
fromPartyId: encryptedShare.fromPartyId,
|
|
1345
|
+
share
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function generateSecureScalar() {
|
|
1349
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
1350
|
+
const scalar = mod(BigInt(`0x${utils.bytesToHex(bytes)}`), CURVE_ORDER);
|
|
1351
|
+
if (scalar === 0n) {
|
|
1352
|
+
throw new Error("RNG failure: generated zero scalar");
|
|
1353
|
+
}
|
|
1354
|
+
return scalar;
|
|
1355
|
+
}
|
|
1356
|
+
function generateEphemeralKeyPair() {
|
|
1357
|
+
const privateKey = generateSecureScalar();
|
|
1358
|
+
const publicKey = secp256k1.secp256k1.ProjectivePoint.BASE.multiply(privateKey).toHex(true);
|
|
1359
|
+
return { privateKey, publicKey };
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/protocols/dkg/dkg-client.ts
|
|
1363
|
+
var PROTOCOL_VERSION = "pedersen-dkg-v1";
|
|
1364
|
+
var DKGClient = class {
|
|
1365
|
+
constructor(config) {
|
|
1366
|
+
this.state = null;
|
|
1367
|
+
this.polynomial = [];
|
|
1368
|
+
this.ephemeralKeyPair = null;
|
|
1369
|
+
this.receivedCommitments = /* @__PURE__ */ new Map();
|
|
1370
|
+
this.receivedShares = /* @__PURE__ */ new Map();
|
|
1371
|
+
this.apiClient = config.apiClient;
|
|
1372
|
+
this.wsClient = config.wsClient;
|
|
1373
|
+
this.timeout = config.timeout ?? 6e4;
|
|
1374
|
+
}
|
|
1375
|
+
async execute() {
|
|
1376
|
+
try {
|
|
1377
|
+
await this.initializeSession();
|
|
1378
|
+
await this.runCommitmentPhase();
|
|
1379
|
+
await this.runShareExchangePhase();
|
|
1380
|
+
await this.runVerificationPhase();
|
|
1381
|
+
return await this.completeProtocol();
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
const message = error instanceof Error ? error.message : "DKG failed";
|
|
1384
|
+
return {
|
|
1385
|
+
success: false,
|
|
1386
|
+
error: message
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
async initializeSession() {
|
|
1391
|
+
const { sessionId, partyId, participantCount, threshold } = await this.apiClient.initiateDKG();
|
|
1392
|
+
if (threshold < 2) {
|
|
1393
|
+
throw new SDKError(
|
|
1394
|
+
"Threshold must be at least 2 for security",
|
|
1395
|
+
"INVALID_THRESHOLD"
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
if (participantCount < 2) {
|
|
1399
|
+
throw new SDKError(
|
|
1400
|
+
"Participant count must be at least 2",
|
|
1401
|
+
"INVALID_PARTICIPANT_COUNT"
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
if (threshold > participantCount) {
|
|
1405
|
+
throw new SDKError(
|
|
1406
|
+
"Threshold cannot exceed participant count",
|
|
1407
|
+
"INVALID_THRESHOLD"
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
this.state = {
|
|
1411
|
+
sessionId,
|
|
1412
|
+
round: "commitment",
|
|
1413
|
+
partyId,
|
|
1414
|
+
participantCount,
|
|
1415
|
+
threshold,
|
|
1416
|
+
commitments: /* @__PURE__ */ new Map(),
|
|
1417
|
+
receivedShares: /* @__PURE__ */ new Map()
|
|
1418
|
+
};
|
|
1419
|
+
this.polynomial = generatePolynomial(threshold - 1);
|
|
1420
|
+
this.ephemeralKeyPair = generateEphemeralKeyPair();
|
|
1421
|
+
if (this.wsClient) {
|
|
1422
|
+
await this.wsClient.connect();
|
|
1423
|
+
this.wsClient.setAccessToken(
|
|
1424
|
+
this.apiClient.getTokens()?.accessToken ?? ""
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
async runCommitmentPhase() {
|
|
1429
|
+
if (!this.state) throw new SDKError("State not initialized", "STATE_ERROR");
|
|
1430
|
+
const commitment = createVSSSCommitments(
|
|
1431
|
+
this.state.partyId,
|
|
1432
|
+
this.polynomial
|
|
1433
|
+
);
|
|
1434
|
+
const apiCommitment = {
|
|
1435
|
+
partyId: this.state.partyId,
|
|
1436
|
+
commitments: commitment.coefficientCommitments,
|
|
1437
|
+
publicKey: this.ephemeralKeyPair?.publicKey ?? "",
|
|
1438
|
+
proofOfKnowledge: commitment.proofOfKnowledge
|
|
1439
|
+
};
|
|
1440
|
+
await this.apiClient.submitDKGCommitment(
|
|
1441
|
+
this.state.sessionId,
|
|
1442
|
+
apiCommitment
|
|
1443
|
+
);
|
|
1444
|
+
this.receivedCommitments.set(this.state.partyId, commitment);
|
|
1445
|
+
const otherCommitments = await this.waitForCommitments();
|
|
1446
|
+
for (const [partyId, comm] of otherCommitments) {
|
|
1447
|
+
if (!comm.proofOfKnowledge) {
|
|
1448
|
+
throw new SDKError(
|
|
1449
|
+
`Missing proof of knowledge from party ${comm.partyId}. Backend must relay proofs for client-side verification.`,
|
|
1450
|
+
"MISSING_COMMITMENT_PROOF"
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
if (comm.partyId !== partyId) {
|
|
1454
|
+
throw new SDKError(
|
|
1455
|
+
`Party ID mismatch: expected ${partyId}, got ${comm.partyId}`,
|
|
1456
|
+
"PARTY_ID_MISMATCH"
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
if (!verifyProofOfKnowledge(
|
|
1460
|
+
comm.proofOfKnowledge,
|
|
1461
|
+
comm.partyId,
|
|
1462
|
+
comm.coefficientCommitments[0]
|
|
1463
|
+
)) {
|
|
1464
|
+
throw new SDKError(
|
|
1465
|
+
`Invalid proof of knowledge from party ${comm.partyId}`,
|
|
1466
|
+
"INVALID_COMMITMENT_PROOF"
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
this.receivedCommitments.set(comm.partyId, comm);
|
|
1470
|
+
}
|
|
1471
|
+
this.state.round = "share_exchange";
|
|
1472
|
+
}
|
|
1473
|
+
async runShareExchangePhase() {
|
|
1474
|
+
if (!this.state) throw new SDKError("State not initialized", "STATE_ERROR");
|
|
1475
|
+
for (let partyId = 1; partyId <= this.state.participantCount; partyId++) {
|
|
1476
|
+
if (partyId === this.state.partyId) continue;
|
|
1477
|
+
const share = evaluatePolynomial(this.polynomial, BigInt(partyId));
|
|
1478
|
+
const commitment = this.receivedCommitments.get(partyId);
|
|
1479
|
+
if (!commitment) {
|
|
1480
|
+
throw new SDKError(
|
|
1481
|
+
`Missing commitment from party ${partyId}`,
|
|
1482
|
+
"MISSING_COMMITMENT"
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
const apiCommitment = await this.apiClient.getDKGCommitments(
|
|
1486
|
+
this.state.sessionId
|
|
1487
|
+
);
|
|
1488
|
+
const partyCommitment = apiCommitment.commitments.find(
|
|
1489
|
+
(c) => c.partyId === partyId
|
|
1490
|
+
);
|
|
1491
|
+
if (!partyCommitment) {
|
|
1492
|
+
throw new SDKError(
|
|
1493
|
+
`Missing public key from party ${partyId}`,
|
|
1494
|
+
"MISSING_PUBLIC_KEY"
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const encrypted = await encryptShare(
|
|
1498
|
+
share,
|
|
1499
|
+
this.state.partyId,
|
|
1500
|
+
partyId,
|
|
1501
|
+
partyCommitment.publicKey
|
|
1502
|
+
);
|
|
1503
|
+
await this.apiClient.submitDKGShare(
|
|
1504
|
+
this.state.sessionId,
|
|
1505
|
+
JSON.stringify(encrypted),
|
|
1506
|
+
partyId
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
const ownShare = evaluatePolynomial(
|
|
1510
|
+
this.polynomial,
|
|
1511
|
+
BigInt(this.state.partyId)
|
|
1512
|
+
);
|
|
1513
|
+
this.receivedShares.set(this.state.partyId, ownShare);
|
|
1514
|
+
const receivedShares = await this.waitForShares();
|
|
1515
|
+
for (const [partyId, encryptedShare] of receivedShares) {
|
|
1516
|
+
const decrypted = await decryptShare(
|
|
1517
|
+
encryptedShare,
|
|
1518
|
+
this.ephemeralKeyPair?.privateKey ?? 0n
|
|
1519
|
+
);
|
|
1520
|
+
const commitment = this.receivedCommitments.get(partyId);
|
|
1521
|
+
if (!commitment) {
|
|
1522
|
+
throw new SDKError(
|
|
1523
|
+
`Missing commitment from party ${partyId}`,
|
|
1524
|
+
"MISSING_COMMITMENT"
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (!verifyVSSSCommitment(commitment, decrypted.share, this.state.partyId)) {
|
|
1528
|
+
throw new SDKError(
|
|
1529
|
+
`Invalid share from party ${partyId}`,
|
|
1530
|
+
"INVALID_SHARE"
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
this.receivedShares.set(partyId, decrypted.share);
|
|
1534
|
+
}
|
|
1535
|
+
this.state.round = "verification";
|
|
1536
|
+
}
|
|
1537
|
+
async runVerificationPhase() {
|
|
1538
|
+
if (!this.state) throw new SDKError("State not initialized", "STATE_ERROR");
|
|
1539
|
+
const finalShare = aggregateShares(this.receivedShares, 0n);
|
|
1540
|
+
const allCommitments = Array.from(this.receivedCommitments.values());
|
|
1541
|
+
const publicKey = combineVSSSCommitments(allCommitments);
|
|
1542
|
+
this.state.privateShare = finalShare;
|
|
1543
|
+
this.state.publicKey = publicKey;
|
|
1544
|
+
this.state.round = "complete";
|
|
1545
|
+
}
|
|
1546
|
+
async completeProtocol() {
|
|
1547
|
+
if (!this.state || !this.state.privateShare || !this.state.publicKey) {
|
|
1548
|
+
throw new SDKError("Protocol not complete", "PROTOCOL_INCOMPLETE");
|
|
1549
|
+
}
|
|
1550
|
+
const walletAddress = this.deriveWalletAddress(this.state.publicKey);
|
|
1551
|
+
await this.apiClient.completeDKG(
|
|
1552
|
+
this.state.sessionId,
|
|
1553
|
+
this.state.partyId,
|
|
1554
|
+
this.state.publicKey,
|
|
1555
|
+
walletAddress
|
|
1556
|
+
);
|
|
1557
|
+
return {
|
|
1558
|
+
success: true,
|
|
1559
|
+
publicKey: this.state.publicKey,
|
|
1560
|
+
walletAddress,
|
|
1561
|
+
partyId: this.state.partyId
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
getKeyShare() {
|
|
1565
|
+
if (!this.state?.privateShare) return null;
|
|
1566
|
+
return {
|
|
1567
|
+
partyId: this.state.partyId,
|
|
1568
|
+
privateShare: scalarToHex(this.state.privateShare),
|
|
1569
|
+
publicShare: secp256k1.secp256k1.ProjectivePoint.BASE.multiply(
|
|
1570
|
+
this.state.privateShare
|
|
1571
|
+
).toHex(true),
|
|
1572
|
+
commitment: this.computeShareCommitment(this.state.privateShare),
|
|
1573
|
+
threshold: this.state.threshold,
|
|
1574
|
+
totalParties: this.state.participantCount,
|
|
1575
|
+
protocolVersion: PROTOCOL_VERSION
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
getPartyPublicShares() {
|
|
1579
|
+
const shares = /* @__PURE__ */ new Map();
|
|
1580
|
+
for (const [partyId, commitment] of this.receivedCommitments) {
|
|
1581
|
+
if (commitment.coefficientCommitments.length > 0) {
|
|
1582
|
+
shares.set(partyId, commitment.coefficientCommitments[0]);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return shares;
|
|
1586
|
+
}
|
|
1587
|
+
async waitForCommitments() {
|
|
1588
|
+
const startTime = Date.now();
|
|
1589
|
+
const commitments = /* @__PURE__ */ new Map();
|
|
1590
|
+
while (Date.now() - startTime < this.timeout) {
|
|
1591
|
+
const result = await this.apiClient.getDKGCommitments(
|
|
1592
|
+
this.state?.sessionId ?? ""
|
|
1593
|
+
);
|
|
1594
|
+
for (const comm of result.commitments) {
|
|
1595
|
+
if (comm.partyId !== this.state?.partyId && !commitments.has(comm.partyId)) {
|
|
1596
|
+
commitments.set(comm.partyId, {
|
|
1597
|
+
partyId: comm.partyId,
|
|
1598
|
+
coefficientCommitments: comm.commitments,
|
|
1599
|
+
proofOfKnowledge: comm.proofOfKnowledge ?? ""
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (result.ready || commitments.size >= (this.state?.participantCount ?? 1) - 1) {
|
|
1604
|
+
return commitments;
|
|
1605
|
+
}
|
|
1606
|
+
await this.delay(1e3);
|
|
1607
|
+
}
|
|
1608
|
+
throw new SDKError("Timeout waiting for commitments", "COMMITMENT_TIMEOUT");
|
|
1609
|
+
}
|
|
1610
|
+
async waitForShares() {
|
|
1611
|
+
const startTime = Date.now();
|
|
1612
|
+
const shares = /* @__PURE__ */ new Map();
|
|
1613
|
+
while (Date.now() - startTime < this.timeout) {
|
|
1614
|
+
const result = await this.apiClient.getDKGShares(
|
|
1615
|
+
this.state?.sessionId ?? "",
|
|
1616
|
+
this.state?.partyId ?? 0
|
|
1617
|
+
);
|
|
1618
|
+
for (const share of result.shares) {
|
|
1619
|
+
if (!shares.has(share.fromPartyId)) {
|
|
1620
|
+
shares.set(share.fromPartyId, JSON.parse(share.encryptedShare));
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (result.ready || shares.size >= (this.state?.participantCount ?? 1) - 1) {
|
|
1624
|
+
return shares;
|
|
1625
|
+
}
|
|
1626
|
+
await this.delay(1e3);
|
|
1627
|
+
}
|
|
1628
|
+
throw new SDKError("Timeout waiting for shares", "SHARE_TIMEOUT");
|
|
1629
|
+
}
|
|
1630
|
+
deriveWalletAddress(publicKey) {
|
|
1631
|
+
const pubKeyBytes = utils.hexToBytes(
|
|
1632
|
+
publicKey.startsWith("04") ? publicKey.slice(2) : publicKey
|
|
1633
|
+
);
|
|
1634
|
+
const hash = sha3.keccak_256(pubKeyBytes);
|
|
1635
|
+
const addressBytes = hash.slice(-20);
|
|
1636
|
+
return `0x${utils.bytesToHex(addressBytes)}`;
|
|
1637
|
+
}
|
|
1638
|
+
computeShareCommitment(share) {
|
|
1639
|
+
const shareHex = scalarToHex(share);
|
|
1640
|
+
return utils.bytesToHex(sha256.sha256(utils.hexToBytes(shareHex)));
|
|
1641
|
+
}
|
|
1642
|
+
delay(ms) {
|
|
1643
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1644
|
+
}
|
|
1645
|
+
cleanup() {
|
|
1646
|
+
this.state = null;
|
|
1647
|
+
this.polynomial = [];
|
|
1648
|
+
this.ephemeralKeyPair = null;
|
|
1649
|
+
this.receivedCommitments.clear();
|
|
1650
|
+
this.receivedShares.clear();
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
// src/transport/api-client.ts
|
|
1655
|
+
var APIClient = class {
|
|
1656
|
+
constructor(config) {
|
|
1657
|
+
this.tokens = null;
|
|
1658
|
+
this.refreshPromise = null;
|
|
1659
|
+
this.baseUrl = config.backendUrl.replace(/\/$/, "");
|
|
1660
|
+
}
|
|
1661
|
+
setTokens(tokens) {
|
|
1662
|
+
this.tokens = tokens;
|
|
1663
|
+
}
|
|
1664
|
+
getTokens() {
|
|
1665
|
+
return this.tokens;
|
|
1666
|
+
}
|
|
1667
|
+
clearTokens() {
|
|
1668
|
+
this.tokens = null;
|
|
1669
|
+
}
|
|
1670
|
+
async request(method, path, body, requiresAuth = true) {
|
|
1671
|
+
if (requiresAuth && this.tokens) {
|
|
1672
|
+
if (Date.now() >= this.tokens.expiresAt - 6e4) {
|
|
1673
|
+
await this.refreshAccessToken();
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
const headers = {
|
|
1677
|
+
"Content-Type": "application/json"
|
|
1678
|
+
};
|
|
1679
|
+
if (requiresAuth && this.tokens) {
|
|
1680
|
+
headers.Authorization = `Bearer ${this.tokens.accessToken}`;
|
|
1681
|
+
}
|
|
1682
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1683
|
+
method,
|
|
1684
|
+
headers,
|
|
1685
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
1686
|
+
credentials: "include"
|
|
1687
|
+
});
|
|
1688
|
+
const data = await response.json();
|
|
1689
|
+
if (!data.success) {
|
|
1690
|
+
throw new SDKError(
|
|
1691
|
+
data.error?.message ?? "Request failed",
|
|
1692
|
+
data.error?.code ?? "REQUEST_FAILED",
|
|
1693
|
+
response.status
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
return data.data;
|
|
1697
|
+
}
|
|
1698
|
+
async refreshAccessToken() {
|
|
1699
|
+
if (this.refreshPromise) {
|
|
1700
|
+
return this.refreshPromise;
|
|
1701
|
+
}
|
|
1702
|
+
this.refreshPromise = (async () => {
|
|
1703
|
+
if (!this.tokens?.refreshToken) {
|
|
1704
|
+
throw new SDKError("No refresh token available", "NO_REFRESH_TOKEN");
|
|
1705
|
+
}
|
|
1706
|
+
const response = await fetch(`${this.baseUrl}/api/auth/refresh`, {
|
|
1707
|
+
method: "POST",
|
|
1708
|
+
headers: { "Content-Type": "application/json" },
|
|
1709
|
+
body: JSON.stringify({ refreshToken: this.tokens.refreshToken })
|
|
1710
|
+
});
|
|
1711
|
+
const data = await response.json();
|
|
1712
|
+
if (!data.success || !data.data) {
|
|
1713
|
+
this.tokens = null;
|
|
1714
|
+
throw new SDKError("Token refresh failed", "TOKEN_REFRESH_FAILED");
|
|
1715
|
+
}
|
|
1716
|
+
this.tokens = {
|
|
1717
|
+
accessToken: data.data.accessToken,
|
|
1718
|
+
refreshToken: data.data.refreshToken,
|
|
1719
|
+
expiresAt: Date.now() + data.data.expiresIn * 1e3
|
|
1720
|
+
};
|
|
1721
|
+
})();
|
|
1722
|
+
try {
|
|
1723
|
+
await this.refreshPromise;
|
|
1724
|
+
} finally {
|
|
1725
|
+
this.refreshPromise = null;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
async getOAuthUrl(provider, redirectUri) {
|
|
1729
|
+
return this.request(
|
|
1730
|
+
"POST",
|
|
1731
|
+
"/api/auth/oauth/url",
|
|
1732
|
+
{ provider, redirectUri },
|
|
1733
|
+
false
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
async handleOAuthCallback(provider, code, state, fingerprint) {
|
|
1737
|
+
const result = await this.request(
|
|
1738
|
+
"POST",
|
|
1739
|
+
"/api/auth/oauth/callback",
|
|
1740
|
+
{ provider, code, state, fingerprint },
|
|
1741
|
+
false
|
|
1742
|
+
);
|
|
1743
|
+
this.tokens = {
|
|
1744
|
+
accessToken: result.accessToken,
|
|
1745
|
+
refreshToken: result.refreshToken,
|
|
1746
|
+
expiresAt: Date.now() + result.expiresIn * 1e3
|
|
1747
|
+
};
|
|
1748
|
+
return {
|
|
1749
|
+
user: result.user,
|
|
1750
|
+
tokens: this.tokens,
|
|
1751
|
+
wallet: result.wallet,
|
|
1752
|
+
requiresDKG: result.requiresDKG
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
async getCurrentUser() {
|
|
1756
|
+
return this.request("GET", "/api/user/me");
|
|
1757
|
+
}
|
|
1758
|
+
async logout() {
|
|
1759
|
+
await this.request("POST", "/api/auth/logout");
|
|
1760
|
+
this.tokens = null;
|
|
1761
|
+
}
|
|
1762
|
+
async initiateDKG() {
|
|
1763
|
+
return this.request("POST", "/api/mpc/dkg/initiate");
|
|
1764
|
+
}
|
|
1765
|
+
async submitDKGCommitment(sessionId, commitment) {
|
|
1766
|
+
return this.request("POST", "/api/mpc/dkg/commitment", {
|
|
1767
|
+
sessionId,
|
|
1768
|
+
commitment
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
async getDKGCommitments(sessionId) {
|
|
1772
|
+
return this.request("GET", `/api/mpc/dkg/${sessionId}/commitments`);
|
|
1773
|
+
}
|
|
1774
|
+
async submitDKGShare(sessionId, encryptedShare, toPartyId) {
|
|
1775
|
+
return this.request("POST", "/api/mpc/dkg/share", {
|
|
1776
|
+
sessionId,
|
|
1777
|
+
encryptedShare,
|
|
1778
|
+
toPartyId
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
async getDKGShares(sessionId, partyId) {
|
|
1782
|
+
return this.request("GET", `/api/mpc/dkg/${sessionId}/shares/${partyId}`);
|
|
1783
|
+
}
|
|
1784
|
+
async completeDKG(sessionId, partyId, publicKey, walletAddress) {
|
|
1785
|
+
return this.request("POST", "/api/mpc/dkg/complete", {
|
|
1786
|
+
sessionId,
|
|
1787
|
+
partyId,
|
|
1788
|
+
publicKey,
|
|
1789
|
+
walletAddress
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
async initiateSigningSession(messageHash, messageType) {
|
|
1793
|
+
return this.request("POST", "/api/mpc/signing/initiate", {
|
|
1794
|
+
messageHash,
|
|
1795
|
+
messageType
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
async submitNonceCommitment(sessionId, partyId, commitment) {
|
|
1799
|
+
return this.request("POST", "/api/mpc/signing/nonce-commitment", {
|
|
1800
|
+
sessionId,
|
|
1801
|
+
partyId,
|
|
1802
|
+
commitment
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
async getNonceCommitments(sessionId) {
|
|
1806
|
+
return this.request(
|
|
1807
|
+
"GET",
|
|
1808
|
+
`/api/mpc/signing/${sessionId}/nonce-commitments`
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
async submitPartialSignature(sessionId, partyId, partialSignature) {
|
|
1812
|
+
return this.request("POST", "/api/mpc/signing/partial", {
|
|
1813
|
+
sessionId,
|
|
1814
|
+
partyId,
|
|
1815
|
+
partialSignature
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
async getPartialSignatures(sessionId) {
|
|
1819
|
+
return this.request("GET", `/api/mpc/signing/${sessionId}/partials`);
|
|
1820
|
+
}
|
|
1821
|
+
async getSigningResult(sessionId) {
|
|
1822
|
+
return this.request("GET", `/api/mpc/signing/${sessionId}/result`);
|
|
1823
|
+
}
|
|
1824
|
+
async getWalletInfo() {
|
|
1825
|
+
return this.request("GET", "/api/wallet/info");
|
|
1826
|
+
}
|
|
1827
|
+
async getPartyPublicShares() {
|
|
1828
|
+
return this.request("GET", "/api/wallet/party-shares");
|
|
1829
|
+
}
|
|
1830
|
+
// DKLS V2 API Methods
|
|
1831
|
+
async dklsKeygenInit() {
|
|
1832
|
+
return this.request("POST", "/api/v2/dkls/keygen/init");
|
|
1833
|
+
}
|
|
1834
|
+
async dklsKeygenCommitment(sessionId, clientCommitment) {
|
|
1835
|
+
return this.request("POST", "/api/v2/dkls/keygen/commitment", {
|
|
1836
|
+
sessionId,
|
|
1837
|
+
clientCommitment
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
async dklsKeygenComplete(sessionId, clientPublicShare) {
|
|
1841
|
+
return this.request("POST", "/api/v2/dkls/keygen/complete", {
|
|
1842
|
+
sessionId,
|
|
1843
|
+
clientPublicShare
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
async dklsSigningInit(params) {
|
|
1847
|
+
return this.request("POST", "/api/v2/dkls/signing/init", params);
|
|
1848
|
+
}
|
|
1849
|
+
async dklsSigningNonce(sessionId, clientNonceCommitment) {
|
|
1850
|
+
return this.request("POST", "/api/v2/dkls/signing/nonce", {
|
|
1851
|
+
sessionId,
|
|
1852
|
+
clientNonceCommitment
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* @deprecated This method defeats threshold security by transmitting the full key share.
|
|
1857
|
+
* Use the MtA-based signing flow instead: dklsMtaRound1 -> dklsMtaRound2 -> dklsSigningPartial.
|
|
1858
|
+
* This endpoint is only available in test mode (DKLS_LOCAL_TESTING_MODE=true).
|
|
1859
|
+
*/
|
|
1860
|
+
async dklsSigningComplete(sessionId, clientKeyShare) {
|
|
1861
|
+
console.warn(
|
|
1862
|
+
"[DEPRECATED] dklsSigningComplete defeats threshold security. Use MtA-based signing (dklsMtaRound1/dklsMtaRound2/dklsSigningPartial) instead."
|
|
1863
|
+
);
|
|
1864
|
+
return this.request("POST", "/api/v2/dkls/signing/complete", {
|
|
1865
|
+
sessionId,
|
|
1866
|
+
clientKeyShare
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
async dklsSigningStatus(sessionId) {
|
|
1870
|
+
return this.request("GET", `/api/v2/dkls/signing/${sessionId}`);
|
|
1871
|
+
}
|
|
1872
|
+
async dklsSigningCancel(sessionId) {
|
|
1873
|
+
return this.request("DELETE", `/api/v2/dkls/signing/${sessionId}`);
|
|
1874
|
+
}
|
|
1875
|
+
async dklsMtaRound1(sessionId, mta1Setup, mta2Setup) {
|
|
1876
|
+
return this.request("POST", "/api/v2/dkls/signing/mta/round1", {
|
|
1877
|
+
sessionId,
|
|
1878
|
+
mta1Setup,
|
|
1879
|
+
mta2Setup
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
async dklsMtaRound2(sessionId, mta1Encrypted, mta2Encrypted) {
|
|
1883
|
+
return this.request("POST", "/api/v2/dkls/signing/mta/round2", {
|
|
1884
|
+
sessionId,
|
|
1885
|
+
mta1Encrypted,
|
|
1886
|
+
mta2Encrypted
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
async dklsSigningPartial(sessionId, clientPartialSignature) {
|
|
1890
|
+
return this.request("POST", "/api/v2/dkls/signing/partial", {
|
|
1891
|
+
sessionId,
|
|
1892
|
+
clientPartialSignature
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
async initiateDeviceVerification(fingerprint, deviceName) {
|
|
1896
|
+
return this.request("POST", "/api/user/devices/verify/initiate", {
|
|
1897
|
+
fingerprint,
|
|
1898
|
+
deviceName
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
async completeDeviceVerification(verificationId, code, fingerprint) {
|
|
1902
|
+
return this.request("POST", "/api/user/devices/verify/complete", {
|
|
1903
|
+
verificationId,
|
|
1904
|
+
code,
|
|
1905
|
+
fingerprint
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
// src/transport/websocket-client.ts
|
|
1911
|
+
var RECONNECT_DELAYS = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
1912
|
+
var PING_INTERVAL = 3e4;
|
|
1913
|
+
var MESSAGE_QUEUE_MAX = 100;
|
|
1914
|
+
var WebSocketClient = class {
|
|
1915
|
+
constructor(url) {
|
|
1916
|
+
this.ws = null;
|
|
1917
|
+
this.accessToken = null;
|
|
1918
|
+
this.messageHandlers = /* @__PURE__ */ new Map();
|
|
1919
|
+
this.globalHandlers = /* @__PURE__ */ new Set();
|
|
1920
|
+
this.onConnectHandlers = /* @__PURE__ */ new Set();
|
|
1921
|
+
this.onDisconnectHandlers = /* @__PURE__ */ new Set();
|
|
1922
|
+
this.onErrorHandlers = /* @__PURE__ */ new Set();
|
|
1923
|
+
this.reconnectAttempt = 0;
|
|
1924
|
+
this.reconnectTimeout = null;
|
|
1925
|
+
this.pingInterval = null;
|
|
1926
|
+
this.messageQueue = [];
|
|
1927
|
+
this.isConnecting = false;
|
|
1928
|
+
this.url = url;
|
|
1929
|
+
}
|
|
1930
|
+
setAccessToken(token) {
|
|
1931
|
+
this.accessToken = token;
|
|
1932
|
+
}
|
|
1933
|
+
async connect() {
|
|
1934
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
if (this.isConnecting) {
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
this.isConnecting = true;
|
|
1941
|
+
return new Promise((resolve, reject) => {
|
|
1942
|
+
const wsUrl = this.accessToken ? `${this.url}?token=${encodeURIComponent(this.accessToken)}` : this.url;
|
|
1943
|
+
this.ws = new WebSocket(wsUrl);
|
|
1944
|
+
this.ws.onopen = () => {
|
|
1945
|
+
this.isConnecting = false;
|
|
1946
|
+
this.reconnectAttempt = 0;
|
|
1947
|
+
this.startPingInterval();
|
|
1948
|
+
this.flushMessageQueue();
|
|
1949
|
+
this.onConnectHandlers.forEach((handler) => handler());
|
|
1950
|
+
resolve();
|
|
1951
|
+
};
|
|
1952
|
+
this.ws.onmessage = (event) => {
|
|
1953
|
+
this.handleMessage(event.data);
|
|
1954
|
+
};
|
|
1955
|
+
this.ws.onerror = () => {
|
|
1956
|
+
const error = new Error("WebSocket error");
|
|
1957
|
+
this.onErrorHandlers.forEach((handler) => handler(error));
|
|
1958
|
+
};
|
|
1959
|
+
this.ws.onclose = (event) => {
|
|
1960
|
+
this.isConnecting = false;
|
|
1961
|
+
this.stopPingInterval();
|
|
1962
|
+
this.onDisconnectHandlers.forEach((handler) => handler());
|
|
1963
|
+
if (!event.wasClean) {
|
|
1964
|
+
this.scheduleReconnect();
|
|
1965
|
+
}
|
|
1966
|
+
if (this.isConnecting) {
|
|
1967
|
+
reject(new SDKError("Connection failed", "CONNECTION_FAILED"));
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
disconnect() {
|
|
1973
|
+
this.stopReconnect();
|
|
1974
|
+
this.stopPingInterval();
|
|
1975
|
+
if (this.ws) {
|
|
1976
|
+
this.ws.close(1e3, "Client disconnect");
|
|
1977
|
+
this.ws = null;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
isConnected() {
|
|
1981
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
1982
|
+
}
|
|
1983
|
+
send(message) {
|
|
1984
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1985
|
+
this.queueMessage(message);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
this.ws.send(JSON.stringify(message));
|
|
1989
|
+
}
|
|
1990
|
+
on(type, handler) {
|
|
1991
|
+
if (!this.messageHandlers.has(type)) {
|
|
1992
|
+
this.messageHandlers.set(type, /* @__PURE__ */ new Set());
|
|
1993
|
+
}
|
|
1994
|
+
this.messageHandlers.get(type)?.add(handler);
|
|
1995
|
+
return () => {
|
|
1996
|
+
this.messageHandlers.get(type)?.delete(handler);
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
onAny(handler) {
|
|
2000
|
+
this.globalHandlers.add(handler);
|
|
2001
|
+
return () => {
|
|
2002
|
+
this.globalHandlers.delete(handler);
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
onConnect(handler) {
|
|
2006
|
+
this.onConnectHandlers.add(handler);
|
|
2007
|
+
return () => {
|
|
2008
|
+
this.onConnectHandlers.delete(handler);
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
onDisconnect(handler) {
|
|
2012
|
+
this.onDisconnectHandlers.add(handler);
|
|
2013
|
+
return () => {
|
|
2014
|
+
this.onDisconnectHandlers.delete(handler);
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
onError(handler) {
|
|
2018
|
+
this.onErrorHandlers.add(handler);
|
|
2019
|
+
return () => {
|
|
2020
|
+
this.onErrorHandlers.delete(handler);
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
waitForMessage(type, sessionId, timeout = 3e4) {
|
|
2024
|
+
return new Promise((resolve, reject) => {
|
|
2025
|
+
const timer = setTimeout(() => {
|
|
2026
|
+
unsubscribe();
|
|
2027
|
+
reject(new SDKError(`Timeout waiting for ${type}`, "MESSAGE_TIMEOUT"));
|
|
2028
|
+
}, timeout);
|
|
2029
|
+
const unsubscribe = this.on(type, (message) => {
|
|
2030
|
+
if (message.sessionId === sessionId) {
|
|
2031
|
+
clearTimeout(timer);
|
|
2032
|
+
unsubscribe();
|
|
2033
|
+
resolve(message);
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
handleMessage(data) {
|
|
2039
|
+
let message;
|
|
2040
|
+
try {
|
|
2041
|
+
message = JSON.parse(data);
|
|
2042
|
+
} catch {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
if (message.type === "pong") {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
this.globalHandlers.forEach((handler) => handler(message));
|
|
2049
|
+
const typeHandlers = this.messageHandlers.get(message.type);
|
|
2050
|
+
if (typeHandlers) {
|
|
2051
|
+
typeHandlers.forEach((handler) => handler(message));
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
queueMessage(message) {
|
|
2055
|
+
if (this.messageQueue.length >= MESSAGE_QUEUE_MAX) {
|
|
2056
|
+
this.messageQueue.shift();
|
|
2057
|
+
}
|
|
2058
|
+
this.messageQueue.push({
|
|
2059
|
+
message,
|
|
2060
|
+
timestamp: Date.now()
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
flushMessageQueue() {
|
|
2064
|
+
const now = Date.now();
|
|
2065
|
+
const validMessages = this.messageQueue.filter(
|
|
2066
|
+
(item) => now - item.timestamp < 6e4
|
|
2067
|
+
);
|
|
2068
|
+
this.messageQueue = [];
|
|
2069
|
+
for (const item of validMessages) {
|
|
2070
|
+
this.send(item.message);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
scheduleReconnect() {
|
|
2074
|
+
if (this.reconnectTimeout) {
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
|
|
2078
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
2079
|
+
this.reconnectTimeout = null;
|
|
2080
|
+
this.reconnectAttempt++;
|
|
2081
|
+
this.connect().catch(() => {
|
|
2082
|
+
});
|
|
2083
|
+
}, delay);
|
|
2084
|
+
}
|
|
2085
|
+
stopReconnect() {
|
|
2086
|
+
if (this.reconnectTimeout) {
|
|
2087
|
+
clearTimeout(this.reconnectTimeout);
|
|
2088
|
+
this.reconnectTimeout = null;
|
|
2089
|
+
}
|
|
2090
|
+
this.reconnectAttempt = 0;
|
|
2091
|
+
}
|
|
2092
|
+
startPingInterval() {
|
|
2093
|
+
this.pingInterval = setInterval(() => {
|
|
2094
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
2095
|
+
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
2096
|
+
}
|
|
2097
|
+
}, PING_INTERVAL);
|
|
2098
|
+
}
|
|
2099
|
+
stopPingInterval() {
|
|
2100
|
+
if (this.pingInterval) {
|
|
2101
|
+
clearInterval(this.pingInterval);
|
|
2102
|
+
this.pingInterval = null;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
var CURVE_ORDER2 = secp256k1.secp256k1.CURVE.n;
|
|
2107
|
+
function mod2(n, m) {
|
|
2108
|
+
return (n % m + m) % m;
|
|
2109
|
+
}
|
|
2110
|
+
function generateRandomScalar2() {
|
|
2111
|
+
const bytes = new Uint8Array(32);
|
|
2112
|
+
crypto.getRandomValues(bytes);
|
|
2113
|
+
let result = 0n;
|
|
2114
|
+
for (const byte of bytes) {
|
|
2115
|
+
result = (result << 8n) + BigInt(byte);
|
|
2116
|
+
}
|
|
2117
|
+
const scalar = mod2(result, CURVE_ORDER2);
|
|
2118
|
+
return scalar === 0n ? generateRandomScalar2() : scalar;
|
|
2119
|
+
}
|
|
2120
|
+
function generateNonceShare() {
|
|
2121
|
+
return {
|
|
2122
|
+
k: generateRandomScalar2(),
|
|
2123
|
+
gamma: generateRandomScalar2()
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function createNonceCommitment(partyId, nonce) {
|
|
2127
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
2128
|
+
const D = G2.multiply(nonce.gamma);
|
|
2129
|
+
const E = G2.multiply(nonce.k);
|
|
2130
|
+
const proof = createNonceProof(partyId, nonce.k, nonce.gamma, D, E);
|
|
2131
|
+
return {
|
|
2132
|
+
partyId,
|
|
2133
|
+
D: D.toHex(true),
|
|
2134
|
+
E: E.toHex(true),
|
|
2135
|
+
proof
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
function verifyNonceCommitment(commitment) {
|
|
2139
|
+
try {
|
|
2140
|
+
secp256k1.secp256k1.ProjectivePoint.fromHex(commitment.D);
|
|
2141
|
+
secp256k1.secp256k1.ProjectivePoint.fromHex(commitment.E);
|
|
2142
|
+
return true;
|
|
2143
|
+
} catch {
|
|
2144
|
+
return false;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function verifyNonceProof(commitment) {
|
|
2148
|
+
try {
|
|
2149
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
2150
|
+
const D = secp256k1.secp256k1.ProjectivePoint.fromHex(commitment.D);
|
|
2151
|
+
const E = secp256k1.secp256k1.ProjectivePoint.fromHex(commitment.E);
|
|
2152
|
+
const { R1, R2, s1, s2 } = JSON.parse(commitment.proof);
|
|
2153
|
+
if (!/^[0-9a-fA-F]{1,64}$/.test(s1) || !/^[0-9a-fA-F]{1,64}$/.test(s2)) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
const R1Point = secp256k1.secp256k1.ProjectivePoint.fromHex(R1);
|
|
2157
|
+
const R2Point = secp256k1.secp256k1.ProjectivePoint.fromHex(R2);
|
|
2158
|
+
const s1Scalar = BigInt(`0x${s1}`);
|
|
2159
|
+
const s2Scalar = BigInt(`0x${s2}`);
|
|
2160
|
+
if (s1Scalar <= 0n || s1Scalar >= CURVE_ORDER2) return false;
|
|
2161
|
+
if (s2Scalar <= 0n || s2Scalar >= CURVE_ORDER2) return false;
|
|
2162
|
+
const challenge = computeNonceChallenge(
|
|
2163
|
+
commitment.partyId,
|
|
2164
|
+
commitment.D,
|
|
2165
|
+
commitment.E,
|
|
2166
|
+
R1,
|
|
2167
|
+
R2
|
|
2168
|
+
);
|
|
2169
|
+
const leftSide1 = G2.multiply(s1Scalar);
|
|
2170
|
+
const rightSide1 = R1Point.add(D.multiply(challenge));
|
|
2171
|
+
if (!leftSide1.equals(rightSide1)) return false;
|
|
2172
|
+
const leftSide2 = G2.multiply(s2Scalar);
|
|
2173
|
+
const rightSide2 = R2Point.add(E.multiply(challenge));
|
|
2174
|
+
if (!leftSide2.equals(rightSide2)) return false;
|
|
2175
|
+
return true;
|
|
2176
|
+
} catch {
|
|
2177
|
+
return false;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
function createNonceProof(partyId, k, gamma, D, E) {
|
|
2181
|
+
const G2 = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
2182
|
+
const r1 = generateRandomScalar2();
|
|
2183
|
+
const r2 = generateRandomScalar2();
|
|
2184
|
+
const R1 = G2.multiply(r1);
|
|
2185
|
+
const R2 = G2.multiply(r2);
|
|
2186
|
+
const challenge = computeNonceChallenge(
|
|
2187
|
+
partyId,
|
|
2188
|
+
D.toHex(true),
|
|
2189
|
+
E.toHex(true),
|
|
2190
|
+
R1.toHex(true),
|
|
2191
|
+
R2.toHex(true)
|
|
2192
|
+
);
|
|
2193
|
+
const s1 = mod2(r1 + gamma * challenge, CURVE_ORDER2);
|
|
2194
|
+
const s2 = mod2(r2 + k * challenge, CURVE_ORDER2);
|
|
2195
|
+
return JSON.stringify({
|
|
2196
|
+
R1: R1.toHex(true),
|
|
2197
|
+
R2: R2.toHex(true),
|
|
2198
|
+
s1: s1.toString(16).padStart(64, "0"),
|
|
2199
|
+
s2: s2.toString(16).padStart(64, "0")
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
function computeNonceChallenge(partyId, D, E, R1, R2) {
|
|
2203
|
+
const input = `${partyId}:${D}:${E}:${R1}:${R2}`;
|
|
2204
|
+
const hash = sha256.sha256(utils.utf8ToBytes(input));
|
|
2205
|
+
return mod2(BigInt(`0x${utils.bytesToHex(hash)}`), CURVE_ORDER2);
|
|
2206
|
+
}
|
|
2207
|
+
function combineNonceCommitments(commitments) {
|
|
2208
|
+
let combinedD = secp256k1.secp256k1.ProjectivePoint.fromHex(commitments[0].D);
|
|
2209
|
+
let combinedE = secp256k1.secp256k1.ProjectivePoint.fromHex(commitments[0].E);
|
|
2210
|
+
for (let i = 1; i < commitments.length; i++) {
|
|
2211
|
+
combinedD = combinedD.add(
|
|
2212
|
+
secp256k1.secp256k1.ProjectivePoint.fromHex(commitments[i].D)
|
|
2213
|
+
);
|
|
2214
|
+
combinedE = combinedE.add(
|
|
2215
|
+
secp256k1.secp256k1.ProjectivePoint.fromHex(commitments[i].E)
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
R: combinedE.toHex(true),
|
|
2220
|
+
combinedGamma: combinedD.toHex(true)
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function computeR(combinedE) {
|
|
2224
|
+
const R = secp256k1.secp256k1.ProjectivePoint.fromHex(combinedE);
|
|
2225
|
+
const rFull = R.toAffine();
|
|
2226
|
+
const r = mod2(rFull.x, CURVE_ORDER2);
|
|
2227
|
+
return {
|
|
2228
|
+
r,
|
|
2229
|
+
R: R.toHex(true),
|
|
2230
|
+
rPoint: { x: rFull.x, y: rFull.y }
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
var CURVE_ORDER3 = secp256k1.secp256k1.CURVE.n;
|
|
2234
|
+
var G = secp256k1.secp256k1.ProjectivePoint.BASE;
|
|
2235
|
+
function mod3(n, m) {
|
|
2236
|
+
return (n % m + m) % m;
|
|
2237
|
+
}
|
|
2238
|
+
function modInverse(a, m) {
|
|
2239
|
+
let [oldR, r] = [a, m];
|
|
2240
|
+
let [oldS, s] = [1n, 0n];
|
|
2241
|
+
while (r !== 0n) {
|
|
2242
|
+
const quotient = oldR / r;
|
|
2243
|
+
[oldR, r] = [r, oldR - quotient * r];
|
|
2244
|
+
[oldS, s] = [s, oldS - quotient * s];
|
|
2245
|
+
}
|
|
2246
|
+
return mod3(oldS, m);
|
|
2247
|
+
}
|
|
2248
|
+
function computeLagrangeCoefficient(partyId, participatingParties) {
|
|
2249
|
+
let numerator = 1n;
|
|
2250
|
+
let denominator = 1n;
|
|
2251
|
+
const i = BigInt(partyId);
|
|
2252
|
+
for (const j of participatingParties) {
|
|
2253
|
+
if (j !== partyId) {
|
|
2254
|
+
const jBig = BigInt(j);
|
|
2255
|
+
numerator = mod3(numerator * (CURVE_ORDER3 - jBig), CURVE_ORDER3);
|
|
2256
|
+
denominator = mod3(denominator * mod3(i - jBig, CURVE_ORDER3), CURVE_ORDER3);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
const denominatorInverse = modInverse(denominator, CURVE_ORDER3);
|
|
2260
|
+
return mod3(numerator * denominatorInverse, CURVE_ORDER3);
|
|
2261
|
+
}
|
|
2262
|
+
function computePartialSignature(partyId, keyShare, nonceK, _nonceGamma, messageHash, r, participatingParties) {
|
|
2263
|
+
const lambda = computeLagrangeCoefficient(partyId, participatingParties);
|
|
2264
|
+
const kInverse = modInverse(nonceK, CURVE_ORDER3);
|
|
2265
|
+
const adjustedShare = mod3(keyShare * lambda, CURVE_ORDER3);
|
|
2266
|
+
const sigma = mod3(kInverse * (messageHash + r * adjustedShare), CURVE_ORDER3);
|
|
2267
|
+
const publicShare = G.multiply(keyShare).toHex(true);
|
|
2268
|
+
const nonceCommitment = G.multiply(nonceK).toHex(true);
|
|
2269
|
+
return {
|
|
2270
|
+
partyId,
|
|
2271
|
+
sigma,
|
|
2272
|
+
publicShare,
|
|
2273
|
+
nonceCommitment
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
function verifyPartialSignature(partial, expectedPublicShare, expectedNonceCommitment, r, messageHash, participatingParties) {
|
|
2277
|
+
if (partial.sigma <= 0n || partial.sigma >= CURVE_ORDER3) {
|
|
2278
|
+
return false;
|
|
2279
|
+
}
|
|
2280
|
+
if (!partial.publicShare || !partial.nonceCommitment) {
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
if (expectedPublicShare && partial.publicShare !== expectedPublicShare) {
|
|
2284
|
+
return false;
|
|
2285
|
+
}
|
|
2286
|
+
if (expectedNonceCommitment && partial.nonceCommitment !== expectedNonceCommitment) {
|
|
2287
|
+
return false;
|
|
2288
|
+
}
|
|
2289
|
+
let R_i;
|
|
2290
|
+
try {
|
|
2291
|
+
R_i = secp256k1.secp256k1.ProjectivePoint.fromHex(partial.nonceCommitment);
|
|
2292
|
+
} catch {
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
let P_i;
|
|
2296
|
+
try {
|
|
2297
|
+
P_i = secp256k1.secp256k1.ProjectivePoint.fromHex(partial.publicShare);
|
|
2298
|
+
} catch {
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
const lambda = computeLagrangeCoefficient(
|
|
2302
|
+
partial.partyId,
|
|
2303
|
+
participatingParties
|
|
2304
|
+
);
|
|
2305
|
+
const rTimesLambda = mod3(r * lambda, CURVE_ORDER3);
|
|
2306
|
+
const leftSide = R_i.multiply(partial.sigma);
|
|
2307
|
+
const rightSide = G.multiply(messageHash).add(P_i.multiply(rTimesLambda));
|
|
2308
|
+
return leftSide.equals(rightSide);
|
|
2309
|
+
}
|
|
2310
|
+
function parseMessageHash(hash) {
|
|
2311
|
+
const cleanHash = hash.startsWith("0x") ? hash.slice(2) : hash;
|
|
2312
|
+
return BigInt(`0x${cleanHash}`);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/protocols/signing/signing-client.ts
|
|
2316
|
+
var SigningClient = class {
|
|
2317
|
+
constructor(config) {
|
|
2318
|
+
this.state = null;
|
|
2319
|
+
this.nonceShare = null;
|
|
2320
|
+
this.receivedCommitments = /* @__PURE__ */ new Map();
|
|
2321
|
+
this.expectedPublicShares = /* @__PURE__ */ new Map();
|
|
2322
|
+
this.r = null;
|
|
2323
|
+
this.apiClient = config.apiClient;
|
|
2324
|
+
this.wsClient = config.wsClient;
|
|
2325
|
+
this.keyShare = config.keyShare;
|
|
2326
|
+
this.expectedPublicShares = new Map(config.partyPublicShares);
|
|
2327
|
+
this.timeout = config.timeout ?? 3e4;
|
|
2328
|
+
}
|
|
2329
|
+
async sign(request) {
|
|
2330
|
+
try {
|
|
2331
|
+
await this.initializeSession(request);
|
|
2332
|
+
await this.runNonceCommitmentPhase();
|
|
2333
|
+
await this.runPartialSignaturePhase();
|
|
2334
|
+
return await this.completeProtocol();
|
|
2335
|
+
} catch (error) {
|
|
2336
|
+
const message = error instanceof Error ? error.message : "Signing failed";
|
|
2337
|
+
return {
|
|
2338
|
+
success: false,
|
|
2339
|
+
error: message
|
|
2340
|
+
};
|
|
2341
|
+
} finally {
|
|
2342
|
+
this.cleanup();
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
async initializeSession(request) {
|
|
2346
|
+
const { sessionId, participatingParties } = await this.apiClient.initiateSigningSession(
|
|
2347
|
+
request.messageHash,
|
|
2348
|
+
request.messageType
|
|
2349
|
+
);
|
|
2350
|
+
if (participatingParties.length < this.keyShare.threshold) {
|
|
2351
|
+
throw new SDKError(
|
|
2352
|
+
`Insufficient participating parties: got ${participatingParties.length}, need ${this.keyShare.threshold}`,
|
|
2353
|
+
"INSUFFICIENT_PARTIES"
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
this.state = {
|
|
2357
|
+
sessionId,
|
|
2358
|
+
round: "nonce_commitment",
|
|
2359
|
+
messageHash: request.messageHash,
|
|
2360
|
+
participatingParties,
|
|
2361
|
+
nonceCommitments: /* @__PURE__ */ new Map(),
|
|
2362
|
+
partialSignatures: /* @__PURE__ */ new Map()
|
|
2363
|
+
};
|
|
2364
|
+
this.nonceShare = generateNonceShare();
|
|
2365
|
+
if (this.wsClient) {
|
|
2366
|
+
await this.wsClient.connect();
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
async runNonceCommitmentPhase() {
|
|
2370
|
+
if (!this.state || !this.nonceShare) {
|
|
2371
|
+
throw new SDKError("State not initialized", "STATE_ERROR");
|
|
2372
|
+
}
|
|
2373
|
+
const commitment = createNonceCommitment(
|
|
2374
|
+
this.keyShare.partyId,
|
|
2375
|
+
this.nonceShare
|
|
2376
|
+
);
|
|
2377
|
+
await this.apiClient.submitNonceCommitment(
|
|
2378
|
+
this.state.sessionId,
|
|
2379
|
+
this.keyShare.partyId,
|
|
2380
|
+
JSON.stringify(commitment)
|
|
2381
|
+
);
|
|
2382
|
+
this.receivedCommitments.set(this.keyShare.partyId, commitment);
|
|
2383
|
+
const otherCommitments = await this.waitForNonceCommitments();
|
|
2384
|
+
for (const [partyId, comm] of otherCommitments) {
|
|
2385
|
+
if (!verifyNonceCommitment(comm)) {
|
|
2386
|
+
throw new SDKError(
|
|
2387
|
+
`Invalid nonce commitment from party ${partyId}`,
|
|
2388
|
+
"INVALID_NONCE_COMMITMENT"
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
if (!verifyNonceProof(comm)) {
|
|
2392
|
+
throw new SDKError(
|
|
2393
|
+
`Invalid nonce proof from party ${partyId}`,
|
|
2394
|
+
"INVALID_NONCE_PROOF"
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
this.receivedCommitments.set(partyId, comm);
|
|
2398
|
+
}
|
|
2399
|
+
const allCommitments = Array.from(this.receivedCommitments.values());
|
|
2400
|
+
const { R } = combineNonceCommitments(allCommitments);
|
|
2401
|
+
const { r } = computeR(R);
|
|
2402
|
+
this.r = r;
|
|
2403
|
+
this.state.round = "partial_signature";
|
|
2404
|
+
}
|
|
2405
|
+
async runPartialSignaturePhase() {
|
|
2406
|
+
if (!this.state || !this.nonceShare || this.r === null) {
|
|
2407
|
+
throw new SDKError("State not initialized", "STATE_ERROR");
|
|
2408
|
+
}
|
|
2409
|
+
const messageHashBigInt = parseMessageHash(this.state.messageHash);
|
|
2410
|
+
const keyShareBigInt = BigInt(`0x${this.keyShare.privateShare}`);
|
|
2411
|
+
const partial = computePartialSignature(
|
|
2412
|
+
this.keyShare.partyId,
|
|
2413
|
+
keyShareBigInt,
|
|
2414
|
+
this.nonceShare.k,
|
|
2415
|
+
this.nonceShare.gamma,
|
|
2416
|
+
messageHashBigInt,
|
|
2417
|
+
this.r,
|
|
2418
|
+
this.state.participatingParties
|
|
2419
|
+
);
|
|
2420
|
+
await this.apiClient.submitPartialSignature(
|
|
2421
|
+
this.state.sessionId,
|
|
2422
|
+
this.keyShare.partyId,
|
|
2423
|
+
{
|
|
2424
|
+
r: this.r.toString(16).padStart(64, "0"),
|
|
2425
|
+
s: partial.sigma.toString(16).padStart(64, "0"),
|
|
2426
|
+
publicShare: partial.publicShare,
|
|
2427
|
+
nonceCommitment: partial.nonceCommitment
|
|
2428
|
+
}
|
|
2429
|
+
);
|
|
2430
|
+
this.state.partialSignatures.set(this.keyShare.partyId, {
|
|
2431
|
+
partyId: this.keyShare.partyId,
|
|
2432
|
+
r: this.r.toString(16).padStart(64, "0"),
|
|
2433
|
+
s: partial.sigma.toString(16).padStart(64, "0"),
|
|
2434
|
+
publicShare: partial.publicShare,
|
|
2435
|
+
nonceCommitment: partial.nonceCommitment
|
|
2436
|
+
});
|
|
2437
|
+
const otherPartials = await this.waitForPartialSignatures();
|
|
2438
|
+
await this.verifyOtherPartials(otherPartials, messageHashBigInt);
|
|
2439
|
+
this.state.round = "complete";
|
|
2440
|
+
}
|
|
2441
|
+
async waitForPartialSignatures() {
|
|
2442
|
+
const startTime = Date.now();
|
|
2443
|
+
const partials = /* @__PURE__ */ new Map();
|
|
2444
|
+
while (Date.now() - startTime < this.timeout) {
|
|
2445
|
+
const result = await this.apiClient.getPartialSignatures(
|
|
2446
|
+
this.state?.sessionId ?? ""
|
|
2447
|
+
);
|
|
2448
|
+
for (const p of result.partials) {
|
|
2449
|
+
if (p.partyId !== this.keyShare.partyId && !partials.has(p.partyId)) {
|
|
2450
|
+
let sigma;
|
|
2451
|
+
try {
|
|
2452
|
+
if (!/^[0-9a-fA-F]{1,64}$/.test(p.s)) {
|
|
2453
|
+
throw new SDKError(
|
|
2454
|
+
`Invalid sigma hex from party ${p.partyId}`,
|
|
2455
|
+
"INVALID_PARTIAL_FORMAT"
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
sigma = BigInt(`0x${p.s}`);
|
|
2459
|
+
} catch (e) {
|
|
2460
|
+
if (e instanceof SDKError) throw e;
|
|
2461
|
+
throw new SDKError(
|
|
2462
|
+
`Failed to parse sigma from party ${p.partyId}`,
|
|
2463
|
+
"INVALID_PARTIAL_FORMAT"
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
partials.set(p.partyId, {
|
|
2467
|
+
partyId: p.partyId,
|
|
2468
|
+
sigma,
|
|
2469
|
+
publicShare: p.publicShare,
|
|
2470
|
+
nonceCommitment: p.nonceCommitment
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
const expectedCount = (this.state?.participatingParties.length ?? 1) - 1;
|
|
2475
|
+
if (result.ready || partials.size >= expectedCount) {
|
|
2476
|
+
return partials;
|
|
2477
|
+
}
|
|
2478
|
+
await this.delay(500);
|
|
2479
|
+
}
|
|
2480
|
+
throw new SDKError(
|
|
2481
|
+
"Timeout waiting for partial signatures",
|
|
2482
|
+
"PARTIAL_SIGNATURE_TIMEOUT"
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
async verifyOtherPartials(partials, messageHash) {
|
|
2486
|
+
for (const [partyId, partial] of partials) {
|
|
2487
|
+
const expectedNonceCommitment = this.receivedCommitments.get(partyId);
|
|
2488
|
+
if (!expectedNonceCommitment) {
|
|
2489
|
+
throw new SDKError(
|
|
2490
|
+
`Missing nonce commitment for party ${partyId}`,
|
|
2491
|
+
"MISSING_NONCE_COMMITMENT"
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
const expectedPublicShare = this.expectedPublicShares.get(partyId);
|
|
2495
|
+
if (!expectedPublicShare) {
|
|
2496
|
+
throw new SDKError(
|
|
2497
|
+
`Missing expected public share for party ${partyId}. Public shares from DKG must be provided via partyPublicShares config.`,
|
|
2498
|
+
"MISSING_PUBLIC_SHARE"
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
const isValid = verifyPartialSignature(
|
|
2502
|
+
partial,
|
|
2503
|
+
expectedPublicShare,
|
|
2504
|
+
expectedNonceCommitment.E,
|
|
2505
|
+
this.r,
|
|
2506
|
+
messageHash,
|
|
2507
|
+
this.state?.participatingParties ?? []
|
|
2508
|
+
);
|
|
2509
|
+
if (!isValid) {
|
|
2510
|
+
throw new SDKError(
|
|
2511
|
+
`Invalid partial signature from party ${partyId}`,
|
|
2512
|
+
"INVALID_PARTIAL_SIGNATURE"
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
this.state?.partialSignatures.set(partyId, {
|
|
2516
|
+
partyId,
|
|
2517
|
+
r: this.r?.toString(16).padStart(64, "0") ?? "",
|
|
2518
|
+
s: partial.sigma.toString(16).padStart(64, "0"),
|
|
2519
|
+
publicShare: partial.publicShare,
|
|
2520
|
+
nonceCommitment: partial.nonceCommitment
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async completeProtocol() {
|
|
2525
|
+
if (!this.state || this.r === null) {
|
|
2526
|
+
throw new SDKError("Protocol not complete", "PROTOCOL_INCOMPLETE");
|
|
2527
|
+
}
|
|
2528
|
+
const result = await this.waitForSigningResult();
|
|
2529
|
+
if (!result.complete || !result.signature) {
|
|
2530
|
+
throw new SDKError("Signing not complete", "SIGNING_INCOMPLETE");
|
|
2531
|
+
}
|
|
2532
|
+
return {
|
|
2533
|
+
success: true,
|
|
2534
|
+
signature: result.signature
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
async waitForNonceCommitments() {
|
|
2538
|
+
const startTime = Date.now();
|
|
2539
|
+
const commitments = /* @__PURE__ */ new Map();
|
|
2540
|
+
while (Date.now() - startTime < this.timeout) {
|
|
2541
|
+
const result = await this.apiClient.getNonceCommitments(
|
|
2542
|
+
this.state?.sessionId ?? ""
|
|
2543
|
+
);
|
|
2544
|
+
for (const [partyIdStr, commitmentStr] of Object.entries(
|
|
2545
|
+
result.commitments
|
|
2546
|
+
)) {
|
|
2547
|
+
const partyId = Number.parseInt(partyIdStr, 10);
|
|
2548
|
+
if (partyId !== this.keyShare.partyId && !commitments.has(partyId)) {
|
|
2549
|
+
commitments.set(partyId, JSON.parse(commitmentStr));
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
if (result.ready) {
|
|
2553
|
+
return commitments;
|
|
2554
|
+
}
|
|
2555
|
+
const expectedCount = (this.state?.participatingParties.length ?? 1) - 1;
|
|
2556
|
+
if (commitments.size >= expectedCount) {
|
|
2557
|
+
return commitments;
|
|
2558
|
+
}
|
|
2559
|
+
await this.delay(500);
|
|
2560
|
+
}
|
|
2561
|
+
throw new SDKError(
|
|
2562
|
+
"Timeout waiting for nonce commitments",
|
|
2563
|
+
"NONCE_COMMITMENT_TIMEOUT"
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2566
|
+
async waitForSigningResult() {
|
|
2567
|
+
const startTime = Date.now();
|
|
2568
|
+
while (Date.now() - startTime < this.timeout) {
|
|
2569
|
+
const result = await this.apiClient.getSigningResult(
|
|
2570
|
+
this.state?.sessionId ?? ""
|
|
2571
|
+
);
|
|
2572
|
+
if (result.complete && result.signature) {
|
|
2573
|
+
return result;
|
|
2574
|
+
}
|
|
2575
|
+
await this.delay(500);
|
|
2576
|
+
}
|
|
2577
|
+
throw new SDKError(
|
|
2578
|
+
"Timeout waiting for signing result",
|
|
2579
|
+
"SIGNING_RESULT_TIMEOUT"
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
delay(ms) {
|
|
2583
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2584
|
+
}
|
|
2585
|
+
cleanup() {
|
|
2586
|
+
this.state = null;
|
|
2587
|
+
this.nonceShare = null;
|
|
2588
|
+
this.receivedCommitments.clear();
|
|
2589
|
+
this.expectedPublicShares.clear();
|
|
2590
|
+
this.r = null;
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
function deriveEOAAddress(publicKey) {
|
|
2594
|
+
let pubKeyHex = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
|
|
2595
|
+
if (pubKeyHex.startsWith("04")) {
|
|
2596
|
+
pubKeyHex = pubKeyHex.slice(2);
|
|
2597
|
+
}
|
|
2598
|
+
if (pubKeyHex.length !== 128) {
|
|
2599
|
+
throw new Error(
|
|
2600
|
+
`Invalid public key length: expected 128 hex chars (64 bytes), got ${pubKeyHex.length}`
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
const pubKeyBytes = utils.hexToBytes(pubKeyHex);
|
|
2604
|
+
const hash = sha3.keccak_256(pubKeyBytes);
|
|
2605
|
+
const addressBytes = hash.slice(-20);
|
|
2606
|
+
return `0x${utils.bytesToHex(addressBytes)}`;
|
|
2607
|
+
}
|
|
2608
|
+
function checksumAddress(address) {
|
|
2609
|
+
const addr = address.toLowerCase().replace("0x", "");
|
|
2610
|
+
const hash = utils.bytesToHex(sha3.keccak_256(new TextEncoder().encode(addr)));
|
|
2611
|
+
let checksummed = "0x";
|
|
2612
|
+
for (let i = 0; i < 40; i++) {
|
|
2613
|
+
if (Number.parseInt(hash[i], 16) >= 8) {
|
|
2614
|
+
checksummed += addr[i].toUpperCase();
|
|
2615
|
+
} else {
|
|
2616
|
+
checksummed += addr[i];
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return checksummed;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// src/wallet/smart-wallet.ts
|
|
2623
|
+
function stripHexPrefix(value) {
|
|
2624
|
+
return value.startsWith("0x") ? value.slice(2) : value;
|
|
2625
|
+
}
|
|
2626
|
+
var DEFAULT_ENTRY_POINT = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
|
|
2627
|
+
var DEFAULT_FACTORY = "0x9406Cc6185a346906296840746125a0E44976454";
|
|
2628
|
+
var GET_ADDRESS_SELECTOR = "8cb84e18";
|
|
2629
|
+
var SmartWallet = class {
|
|
2630
|
+
constructor(config) {
|
|
2631
|
+
this._smartWalletAddress = null;
|
|
2632
|
+
this._isDeployed = null;
|
|
2633
|
+
this._cachedNonce = null;
|
|
2634
|
+
this.apiClient = config.apiClient;
|
|
2635
|
+
this.keyShare = config.keyShare;
|
|
2636
|
+
this.partyPublicShares = config.partyPublicShares;
|
|
2637
|
+
this.publicKey = config.publicKey;
|
|
2638
|
+
this.chainId = config.chainId;
|
|
2639
|
+
this.rpcConnection = config.rpcConnection;
|
|
2640
|
+
this.bundlerUrl = config.bundlerUrl;
|
|
2641
|
+
this.entryPointAddress = config.entryPointAddress ?? DEFAULT_ENTRY_POINT;
|
|
2642
|
+
this.factoryAddress = config.factoryAddress ?? DEFAULT_FACTORY;
|
|
2643
|
+
this.paymasterUrl = config.paymasterUrl;
|
|
2644
|
+
this._eoaAddress = deriveEOAAddress(this.publicKey);
|
|
2645
|
+
}
|
|
2646
|
+
get eoaAddress() {
|
|
2647
|
+
return checksumAddress(this._eoaAddress);
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* @deprecated Use getSmartWalletAddress() instead.
|
|
2651
|
+
*/
|
|
2652
|
+
get smartWalletAddress() {
|
|
2653
|
+
if (this._smartWalletAddress) {
|
|
2654
|
+
return checksumAddress(this._smartWalletAddress);
|
|
2655
|
+
}
|
|
2656
|
+
throw new Error(
|
|
2657
|
+
"SmartWallet.smartWalletAddress is deprecated. Use 'await wallet.getSmartWalletAddress()' instead."
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
async getSmartWalletAddress() {
|
|
2661
|
+
if (this._smartWalletAddress) {
|
|
2662
|
+
return checksumAddress(this._smartWalletAddress);
|
|
2663
|
+
}
|
|
2664
|
+
const ownerPadded = this._eoaAddress.slice(2).padStart(64, "0");
|
|
2665
|
+
const saltPadded = "0".padStart(64, "0");
|
|
2666
|
+
const result = await this.rpcConnection.call("eth_call", [
|
|
2667
|
+
{
|
|
2668
|
+
to: this.factoryAddress,
|
|
2669
|
+
data: `0x${GET_ADDRESS_SELECTOR}${ownerPadded}${saltPadded}`
|
|
2670
|
+
},
|
|
2671
|
+
"latest"
|
|
2672
|
+
]);
|
|
2673
|
+
this._smartWalletAddress = `0x${result.slice(-40)}`;
|
|
2674
|
+
return checksumAddress(this._smartWalletAddress);
|
|
2675
|
+
}
|
|
2676
|
+
async getWalletInfo() {
|
|
2677
|
+
return {
|
|
2678
|
+
eoaAddress: this.eoaAddress,
|
|
2679
|
+
smartWalletAddress: await this.getSmartWalletAddress(),
|
|
2680
|
+
publicKey: this.publicKey,
|
|
2681
|
+
chainId: this.chainId
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
async buildUserOperation(transactions) {
|
|
2685
|
+
const txArray = Array.isArray(transactions) ? transactions : [transactions];
|
|
2686
|
+
const callData = this.encodeExecuteBatch(txArray);
|
|
2687
|
+
const smartWalletAddress = await this.getSmartWalletAddress();
|
|
2688
|
+
const nonce = await this.getNonce();
|
|
2689
|
+
const isDeployed = await this.isDeployed();
|
|
2690
|
+
const initCode = isDeployed ? "0x" : this.getInitCode();
|
|
2691
|
+
const feeData = await this.getFeeData();
|
|
2692
|
+
const userOp = {
|
|
2693
|
+
sender: smartWalletAddress,
|
|
2694
|
+
nonce,
|
|
2695
|
+
initCode,
|
|
2696
|
+
callData,
|
|
2697
|
+
callGasLimit: 200000n,
|
|
2698
|
+
verificationGasLimit: isDeployed ? 100000n : 400000n,
|
|
2699
|
+
preVerificationGas: 50000n,
|
|
2700
|
+
maxFeePerGas: feeData.maxFeePerGas,
|
|
2701
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
|
|
2702
|
+
paymasterAndData: "0x",
|
|
2703
|
+
signature: "0x"
|
|
2704
|
+
};
|
|
2705
|
+
if (this.paymasterUrl) {
|
|
2706
|
+
const paymasterData = await this.getPaymasterData(userOp);
|
|
2707
|
+
userOp.paymasterAndData = paymasterData;
|
|
2708
|
+
}
|
|
2709
|
+
return userOp;
|
|
2710
|
+
}
|
|
2711
|
+
async signUserOperation(userOp) {
|
|
2712
|
+
const userOpHash = this.computeUserOpHash(userOp);
|
|
2713
|
+
const signingClient = new SigningClient({
|
|
2714
|
+
apiClient: this.apiClient,
|
|
2715
|
+
keyShare: this.keyShare,
|
|
2716
|
+
partyPublicShares: this.partyPublicShares
|
|
2717
|
+
});
|
|
2718
|
+
const result = await signingClient.sign({
|
|
2719
|
+
messageHash: userOpHash,
|
|
2720
|
+
messageType: "transaction"
|
|
2721
|
+
});
|
|
2722
|
+
if (!result.success || !result.signature) {
|
|
2723
|
+
throw new SDKError(result.error ?? "Signing failed", "SIGNING_FAILED");
|
|
2724
|
+
}
|
|
2725
|
+
return {
|
|
2726
|
+
...userOp,
|
|
2727
|
+
signature: result.signature.fullSignature
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
async sendUserOperation(userOp) {
|
|
2731
|
+
const signedUserOp = userOp.signature === "0x" ? await this.signUserOperation(userOp) : userOp;
|
|
2732
|
+
const userOpHex = this.userOpToHex(signedUserOp);
|
|
2733
|
+
const response = await fetch(this.bundlerUrl, {
|
|
2734
|
+
method: "POST",
|
|
2735
|
+
headers: { "Content-Type": "application/json" },
|
|
2736
|
+
body: JSON.stringify({
|
|
2737
|
+
jsonrpc: "2.0",
|
|
2738
|
+
id: 1,
|
|
2739
|
+
method: "eth_sendUserOperation",
|
|
2740
|
+
params: [userOpHex, this.entryPointAddress]
|
|
2741
|
+
})
|
|
2742
|
+
});
|
|
2743
|
+
const data = await response.json();
|
|
2744
|
+
if (data.error) {
|
|
2745
|
+
throw new SDKError(
|
|
2746
|
+
`Bundler error: ${data.error.message}`,
|
|
2747
|
+
"BUNDLER_ERROR"
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
const userOpHash = data.result;
|
|
2751
|
+
return {
|
|
2752
|
+
userOpHash,
|
|
2753
|
+
wait: async () => {
|
|
2754
|
+
await this.waitForUserOperationReceipt(userOpHash);
|
|
2755
|
+
this._isDeployed = true;
|
|
2756
|
+
this._cachedNonce = null;
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
async waitForUserOperationReceipt(userOpHash) {
|
|
2761
|
+
const maxAttempts = 60;
|
|
2762
|
+
const intervalMs = 2e3;
|
|
2763
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
2764
|
+
const response = await fetch(this.bundlerUrl, {
|
|
2765
|
+
method: "POST",
|
|
2766
|
+
headers: { "Content-Type": "application/json" },
|
|
2767
|
+
body: JSON.stringify({
|
|
2768
|
+
jsonrpc: "2.0",
|
|
2769
|
+
id: 1,
|
|
2770
|
+
method: "eth_getUserOperationReceipt",
|
|
2771
|
+
params: [userOpHash]
|
|
2772
|
+
})
|
|
2773
|
+
});
|
|
2774
|
+
const data = await response.json();
|
|
2775
|
+
if (data.result) {
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
2779
|
+
}
|
|
2780
|
+
throw new SDKError("UserOperation receipt not found", "TIMEOUT");
|
|
2781
|
+
}
|
|
2782
|
+
userOpToHex(userOp) {
|
|
2783
|
+
return {
|
|
2784
|
+
sender: userOp.sender,
|
|
2785
|
+
nonce: `0x${userOp.nonce.toString(16)}`,
|
|
2786
|
+
initCode: userOp.initCode,
|
|
2787
|
+
callData: userOp.callData,
|
|
2788
|
+
callGasLimit: `0x${userOp.callGasLimit.toString(16)}`,
|
|
2789
|
+
verificationGasLimit: `0x${userOp.verificationGasLimit.toString(16)}`,
|
|
2790
|
+
preVerificationGas: `0x${userOp.preVerificationGas.toString(16)}`,
|
|
2791
|
+
maxFeePerGas: `0x${userOp.maxFeePerGas.toString(16)}`,
|
|
2792
|
+
maxPriorityFeePerGas: `0x${userOp.maxPriorityFeePerGas.toString(16)}`,
|
|
2793
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
2794
|
+
signature: userOp.signature
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
async signMessage(message) {
|
|
2798
|
+
const messageBytes = typeof message === "string" ? utils.utf8ToBytes(message) : message;
|
|
2799
|
+
const prefix = utils.utf8ToBytes(
|
|
2800
|
+
`Ethereum Signed Message:
|
|
2801
|
+
${messageBytes.length}`
|
|
2802
|
+
);
|
|
2803
|
+
const prefixedMessage = new Uint8Array(prefix.length + messageBytes.length);
|
|
2804
|
+
prefixedMessage.set(prefix);
|
|
2805
|
+
prefixedMessage.set(messageBytes, prefix.length);
|
|
2806
|
+
const messageHash = `0x${utils.bytesToHex(sha3.keccak_256(prefixedMessage))}`;
|
|
2807
|
+
const signingClient = new SigningClient({
|
|
2808
|
+
apiClient: this.apiClient,
|
|
2809
|
+
keyShare: this.keyShare,
|
|
2810
|
+
partyPublicShares: this.partyPublicShares
|
|
2811
|
+
});
|
|
2812
|
+
const result = await signingClient.sign({
|
|
2813
|
+
messageHash,
|
|
2814
|
+
messageType: "message"
|
|
2815
|
+
});
|
|
2816
|
+
if (!result.success || !result.signature) {
|
|
2817
|
+
throw new SDKError(
|
|
2818
|
+
result.error ?? "Message signing failed",
|
|
2819
|
+
"MESSAGE_SIGNING_FAILED"
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
return result.signature;
|
|
2823
|
+
}
|
|
2824
|
+
async signTypedData(domain, types, primaryType, value) {
|
|
2825
|
+
const domainSeparator = this.hashTypedDataDomain(domain);
|
|
2826
|
+
const structHash = this.hashTypedDataStruct(types, primaryType, value);
|
|
2827
|
+
const messageHash = `0x${utils.bytesToHex(
|
|
2828
|
+
sha3.keccak_256(
|
|
2829
|
+
utils.hexToBytes(`1901${domainSeparator.slice(2)}${structHash.slice(2)}`)
|
|
2830
|
+
)
|
|
2831
|
+
)}`;
|
|
2832
|
+
const signingClient = new SigningClient({
|
|
2833
|
+
apiClient: this.apiClient,
|
|
2834
|
+
keyShare: this.keyShare,
|
|
2835
|
+
partyPublicShares: this.partyPublicShares
|
|
2836
|
+
});
|
|
2837
|
+
const result = await signingClient.sign({
|
|
2838
|
+
messageHash,
|
|
2839
|
+
messageType: "typed_data"
|
|
2840
|
+
});
|
|
2841
|
+
if (!result.success || !result.signature) {
|
|
2842
|
+
throw new SDKError(
|
|
2843
|
+
result.error ?? "Typed data signing failed",
|
|
2844
|
+
"TYPED_DATA_SIGNING_FAILED"
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
return result.signature;
|
|
2848
|
+
}
|
|
2849
|
+
computeUserOpHash(userOp) {
|
|
2850
|
+
const packed = this.packUserOp(userOp);
|
|
2851
|
+
const userOpHash = sha3.keccak_256(packed);
|
|
2852
|
+
const finalHash = sha3.keccak_256(
|
|
2853
|
+
utils.hexToBytes(
|
|
2854
|
+
utils.bytesToHex(userOpHash) + this.entryPointAddress.slice(2).toLowerCase().padStart(64, "0") + this.chainId.toString(16).padStart(64, "0")
|
|
2855
|
+
)
|
|
2856
|
+
);
|
|
2857
|
+
return `0x${utils.bytesToHex(finalHash)}`;
|
|
2858
|
+
}
|
|
2859
|
+
packUserOp(userOp) {
|
|
2860
|
+
const initCodeHash = sha3.keccak_256(utils.hexToBytes(userOp.initCode.slice(2) || ""));
|
|
2861
|
+
const callDataHash = sha3.keccak_256(utils.hexToBytes(userOp.callData.slice(2) || ""));
|
|
2862
|
+
const paymasterAndDataHash = sha3.keccak_256(
|
|
2863
|
+
utils.hexToBytes(userOp.paymasterAndData.slice(2) || "")
|
|
2864
|
+
);
|
|
2865
|
+
const packed = userOp.sender.slice(2).toLowerCase().padStart(64, "0") + userOp.nonce.toString(16).padStart(64, "0") + utils.bytesToHex(initCodeHash) + utils.bytesToHex(callDataHash) + userOp.callGasLimit.toString(16).padStart(64, "0") + userOp.verificationGasLimit.toString(16).padStart(64, "0") + userOp.preVerificationGas.toString(16).padStart(64, "0") + userOp.maxFeePerGas.toString(16).padStart(64, "0") + userOp.maxPriorityFeePerGas.toString(16).padStart(64, "0") + utils.bytesToHex(paymasterAndDataHash);
|
|
2866
|
+
return utils.hexToBytes(packed);
|
|
2867
|
+
}
|
|
2868
|
+
encodeExecuteBatch(transactions) {
|
|
2869
|
+
if (transactions.length === 1) {
|
|
2870
|
+
const tx = transactions[0];
|
|
2871
|
+
const selector = "b61d27f6";
|
|
2872
|
+
const to = tx.to.slice(2).toLowerCase().padStart(64, "0");
|
|
2873
|
+
const value = (tx.value ?? 0n).toString(16).padStart(64, "0");
|
|
2874
|
+
const dataOffset = "60".padStart(64, "0");
|
|
2875
|
+
const data = tx.data?.slice(2) ?? "";
|
|
2876
|
+
const dataLength = (data.length / 2).toString(16).padStart(64, "0");
|
|
2877
|
+
return `0x${selector}${to}${value}${dataOffset}${dataLength}${data}`;
|
|
2878
|
+
}
|
|
2879
|
+
const n = transactions.length;
|
|
2880
|
+
const destArraySize = 32 + n * 32;
|
|
2881
|
+
const valueArraySize = 32 + n * 32;
|
|
2882
|
+
const destOffset = 96;
|
|
2883
|
+
const valueOffset = destOffset + destArraySize;
|
|
2884
|
+
const funcOffset = valueOffset + valueArraySize;
|
|
2885
|
+
let encoded = "18dfb3c7";
|
|
2886
|
+
encoded += destOffset.toString(16).padStart(64, "0");
|
|
2887
|
+
encoded += valueOffset.toString(16).padStart(64, "0");
|
|
2888
|
+
encoded += funcOffset.toString(16).padStart(64, "0");
|
|
2889
|
+
encoded += n.toString(16).padStart(64, "0");
|
|
2890
|
+
for (const tx of transactions) {
|
|
2891
|
+
encoded += tx.to.slice(2).toLowerCase().padStart(64, "0");
|
|
2892
|
+
}
|
|
2893
|
+
encoded += n.toString(16).padStart(64, "0");
|
|
2894
|
+
for (const tx of transactions) {
|
|
2895
|
+
encoded += (tx.value ?? 0n).toString(16).padStart(64, "0");
|
|
2896
|
+
}
|
|
2897
|
+
const funcDatas = transactions.map((tx) => {
|
|
2898
|
+
const data = tx.data ?? "0x";
|
|
2899
|
+
return data.startsWith("0x") ? data.slice(2) : data;
|
|
2900
|
+
});
|
|
2901
|
+
const funcElementOffsets = [];
|
|
2902
|
+
let currentOffset = 32 + n * 32;
|
|
2903
|
+
for (const funcData of funcDatas) {
|
|
2904
|
+
funcElementOffsets.push(currentOffset);
|
|
2905
|
+
const dataLen = funcData.length / 2;
|
|
2906
|
+
const paddedLen = Math.ceil(dataLen / 32) * 32;
|
|
2907
|
+
currentOffset += 32 + paddedLen;
|
|
2908
|
+
}
|
|
2909
|
+
encoded += n.toString(16).padStart(64, "0");
|
|
2910
|
+
for (const offset of funcElementOffsets) {
|
|
2911
|
+
encoded += offset.toString(16).padStart(64, "0");
|
|
2912
|
+
}
|
|
2913
|
+
for (const funcData of funcDatas) {
|
|
2914
|
+
const dataLen = funcData.length / 2;
|
|
2915
|
+
encoded += dataLen.toString(16).padStart(64, "0");
|
|
2916
|
+
if (funcData.length > 0) {
|
|
2917
|
+
const paddedLen = Math.ceil(dataLen / 32) * 32;
|
|
2918
|
+
encoded += funcData.padEnd(paddedLen * 2, "0");
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
return `0x${encoded}`;
|
|
2922
|
+
}
|
|
2923
|
+
getInitCode() {
|
|
2924
|
+
const selector = "5fbfb9cf";
|
|
2925
|
+
const owner = this._eoaAddress.slice(2).toLowerCase().padStart(64, "0");
|
|
2926
|
+
const salt = "0".padStart(64, "0");
|
|
2927
|
+
return this.factoryAddress.toLowerCase() + selector + owner + salt;
|
|
2928
|
+
}
|
|
2929
|
+
async getNonce() {
|
|
2930
|
+
const isDeployed = await this.isDeployed();
|
|
2931
|
+
if (!isDeployed) {
|
|
2932
|
+
return 0n;
|
|
2933
|
+
}
|
|
2934
|
+
if (this._cachedNonce !== null) {
|
|
2935
|
+
return this._cachedNonce;
|
|
2936
|
+
}
|
|
2937
|
+
const smartWalletAddress = await this.getSmartWalletAddress();
|
|
2938
|
+
const nonceKey = 0n;
|
|
2939
|
+
const nonceData = await this.rpcConnection.call("eth_call", [
|
|
2940
|
+
{
|
|
2941
|
+
to: this.entryPointAddress,
|
|
2942
|
+
data: `0x35567e1a${smartWalletAddress.slice(2).padStart(64, "0")}${nonceKey.toString(16).padStart(64, "0")}`
|
|
2943
|
+
},
|
|
2944
|
+
"latest"
|
|
2945
|
+
]);
|
|
2946
|
+
const nonce = BigInt(nonceData);
|
|
2947
|
+
this._cachedNonce = nonce;
|
|
2948
|
+
return nonce;
|
|
2949
|
+
}
|
|
2950
|
+
async isDeployed() {
|
|
2951
|
+
if (this._isDeployed !== null) {
|
|
2952
|
+
return this._isDeployed;
|
|
2953
|
+
}
|
|
2954
|
+
try {
|
|
2955
|
+
const smartWalletAddress = await this.getSmartWalletAddress();
|
|
2956
|
+
const code = await this.rpcConnection.getCode(smartWalletAddress);
|
|
2957
|
+
this._isDeployed = code !== "0x" && code.length > 2;
|
|
2958
|
+
return this._isDeployed;
|
|
2959
|
+
} catch {
|
|
2960
|
+
return false;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
async getFeeData() {
|
|
2964
|
+
try {
|
|
2965
|
+
const gasPrice = await this.rpcConnection.getGasPrice();
|
|
2966
|
+
return {
|
|
2967
|
+
maxFeePerGas: gasPrice * 2n,
|
|
2968
|
+
maxPriorityFeePerGas: gasPrice / 10n
|
|
2969
|
+
};
|
|
2970
|
+
} catch {
|
|
2971
|
+
return {
|
|
2972
|
+
maxFeePerGas: 1000000000n,
|
|
2973
|
+
maxPriorityFeePerGas: 1000000000n
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
async getPaymasterData(userOp) {
|
|
2978
|
+
if (!this.paymasterUrl) {
|
|
2979
|
+
return "0x";
|
|
2980
|
+
}
|
|
2981
|
+
try {
|
|
2982
|
+
const userOpHex = this.userOpToHex(userOp);
|
|
2983
|
+
const response = await fetch(this.paymasterUrl, {
|
|
2984
|
+
method: "POST",
|
|
2985
|
+
headers: { "Content-Type": "application/json" },
|
|
2986
|
+
body: JSON.stringify({
|
|
2987
|
+
jsonrpc: "2.0",
|
|
2988
|
+
id: 1,
|
|
2989
|
+
method: "pm_sponsorUserOperation",
|
|
2990
|
+
params: [userOpHex, this.entryPointAddress, {}]
|
|
2991
|
+
})
|
|
2992
|
+
});
|
|
2993
|
+
const data = await response.json();
|
|
2994
|
+
if (data.error || !data.result?.paymasterAndData) {
|
|
2995
|
+
return "0x";
|
|
2996
|
+
}
|
|
2997
|
+
return data.result.paymasterAndData;
|
|
2998
|
+
} catch {
|
|
2999
|
+
return "0x";
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
hashTypedDataDomain(domain) {
|
|
3003
|
+
const domainFields = [];
|
|
3004
|
+
let domainValues = "";
|
|
3005
|
+
if (domain.name !== void 0) {
|
|
3006
|
+
domainFields.push({ name: "name", type: "string" });
|
|
3007
|
+
domainValues += utils.bytesToHex(
|
|
3008
|
+
sha3.keccak_256(utils.utf8ToBytes(domain.name))
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
if (domain.version !== void 0) {
|
|
3012
|
+
domainFields.push({ name: "version", type: "string" });
|
|
3013
|
+
domainValues += utils.bytesToHex(
|
|
3014
|
+
sha3.keccak_256(utils.utf8ToBytes(domain.version))
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
if (domain.chainId !== void 0) {
|
|
3018
|
+
domainFields.push({ name: "chainId", type: "uint256" });
|
|
3019
|
+
domainValues += BigInt(domain.chainId).toString(16).padStart(64, "0");
|
|
3020
|
+
}
|
|
3021
|
+
if (domain.verifyingContract !== void 0) {
|
|
3022
|
+
domainFields.push({ name: "verifyingContract", type: "address" });
|
|
3023
|
+
domainValues += stripHexPrefix(domain.verifyingContract).toLowerCase().padStart(64, "0");
|
|
3024
|
+
}
|
|
3025
|
+
if (domain.salt !== void 0) {
|
|
3026
|
+
domainFields.push({ name: "salt", type: "bytes32" });
|
|
3027
|
+
domainValues += stripHexPrefix(domain.salt).padStart(64, "0");
|
|
3028
|
+
}
|
|
3029
|
+
const domainTypeString = this.formatType("EIP712Domain", domainFields);
|
|
3030
|
+
const domainTypeHash = `0x${utils.bytesToHex(sha3.keccak_256(utils.utf8ToBytes(domainTypeString)))}`;
|
|
3031
|
+
return `0x${utils.bytesToHex(sha3.keccak_256(utils.hexToBytes(domainTypeHash.slice(2) + domainValues)))}`;
|
|
3032
|
+
}
|
|
3033
|
+
hashTypedDataStruct(types, primaryType, value) {
|
|
3034
|
+
return this.hashStruct(primaryType, types, value);
|
|
3035
|
+
}
|
|
3036
|
+
encodeType(typeName, types) {
|
|
3037
|
+
const fields = types[typeName];
|
|
3038
|
+
if (!fields) {
|
|
3039
|
+
throw new SDKError(`Unknown type: ${typeName}`, "INVALID_TYPE");
|
|
3040
|
+
}
|
|
3041
|
+
const deps = this.findTypeDependencies(typeName, types, /* @__PURE__ */ new Set());
|
|
3042
|
+
deps.delete(typeName);
|
|
3043
|
+
const sortedDeps = Array.from(deps).sort();
|
|
3044
|
+
let result = this.formatType(typeName, fields);
|
|
3045
|
+
for (const dep of sortedDeps) {
|
|
3046
|
+
result += this.formatType(dep, types[dep]);
|
|
3047
|
+
}
|
|
3048
|
+
return result;
|
|
3049
|
+
}
|
|
3050
|
+
formatType(typeName, fields) {
|
|
3051
|
+
return `${typeName}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
3052
|
+
}
|
|
3053
|
+
findTypeDependencies(typeName, types, found) {
|
|
3054
|
+
if (found.has(typeName)) return found;
|
|
3055
|
+
const fields = types[typeName];
|
|
3056
|
+
if (!fields) return found;
|
|
3057
|
+
found.add(typeName);
|
|
3058
|
+
for (const field of fields) {
|
|
3059
|
+
let baseType = field.type;
|
|
3060
|
+
if (baseType.endsWith("[]")) {
|
|
3061
|
+
baseType = baseType.slice(0, -2);
|
|
3062
|
+
}
|
|
3063
|
+
if (types[baseType]) {
|
|
3064
|
+
this.findTypeDependencies(baseType, types, found);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
return found;
|
|
3068
|
+
}
|
|
3069
|
+
hashType(typeName, types) {
|
|
3070
|
+
const encodedType = this.encodeType(typeName, types);
|
|
3071
|
+
return `0x${utils.bytesToHex(sha3.keccak_256(utils.utf8ToBytes(encodedType)))}`;
|
|
3072
|
+
}
|
|
3073
|
+
hashStruct(typeName, types, value) {
|
|
3074
|
+
const fields = types[typeName];
|
|
3075
|
+
if (!fields) {
|
|
3076
|
+
throw new SDKError(`Unknown type: ${typeName}`, "INVALID_TYPE");
|
|
3077
|
+
}
|
|
3078
|
+
const typeHash = this.hashType(typeName, types);
|
|
3079
|
+
let encodedValues = "";
|
|
3080
|
+
for (const field of fields) {
|
|
3081
|
+
const fieldValue = value[field.name];
|
|
3082
|
+
encodedValues += this.encodeField(field.type, fieldValue, types);
|
|
3083
|
+
}
|
|
3084
|
+
return `0x${utils.bytesToHex(sha3.keccak_256(utils.hexToBytes(typeHash.slice(2) + encodedValues)))}`;
|
|
3085
|
+
}
|
|
3086
|
+
encodeField(type, value, types) {
|
|
3087
|
+
if (type === "string") {
|
|
3088
|
+
return utils.bytesToHex(sha3.keccak_256(utils.utf8ToBytes(value)));
|
|
3089
|
+
}
|
|
3090
|
+
if (type === "bytes") {
|
|
3091
|
+
const bytesValue = stripHexPrefix(value);
|
|
3092
|
+
return utils.bytesToHex(sha3.keccak_256(utils.hexToBytes(bytesValue)));
|
|
3093
|
+
}
|
|
3094
|
+
if (type === "bool") {
|
|
3095
|
+
return (value ? 1n : 0n).toString(16).padStart(64, "0");
|
|
3096
|
+
}
|
|
3097
|
+
if (type === "address") {
|
|
3098
|
+
return stripHexPrefix(value).toLowerCase().padStart(64, "0");
|
|
3099
|
+
}
|
|
3100
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
3101
|
+
const n = BigInt(value);
|
|
3102
|
+
if (type.startsWith("int") && n < 0n) {
|
|
3103
|
+
return ((1n << 256n) + n).toString(16).padStart(64, "0");
|
|
3104
|
+
}
|
|
3105
|
+
return n.toString(16).padStart(64, "0");
|
|
3106
|
+
}
|
|
3107
|
+
if (type.startsWith("bytes")) {
|
|
3108
|
+
const bytesValue = stripHexPrefix(value);
|
|
3109
|
+
return bytesValue.padEnd(64, "0");
|
|
3110
|
+
}
|
|
3111
|
+
if (types[type]) {
|
|
3112
|
+
return this.hashStruct(
|
|
3113
|
+
type,
|
|
3114
|
+
types,
|
|
3115
|
+
value
|
|
3116
|
+
).slice(2);
|
|
3117
|
+
}
|
|
3118
|
+
if (type.endsWith("[]")) {
|
|
3119
|
+
const arrayType = type.slice(0, -2);
|
|
3120
|
+
const arrayValue = value;
|
|
3121
|
+
const encodedItems = arrayValue.map(
|
|
3122
|
+
(item) => this.encodeField(arrayType, item, types)
|
|
3123
|
+
);
|
|
3124
|
+
return utils.bytesToHex(sha3.keccak_256(utils.hexToBytes(encodedItems.join(""))));
|
|
3125
|
+
}
|
|
3126
|
+
throw new SDKError(`Unsupported type: ${type}`, "UNSUPPORTED_TYPE");
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
|
|
3130
|
+
// src/nero-sdk.ts
|
|
3131
|
+
var NeroMpcSDK = class {
|
|
3132
|
+
constructor(config) {
|
|
3133
|
+
this.wsClient = null;
|
|
3134
|
+
this.keyManager = null;
|
|
3135
|
+
this.deviceKey = null;
|
|
3136
|
+
this._user = null;
|
|
3137
|
+
this._wallet = null;
|
|
3138
|
+
this._publicKey = null;
|
|
3139
|
+
this._partyPublicShares = /* @__PURE__ */ new Map();
|
|
3140
|
+
this._provider = null;
|
|
3141
|
+
this._connectionStatus = "disconnected";
|
|
3142
|
+
this._customChains = /* @__PURE__ */ new Map();
|
|
3143
|
+
this._cachedWalletInfo = null;
|
|
3144
|
+
this.config = {
|
|
3145
|
+
chainId: 689,
|
|
3146
|
+
storagePrefix: "nero",
|
|
3147
|
+
autoConnect: true,
|
|
3148
|
+
...config
|
|
3149
|
+
};
|
|
3150
|
+
this._chainId = this.config.chainId;
|
|
3151
|
+
this.apiClient = new APIClient(this.config);
|
|
3152
|
+
this.chainManager = new ChainManager(this._chainId);
|
|
3153
|
+
if (this.config.wsUrl) {
|
|
3154
|
+
this.wsClient = new WebSocketClient(this.config.wsUrl);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
get isAuthenticated() {
|
|
3158
|
+
return this._user !== null && this.apiClient.getTokens() !== null;
|
|
3159
|
+
}
|
|
3160
|
+
get hasWallet() {
|
|
3161
|
+
return this._wallet !== null;
|
|
3162
|
+
}
|
|
3163
|
+
get user() {
|
|
3164
|
+
return this._user;
|
|
3165
|
+
}
|
|
3166
|
+
get wallet() {
|
|
3167
|
+
return this._wallet;
|
|
3168
|
+
}
|
|
3169
|
+
get chainId() {
|
|
3170
|
+
return this._chainId;
|
|
3171
|
+
}
|
|
3172
|
+
get connected() {
|
|
3173
|
+
return this._connectionStatus === "connected";
|
|
3174
|
+
}
|
|
3175
|
+
get status() {
|
|
3176
|
+
return this._connectionStatus;
|
|
3177
|
+
}
|
|
3178
|
+
get provider() {
|
|
3179
|
+
return this._provider;
|
|
3180
|
+
}
|
|
3181
|
+
get state() {
|
|
3182
|
+
return {
|
|
3183
|
+
isAuthenticated: this.isAuthenticated,
|
|
3184
|
+
isInitialized: this.keyManager !== null,
|
|
3185
|
+
hasWallet: this.hasWallet,
|
|
3186
|
+
user: this._user,
|
|
3187
|
+
walletInfo: this._cachedWalletInfo,
|
|
3188
|
+
chainId: this._chainId,
|
|
3189
|
+
isConnected: this.connected
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
async getWalletInfo() {
|
|
3193
|
+
if (!this._wallet) return null;
|
|
3194
|
+
this._cachedWalletInfo = await this._wallet.getWalletInfo();
|
|
3195
|
+
return this._cachedWalletInfo;
|
|
3196
|
+
}
|
|
3197
|
+
async initialize() {
|
|
3198
|
+
this.deviceKey = this.loadOrGenerateDeviceKey();
|
|
3199
|
+
this.keyManager = new ClientKeyManager(this.deviceKey, {
|
|
3200
|
+
storagePrefix: this.config.storagePrefix
|
|
3201
|
+
});
|
|
3202
|
+
const storedTokens = this.loadStoredTokens();
|
|
3203
|
+
if (storedTokens) {
|
|
3204
|
+
this.apiClient.setTokens(storedTokens);
|
|
3205
|
+
try {
|
|
3206
|
+
this._user = await this.apiClient.getCurrentUser();
|
|
3207
|
+
await this.initializeWallet();
|
|
3208
|
+
} catch {
|
|
3209
|
+
this.apiClient.clearTokens();
|
|
3210
|
+
this.clearStoredTokens();
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
async getOAuthUrl(provider, redirectUri) {
|
|
3215
|
+
return this.apiClient.getOAuthUrl(provider, redirectUri);
|
|
3216
|
+
}
|
|
3217
|
+
async handleOAuthCallback(provider, code, state) {
|
|
3218
|
+
const fingerprint = this.getDeviceFingerprint();
|
|
3219
|
+
const result = await this.apiClient.handleOAuthCallback(
|
|
3220
|
+
provider,
|
|
3221
|
+
code,
|
|
3222
|
+
state,
|
|
3223
|
+
fingerprint
|
|
3224
|
+
);
|
|
3225
|
+
this._user = result.user;
|
|
3226
|
+
this.storeTokens(result.tokens);
|
|
3227
|
+
if (this.keyManager && this._user) {
|
|
3228
|
+
await this.keyManager.initialize(this._user.id);
|
|
3229
|
+
}
|
|
3230
|
+
if (!result.requiresDKG && result.wallet) {
|
|
3231
|
+
await this.initializeWallet();
|
|
3232
|
+
await this.connect();
|
|
3233
|
+
}
|
|
3234
|
+
return {
|
|
3235
|
+
user: result.user,
|
|
3236
|
+
requiresDKG: result.requiresDKG
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
async loginWithGoogle(redirectUri) {
|
|
3240
|
+
const { url } = await this.getOAuthUrl(
|
|
3241
|
+
"google",
|
|
3242
|
+
redirectUri ?? window.location.href
|
|
3243
|
+
);
|
|
3244
|
+
window.location.href = url;
|
|
3245
|
+
}
|
|
3246
|
+
async loginWithGithub(redirectUri) {
|
|
3247
|
+
const { url } = await this.getOAuthUrl(
|
|
3248
|
+
"github",
|
|
3249
|
+
redirectUri ?? window.location.href
|
|
3250
|
+
);
|
|
3251
|
+
window.location.href = url;
|
|
3252
|
+
}
|
|
3253
|
+
async loginWithApple(redirectUri) {
|
|
3254
|
+
const { url } = await this.getOAuthUrl(
|
|
3255
|
+
"apple",
|
|
3256
|
+
redirectUri ?? window.location.href
|
|
3257
|
+
);
|
|
3258
|
+
window.location.href = url;
|
|
3259
|
+
}
|
|
3260
|
+
async generateWallet() {
|
|
3261
|
+
if (!this._user) {
|
|
3262
|
+
throw new SDKError("User not authenticated", "NOT_AUTHENTICATED");
|
|
3263
|
+
}
|
|
3264
|
+
if (!this.keyManager) {
|
|
3265
|
+
throw new SDKError("SDK not initialized", "NOT_INITIALIZED");
|
|
3266
|
+
}
|
|
3267
|
+
const dkgClient = new DKGClient({
|
|
3268
|
+
apiClient: this.apiClient,
|
|
3269
|
+
wsClient: this.wsClient ?? void 0
|
|
3270
|
+
});
|
|
3271
|
+
const result = await dkgClient.execute();
|
|
3272
|
+
if (!result.success) {
|
|
3273
|
+
throw new SDKError(result.error ?? "DKG failed", "DKG_FAILED");
|
|
3274
|
+
}
|
|
3275
|
+
const keyShare = dkgClient.getKeyShare();
|
|
3276
|
+
if (!keyShare) {
|
|
3277
|
+
throw new SDKError("Failed to get key share", "KEY_SHARE_ERROR");
|
|
3278
|
+
}
|
|
3279
|
+
await this.keyManager.storeKeyShare(keyShare);
|
|
3280
|
+
this._publicKey = result.publicKey;
|
|
3281
|
+
this._partyPublicShares = dkgClient.getPartyPublicShares();
|
|
3282
|
+
await this.keyManager.storePartyPublicShares(this._partyPublicShares);
|
|
3283
|
+
this._wallet = this.createSmartWallet(
|
|
3284
|
+
keyShare,
|
|
3285
|
+
this._partyPublicShares,
|
|
3286
|
+
this._publicKey
|
|
3287
|
+
);
|
|
3288
|
+
dkgClient.cleanup();
|
|
3289
|
+
await this.connect();
|
|
3290
|
+
this._cachedWalletInfo = await this._wallet.getWalletInfo();
|
|
3291
|
+
return this._cachedWalletInfo;
|
|
3292
|
+
}
|
|
3293
|
+
async logout() {
|
|
3294
|
+
try {
|
|
3295
|
+
await this.apiClient.logout();
|
|
3296
|
+
} catch {
|
|
3297
|
+
}
|
|
3298
|
+
this._user = null;
|
|
3299
|
+
this._wallet = null;
|
|
3300
|
+
this._publicKey = null;
|
|
3301
|
+
this._connectionStatus = "disconnected";
|
|
3302
|
+
this.apiClient.clearTokens();
|
|
3303
|
+
this.clearStoredTokens();
|
|
3304
|
+
if (this._provider) {
|
|
3305
|
+
this._provider.disconnect();
|
|
3306
|
+
}
|
|
3307
|
+
if (this.wsClient) {
|
|
3308
|
+
this.wsClient.disconnect();
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
async connect() {
|
|
3312
|
+
if (!this._wallet) {
|
|
3313
|
+
throw new SDKError("Wallet not available", "NO_WALLET");
|
|
3314
|
+
}
|
|
3315
|
+
this._connectionStatus = "connecting";
|
|
3316
|
+
try {
|
|
3317
|
+
this._provider = new NeroProvider({
|
|
3318
|
+
chainId: this._chainId,
|
|
3319
|
+
getAccounts: () => this.getAccounts(),
|
|
3320
|
+
signMessage: (message) => this.signMessageInternal(message),
|
|
3321
|
+
signTypedData: (domain, types, primaryType, message) => this.signTypedDataInternal(domain, types, primaryType, message),
|
|
3322
|
+
sendTransaction: (tx) => this.sendTransactionInternal(tx),
|
|
3323
|
+
onChainChanged: (chainId) => this.handleChainChanged(chainId)
|
|
3324
|
+
});
|
|
3325
|
+
this._provider.connect();
|
|
3326
|
+
this._connectionStatus = "connected";
|
|
3327
|
+
return this._provider;
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
this._connectionStatus = "errored";
|
|
3330
|
+
throw error;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
async disconnect() {
|
|
3334
|
+
if (this._provider) {
|
|
3335
|
+
this._provider.disconnect();
|
|
3336
|
+
}
|
|
3337
|
+
this._connectionStatus = "disconnected";
|
|
3338
|
+
await this.logout();
|
|
3339
|
+
}
|
|
3340
|
+
getProvider() {
|
|
3341
|
+
return this._provider;
|
|
3342
|
+
}
|
|
3343
|
+
getUserInfo() {
|
|
3344
|
+
if (!this._user) return null;
|
|
3345
|
+
return {
|
|
3346
|
+
email: this._user.email,
|
|
3347
|
+
name: this._user.displayName,
|
|
3348
|
+
profileImage: this._user.profilePicture,
|
|
3349
|
+
verifier: "nero-mpc",
|
|
3350
|
+
verifierId: this._user.id,
|
|
3351
|
+
typeOfLogin: "social"
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
async switchChain(chainId) {
|
|
3355
|
+
const config = this.getChainConfigForId(chainId);
|
|
3356
|
+
if (!config) {
|
|
3357
|
+
throw new SDKError(`Chain ${chainId} not supported`, "UNSUPPORTED_CHAIN");
|
|
3358
|
+
}
|
|
3359
|
+
this._chainId = chainId;
|
|
3360
|
+
await this.chainManager.switchChain(chainId);
|
|
3361
|
+
if (this._provider) {
|
|
3362
|
+
await this._provider.switchChain(chainId);
|
|
3363
|
+
}
|
|
3364
|
+
if (this._wallet && this.keyManager) {
|
|
3365
|
+
const keyShare = await this.keyManager.getKeyShare();
|
|
3366
|
+
if (keyShare) {
|
|
3367
|
+
this._wallet = this.createSmartWallet(
|
|
3368
|
+
keyShare,
|
|
3369
|
+
this._partyPublicShares,
|
|
3370
|
+
this._publicKey
|
|
3371
|
+
);
|
|
3372
|
+
this._cachedWalletInfo = await this._wallet.getWalletInfo();
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
addChain(config) {
|
|
3377
|
+
this._customChains.set(config.chainId, config);
|
|
3378
|
+
this.chainManager.addChain(config);
|
|
3379
|
+
if (this._provider) {
|
|
3380
|
+
this._provider.addChain(config);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
getChainConfig(chainId) {
|
|
3384
|
+
const targetChainId = chainId ?? this._chainId;
|
|
3385
|
+
return this.getChainConfigForId(targetChainId);
|
|
3386
|
+
}
|
|
3387
|
+
getSupportedChainIds() {
|
|
3388
|
+
const builtinIds = Array.from(BUILTIN_CHAINS.keys());
|
|
3389
|
+
const customIds = Array.from(this._customChains.keys());
|
|
3390
|
+
return [.../* @__PURE__ */ new Set([...builtinIds, ...customIds])];
|
|
3391
|
+
}
|
|
3392
|
+
getAccounts() {
|
|
3393
|
+
if (!this._wallet) return [];
|
|
3394
|
+
return [this._wallet.eoaAddress];
|
|
3395
|
+
}
|
|
3396
|
+
async signMessageInternal(message) {
|
|
3397
|
+
if (!this._wallet) {
|
|
3398
|
+
throw new SDKError("Wallet not available", "NO_WALLET");
|
|
3399
|
+
}
|
|
3400
|
+
const signature = await this._wallet.signMessage(message);
|
|
3401
|
+
return signature.fullSignature;
|
|
3402
|
+
}
|
|
3403
|
+
async signTypedDataInternal(domain, types, primaryType, message) {
|
|
3404
|
+
if (!this._wallet) {
|
|
3405
|
+
throw new SDKError("Wallet not available", "NO_WALLET");
|
|
3406
|
+
}
|
|
3407
|
+
const signature = await this._wallet.signTypedData(
|
|
3408
|
+
domain,
|
|
3409
|
+
types,
|
|
3410
|
+
primaryType,
|
|
3411
|
+
message
|
|
3412
|
+
);
|
|
3413
|
+
return signature.fullSignature;
|
|
3414
|
+
}
|
|
3415
|
+
async sendTransactionInternal(tx) {
|
|
3416
|
+
if (!this._wallet) {
|
|
3417
|
+
throw new SDKError("Wallet not available", "NO_WALLET");
|
|
3418
|
+
}
|
|
3419
|
+
const txRequest = tx;
|
|
3420
|
+
const userOp = await this._wallet.buildUserOperation({
|
|
3421
|
+
to: txRequest.to,
|
|
3422
|
+
value: txRequest.value ? BigInt(txRequest.value) : void 0,
|
|
3423
|
+
data: txRequest.data
|
|
3424
|
+
});
|
|
3425
|
+
const result = await this._wallet.sendUserOperation(userOp);
|
|
3426
|
+
return result.userOpHash;
|
|
3427
|
+
}
|
|
3428
|
+
handleChainChanged(chainId) {
|
|
3429
|
+
this._chainId = chainId;
|
|
3430
|
+
}
|
|
3431
|
+
getChainConfigForId(chainId) {
|
|
3432
|
+
return getChainConfig(chainId) ?? this._customChains.get(chainId);
|
|
3433
|
+
}
|
|
3434
|
+
createSmartWallet(keyShare, partyPublicShares, publicKey) {
|
|
3435
|
+
const chainConfig = this.getChainConfigForId(this._chainId);
|
|
3436
|
+
const rpcConnection = this.chainManager.getRpcConnection();
|
|
3437
|
+
const bundlerUrl = chainConfig?.bundlerUrl ?? `${this.config.backendUrl}/api/v1/bundler`;
|
|
3438
|
+
return new SmartWallet({
|
|
3439
|
+
apiClient: this.apiClient,
|
|
3440
|
+
keyShare,
|
|
3441
|
+
partyPublicShares,
|
|
3442
|
+
publicKey,
|
|
3443
|
+
chainId: this._chainId,
|
|
3444
|
+
rpcConnection,
|
|
3445
|
+
bundlerUrl,
|
|
3446
|
+
entryPointAddress: chainConfig?.entryPointAddress,
|
|
3447
|
+
factoryAddress: chainConfig?.simpleAccountFactoryAddress,
|
|
3448
|
+
paymasterUrl: chainConfig?.paymasterUrl
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
async exportBackup(password) {
|
|
3452
|
+
if (!this.keyManager) {
|
|
3453
|
+
throw new SDKError("SDK not initialized", "NOT_INITIALIZED");
|
|
3454
|
+
}
|
|
3455
|
+
return this.keyManager.exportBackup(password);
|
|
3456
|
+
}
|
|
3457
|
+
async importBackup(backupString, password) {
|
|
3458
|
+
if (!this.keyManager || !this._user) {
|
|
3459
|
+
throw new SDKError("SDK not initialized", "NOT_INITIALIZED");
|
|
3460
|
+
}
|
|
3461
|
+
const keyShare = await this.keyManager.importBackup(backupString, password);
|
|
3462
|
+
await this.keyManager.storeKeyShare(keyShare);
|
|
3463
|
+
await this.initializeWallet();
|
|
3464
|
+
}
|
|
3465
|
+
async initializeWallet() {
|
|
3466
|
+
if (!this.keyManager || !this._user) {
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
const keyShare = await this.keyManager.getKeyShare();
|
|
3470
|
+
if (!keyShare) {
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
const storedPartyShares = await this.keyManager.getPartyPublicShares();
|
|
3474
|
+
if (!storedPartyShares) {
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
try {
|
|
3478
|
+
const walletInfo = await this.apiClient.getWalletInfo();
|
|
3479
|
+
this._publicKey = walletInfo.publicKey;
|
|
3480
|
+
this._partyPublicShares = storedPartyShares;
|
|
3481
|
+
this._wallet = this.createSmartWallet(
|
|
3482
|
+
keyShare,
|
|
3483
|
+
this._partyPublicShares,
|
|
3484
|
+
this._publicKey
|
|
3485
|
+
);
|
|
3486
|
+
this._cachedWalletInfo = await this._wallet.getWalletInfo();
|
|
3487
|
+
} catch {
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
getDeviceFingerprint() {
|
|
3491
|
+
return {
|
|
3492
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown"
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
loadOrGenerateDeviceKey() {
|
|
3496
|
+
const storageKey = `${this.config.storagePrefix}:device_key`;
|
|
3497
|
+
if (typeof localStorage !== "undefined") {
|
|
3498
|
+
const stored = localStorage.getItem(storageKey);
|
|
3499
|
+
if (stored) {
|
|
3500
|
+
return stored;
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
const newKey = generateDeviceKey();
|
|
3504
|
+
if (typeof localStorage !== "undefined") {
|
|
3505
|
+
localStorage.setItem(storageKey, newKey);
|
|
3506
|
+
}
|
|
3507
|
+
return newKey;
|
|
3508
|
+
}
|
|
3509
|
+
storeTokens(tokens) {
|
|
3510
|
+
if (typeof localStorage !== "undefined") {
|
|
3511
|
+
localStorage.setItem(
|
|
3512
|
+
`${this.config.storagePrefix}:tokens`,
|
|
3513
|
+
JSON.stringify(tokens)
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
loadStoredTokens() {
|
|
3518
|
+
if (typeof localStorage === "undefined") {
|
|
3519
|
+
return null;
|
|
3520
|
+
}
|
|
3521
|
+
const stored = localStorage.getItem(`${this.config.storagePrefix}:tokens`);
|
|
3522
|
+
if (!stored) {
|
|
3523
|
+
return null;
|
|
3524
|
+
}
|
|
3525
|
+
try {
|
|
3526
|
+
return JSON.parse(stored);
|
|
3527
|
+
} catch {
|
|
3528
|
+
return null;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
clearStoredTokens() {
|
|
3532
|
+
if (typeof localStorage !== "undefined") {
|
|
3533
|
+
localStorage.removeItem(`${this.config.storagePrefix}:tokens`);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
};
|
|
3537
|
+
var NeroMpcAuthContext = react.createContext(
|
|
3538
|
+
null
|
|
3539
|
+
);
|
|
3540
|
+
function useNeroMpcAuthContext() {
|
|
3541
|
+
const context = react.useContext(NeroMpcAuthContext);
|
|
3542
|
+
if (!context) {
|
|
3543
|
+
throw new Error(
|
|
3544
|
+
"useNeroMpcAuthContext must be used within a NeroMpcAuthProvider"
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3547
|
+
return context;
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
// src/react/theme/default-theme.ts
|
|
3551
|
+
var lightColors = {
|
|
3552
|
+
primary: "#6366f1",
|
|
3553
|
+
primaryHover: "#4f46e5",
|
|
3554
|
+
secondary: "#8b5cf6",
|
|
3555
|
+
secondaryHover: "#7c3aed",
|
|
3556
|
+
background: "#ffffff",
|
|
3557
|
+
backgroundSecondary: "#f9fafb",
|
|
3558
|
+
surface: "#ffffff",
|
|
3559
|
+
surfaceHover: "#f3f4f6",
|
|
3560
|
+
text: "#111827",
|
|
3561
|
+
textSecondary: "#374151",
|
|
3562
|
+
textMuted: "#6b7280",
|
|
3563
|
+
border: "#e5e7eb",
|
|
3564
|
+
borderFocus: "#6366f1",
|
|
3565
|
+
error: "#ef4444",
|
|
3566
|
+
errorBackground: "#fef2f2",
|
|
3567
|
+
success: "#22c55e",
|
|
3568
|
+
successBackground: "#f0fdf4",
|
|
3569
|
+
warning: "#f59e0b",
|
|
3570
|
+
warningBackground: "#fffbeb",
|
|
3571
|
+
overlay: "rgba(0, 0, 0, 0.5)"
|
|
3572
|
+
};
|
|
3573
|
+
var darkColors = {
|
|
3574
|
+
primary: "#818cf8",
|
|
3575
|
+
primaryHover: "#6366f1",
|
|
3576
|
+
secondary: "#a78bfa",
|
|
3577
|
+
secondaryHover: "#8b5cf6",
|
|
3578
|
+
background: "#0f172a",
|
|
3579
|
+
backgroundSecondary: "#1e293b",
|
|
3580
|
+
surface: "#1e293b",
|
|
3581
|
+
surfaceHover: "#334155",
|
|
3582
|
+
text: "#f8fafc",
|
|
3583
|
+
textSecondary: "#e2e8f0",
|
|
3584
|
+
textMuted: "#94a3b8",
|
|
3585
|
+
border: "#334155",
|
|
3586
|
+
borderFocus: "#818cf8",
|
|
3587
|
+
error: "#f87171",
|
|
3588
|
+
errorBackground: "#450a0a",
|
|
3589
|
+
success: "#4ade80",
|
|
3590
|
+
successBackground: "#052e16",
|
|
3591
|
+
warning: "#fbbf24",
|
|
3592
|
+
warningBackground: "#451a03",
|
|
3593
|
+
overlay: "rgba(0, 0, 0, 0.7)"
|
|
3594
|
+
};
|
|
3595
|
+
var typography = {
|
|
3596
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
3597
|
+
fontFamilyMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
3598
|
+
fontSizeXs: "0.75rem",
|
|
3599
|
+
fontSizeSm: "0.875rem",
|
|
3600
|
+
fontSizeMd: "1rem",
|
|
3601
|
+
fontSizeLg: "1.125rem",
|
|
3602
|
+
fontSizeXl: "1.25rem",
|
|
3603
|
+
fontWeightNormal: 400,
|
|
3604
|
+
fontWeightMedium: 500,
|
|
3605
|
+
fontWeightSemibold: 600,
|
|
3606
|
+
fontWeightBold: 700,
|
|
3607
|
+
lineHeightTight: 1.25,
|
|
3608
|
+
lineHeightNormal: 1.5,
|
|
3609
|
+
lineHeightRelaxed: 1.75
|
|
3610
|
+
};
|
|
3611
|
+
var spacing = {
|
|
3612
|
+
xs: "0.25rem",
|
|
3613
|
+
sm: "0.5rem",
|
|
3614
|
+
md: "1rem",
|
|
3615
|
+
lg: "1.5rem",
|
|
3616
|
+
xl: "2rem",
|
|
3617
|
+
xxl: "3rem"
|
|
3618
|
+
};
|
|
3619
|
+
var borderRadius = {
|
|
3620
|
+
none: "0",
|
|
3621
|
+
sm: "0.25rem",
|
|
3622
|
+
md: "0.375rem",
|
|
3623
|
+
lg: "0.5rem",
|
|
3624
|
+
xl: "0.75rem",
|
|
3625
|
+
full: "9999px"
|
|
3626
|
+
};
|
|
3627
|
+
var shadows = {
|
|
3628
|
+
sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
|
3629
|
+
md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
|
3630
|
+
lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
|
3631
|
+
xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)"
|
|
3632
|
+
};
|
|
3633
|
+
var lightTheme = {
|
|
3634
|
+
mode: "light",
|
|
3635
|
+
colors: lightColors,
|
|
3636
|
+
typography,
|
|
3637
|
+
spacing,
|
|
3638
|
+
borderRadius,
|
|
3639
|
+
shadows
|
|
3640
|
+
};
|
|
3641
|
+
var darkTheme = {
|
|
3642
|
+
mode: "dark",
|
|
3643
|
+
colors: darkColors,
|
|
3644
|
+
typography,
|
|
3645
|
+
spacing,
|
|
3646
|
+
borderRadius,
|
|
3647
|
+
shadows
|
|
3648
|
+
};
|
|
3649
|
+
function getDefaultTheme(mode) {
|
|
3650
|
+
return mode === "dark" ? darkTheme : lightTheme;
|
|
3651
|
+
}
|
|
3652
|
+
function createTheme(mode, overrides) {
|
|
3653
|
+
const base = getDefaultTheme(mode);
|
|
3654
|
+
if (!overrides) {
|
|
3655
|
+
return base;
|
|
3656
|
+
}
|
|
3657
|
+
return {
|
|
3658
|
+
...base,
|
|
3659
|
+
colors: {
|
|
3660
|
+
...base.colors,
|
|
3661
|
+
...overrides.primary && { primary: overrides.primary },
|
|
3662
|
+
...overrides.secondary && { secondary: overrides.secondary },
|
|
3663
|
+
...overrides.background && { background: overrides.background },
|
|
3664
|
+
...overrides.text && { text: overrides.text },
|
|
3665
|
+
...overrides.border && { border: overrides.border }
|
|
3666
|
+
},
|
|
3667
|
+
typography: {
|
|
3668
|
+
...base.typography,
|
|
3669
|
+
...overrides.fontFamily && { fontFamily: overrides.fontFamily }
|
|
3670
|
+
}
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
var ThemeContext = react.createContext(null);
|
|
3674
|
+
function getSystemTheme() {
|
|
3675
|
+
if (typeof window === "undefined" || !window.matchMedia) {
|
|
3676
|
+
return "light";
|
|
3677
|
+
}
|
|
3678
|
+
try {
|
|
3679
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
3680
|
+
} catch {
|
|
3681
|
+
return "light";
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
function ThemeProvider({
|
|
3685
|
+
children,
|
|
3686
|
+
uiConfig,
|
|
3687
|
+
defaultMode = "auto"
|
|
3688
|
+
}) {
|
|
3689
|
+
const [mode, setMode] = react.useState(defaultMode);
|
|
3690
|
+
const [systemTheme, setSystemTheme] = react.useState(
|
|
3691
|
+
getSystemTheme
|
|
3692
|
+
);
|
|
3693
|
+
react.useEffect(() => {
|
|
3694
|
+
if (typeof window === "undefined" || !window.matchMedia) return void 0;
|
|
3695
|
+
try {
|
|
3696
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
3697
|
+
const handleChange = (e) => {
|
|
3698
|
+
setSystemTheme(e.matches ? "dark" : "light");
|
|
3699
|
+
};
|
|
3700
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
3701
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
3702
|
+
} catch {
|
|
3703
|
+
return void 0;
|
|
3704
|
+
}
|
|
3705
|
+
}, []);
|
|
3706
|
+
const resolvedMode = react.useMemo(() => {
|
|
3707
|
+
if (mode === "auto") {
|
|
3708
|
+
return systemTheme;
|
|
3709
|
+
}
|
|
3710
|
+
return mode;
|
|
3711
|
+
}, [mode, systemTheme]);
|
|
3712
|
+
const theme = react.useMemo(() => {
|
|
3713
|
+
const overrides = uiConfig?.theme;
|
|
3714
|
+
if (overrides) {
|
|
3715
|
+
return createTheme(resolvedMode, overrides);
|
|
3716
|
+
}
|
|
3717
|
+
return getDefaultTheme(resolvedMode);
|
|
3718
|
+
}, [resolvedMode, uiConfig?.theme]);
|
|
3719
|
+
const logo = react.useMemo(() => {
|
|
3720
|
+
if (!uiConfig) return void 0;
|
|
3721
|
+
return resolvedMode === "dark" ? uiConfig.logoDark : uiConfig.logoLight;
|
|
3722
|
+
}, [uiConfig, resolvedMode]);
|
|
3723
|
+
const config = react.useMemo(() => {
|
|
3724
|
+
return {
|
|
3725
|
+
appName: uiConfig?.appName ?? "NERO MPC Wallet",
|
|
3726
|
+
logoLight: uiConfig?.logoLight,
|
|
3727
|
+
logoDark: uiConfig?.logoDark,
|
|
3728
|
+
mode: uiConfig?.mode ?? defaultMode,
|
|
3729
|
+
theme: uiConfig?.theme,
|
|
3730
|
+
defaultLanguage: uiConfig?.defaultLanguage ?? "en"
|
|
3731
|
+
};
|
|
3732
|
+
}, [uiConfig, defaultMode]);
|
|
3733
|
+
const handleSetMode = react.useCallback((newMode) => {
|
|
3734
|
+
setMode(newMode);
|
|
3735
|
+
if (typeof localStorage !== "undefined") {
|
|
3736
|
+
localStorage.setItem("nero-theme-mode", newMode);
|
|
3737
|
+
}
|
|
3738
|
+
}, []);
|
|
3739
|
+
react.useEffect(() => {
|
|
3740
|
+
if (typeof localStorage === "undefined") return;
|
|
3741
|
+
const stored = localStorage.getItem("nero-theme-mode");
|
|
3742
|
+
if (stored && ["light", "dark", "auto"].includes(stored)) {
|
|
3743
|
+
setMode(stored);
|
|
3744
|
+
}
|
|
3745
|
+
}, []);
|
|
3746
|
+
const value = react.useMemo(
|
|
3747
|
+
() => ({
|
|
3748
|
+
theme,
|
|
3749
|
+
mode,
|
|
3750
|
+
resolvedMode,
|
|
3751
|
+
setMode: handleSetMode,
|
|
3752
|
+
uiConfig: config,
|
|
3753
|
+
logo
|
|
3754
|
+
}),
|
|
3755
|
+
[theme, mode, resolvedMode, handleSetMode, config, logo]
|
|
3756
|
+
);
|
|
3757
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children });
|
|
3758
|
+
}
|
|
3759
|
+
function useTheme() {
|
|
3760
|
+
const context = react.useContext(ThemeContext);
|
|
3761
|
+
if (!context) {
|
|
3762
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
3763
|
+
}
|
|
3764
|
+
return context;
|
|
3765
|
+
}
|
|
3766
|
+
function useThemeColors() {
|
|
3767
|
+
const { theme } = useTheme();
|
|
3768
|
+
return theme.colors;
|
|
3769
|
+
}
|
|
3770
|
+
function useResolvedMode() {
|
|
3771
|
+
const { resolvedMode } = useTheme();
|
|
3772
|
+
return resolvedMode;
|
|
3773
|
+
}
|
|
3774
|
+
var DEFAULT_STATE = {
|
|
3775
|
+
isAuthenticated: false,
|
|
3776
|
+
isInitialized: false,
|
|
3777
|
+
hasWallet: false,
|
|
3778
|
+
user: null,
|
|
3779
|
+
walletInfo: null,
|
|
3780
|
+
chainId: 689,
|
|
3781
|
+
isConnected: false
|
|
3782
|
+
};
|
|
3783
|
+
function NeroMpcAuthProvider({
|
|
3784
|
+
children,
|
|
3785
|
+
config,
|
|
3786
|
+
autoConnect = true,
|
|
3787
|
+
uiConfig,
|
|
3788
|
+
themeMode = "auto"
|
|
3789
|
+
}) {
|
|
3790
|
+
const [sdk, setSdk] = react.useState(null);
|
|
3791
|
+
const [state, setState] = react.useState(DEFAULT_STATE);
|
|
3792
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
3793
|
+
const [error, setError] = react.useState(null);
|
|
3794
|
+
react.useEffect(() => {
|
|
3795
|
+
const initializeSDK = async () => {
|
|
3796
|
+
try {
|
|
3797
|
+
setIsLoading(true);
|
|
3798
|
+
setError(null);
|
|
3799
|
+
const instance = new NeroMpcSDK(config);
|
|
3800
|
+
setSdk(instance);
|
|
3801
|
+
if (autoConnect) {
|
|
3802
|
+
await instance.initialize();
|
|
3803
|
+
}
|
|
3804
|
+
setState(instance.state);
|
|
3805
|
+
} catch (err) {
|
|
3806
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3807
|
+
} finally {
|
|
3808
|
+
setIsLoading(false);
|
|
3809
|
+
}
|
|
3810
|
+
};
|
|
3811
|
+
initializeSDK();
|
|
3812
|
+
}, [config.backendUrl, config.chainId, autoConnect]);
|
|
3813
|
+
react.useEffect(() => {
|
|
3814
|
+
if (sdk) {
|
|
3815
|
+
setState(sdk.state);
|
|
3816
|
+
}
|
|
3817
|
+
}, [sdk?.isAuthenticated, sdk?.hasWallet, sdk?.user]);
|
|
3818
|
+
const contextValue = react.useMemo(
|
|
3819
|
+
() => ({
|
|
3820
|
+
sdk,
|
|
3821
|
+
state,
|
|
3822
|
+
isLoading,
|
|
3823
|
+
error
|
|
3824
|
+
}),
|
|
3825
|
+
[sdk, state, isLoading, error]
|
|
3826
|
+
);
|
|
3827
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ThemeProvider, { uiConfig, defaultMode: themeMode, children: /* @__PURE__ */ jsxRuntime.jsx(NeroMpcAuthContext.Provider, { value: contextValue, children }) });
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
// src/react/hooks/useNeroMpcAuth.ts
|
|
3831
|
+
function useNeroMpcAuth() {
|
|
3832
|
+
const { sdk, state, isLoading, error } = useNeroMpcAuthContext();
|
|
3833
|
+
return {
|
|
3834
|
+
...state,
|
|
3835
|
+
sdk,
|
|
3836
|
+
isLoading,
|
|
3837
|
+
error
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
function useNeroConnect() {
|
|
3841
|
+
const { sdk } = useNeroMpcAuthContext();
|
|
3842
|
+
const [isConnecting, setIsConnecting] = react.useState(false);
|
|
3843
|
+
const [error, setError] = react.useState(null);
|
|
3844
|
+
const connect = react.useCallback(
|
|
3845
|
+
async (provider, redirectUri) => {
|
|
3846
|
+
if (!sdk) {
|
|
3847
|
+
throw new Error("SDK not initialized");
|
|
3848
|
+
}
|
|
3849
|
+
setIsConnecting(true);
|
|
3850
|
+
setError(null);
|
|
3851
|
+
try {
|
|
3852
|
+
switch (provider) {
|
|
3853
|
+
case "google":
|
|
3854
|
+
await sdk.loginWithGoogle(redirectUri);
|
|
3855
|
+
break;
|
|
3856
|
+
case "github":
|
|
3857
|
+
await sdk.loginWithGithub(redirectUri);
|
|
3858
|
+
break;
|
|
3859
|
+
case "apple":
|
|
3860
|
+
await sdk.loginWithApple(redirectUri);
|
|
3861
|
+
break;
|
|
3862
|
+
default:
|
|
3863
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
3864
|
+
}
|
|
3865
|
+
} catch (err) {
|
|
3866
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3867
|
+
setError(error2);
|
|
3868
|
+
throw error2;
|
|
3869
|
+
} finally {
|
|
3870
|
+
setIsConnecting(false);
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3873
|
+
[sdk]
|
|
3874
|
+
);
|
|
3875
|
+
const handleCallback = react.useCallback(
|
|
3876
|
+
async (provider, code, state) => {
|
|
3877
|
+
if (!sdk) {
|
|
3878
|
+
throw new Error("SDK not initialized");
|
|
3879
|
+
}
|
|
3880
|
+
setIsConnecting(true);
|
|
3881
|
+
setError(null);
|
|
3882
|
+
try {
|
|
3883
|
+
const result = await sdk.handleOAuthCallback(provider, code, state);
|
|
3884
|
+
return result;
|
|
3885
|
+
} catch (err) {
|
|
3886
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3887
|
+
setError(error2);
|
|
3888
|
+
throw error2;
|
|
3889
|
+
} finally {
|
|
3890
|
+
setIsConnecting(false);
|
|
3891
|
+
}
|
|
3892
|
+
},
|
|
3893
|
+
[sdk]
|
|
3894
|
+
);
|
|
3895
|
+
return {
|
|
3896
|
+
connect,
|
|
3897
|
+
handleCallback,
|
|
3898
|
+
isConnecting,
|
|
3899
|
+
error
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
function useNeroDisconnect() {
|
|
3903
|
+
const { sdk } = useNeroMpcAuthContext();
|
|
3904
|
+
const [isDisconnecting, setIsDisconnecting] = react.useState(false);
|
|
3905
|
+
const [error, setError] = react.useState(null);
|
|
3906
|
+
const disconnect = react.useCallback(async () => {
|
|
3907
|
+
if (!sdk) {
|
|
3908
|
+
throw new Error("SDK not initialized");
|
|
3909
|
+
}
|
|
3910
|
+
setIsDisconnecting(true);
|
|
3911
|
+
setError(null);
|
|
3912
|
+
try {
|
|
3913
|
+
await sdk.logout();
|
|
3914
|
+
} catch (err) {
|
|
3915
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3916
|
+
setError(error2);
|
|
3917
|
+
throw error2;
|
|
3918
|
+
} finally {
|
|
3919
|
+
setIsDisconnecting(false);
|
|
3920
|
+
}
|
|
3921
|
+
}, [sdk]);
|
|
3922
|
+
return {
|
|
3923
|
+
disconnect,
|
|
3924
|
+
isDisconnecting,
|
|
3925
|
+
error
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
// src/react/hooks/useNeroUser.ts
|
|
3930
|
+
function useNeroUser() {
|
|
3931
|
+
const { state } = useNeroMpcAuthContext();
|
|
3932
|
+
return {
|
|
3933
|
+
user: state.user,
|
|
3934
|
+
isAuthenticated: state.isAuthenticated
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
function useNeroWallet() {
|
|
3938
|
+
const { sdk, state } = useNeroMpcAuthContext();
|
|
3939
|
+
const [isGenerating, setIsGenerating] = react.useState(false);
|
|
3940
|
+
const [isSigning, setIsSigning] = react.useState(false);
|
|
3941
|
+
const [error, setError] = react.useState(null);
|
|
3942
|
+
const generateWallet = react.useCallback(async () => {
|
|
3943
|
+
if (!sdk) {
|
|
3944
|
+
throw new Error("SDK not initialized");
|
|
3945
|
+
}
|
|
3946
|
+
setIsGenerating(true);
|
|
3947
|
+
setError(null);
|
|
3948
|
+
try {
|
|
3949
|
+
const walletInfo = await sdk.generateWallet();
|
|
3950
|
+
return walletInfo;
|
|
3951
|
+
} catch (err) {
|
|
3952
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3953
|
+
setError(error2);
|
|
3954
|
+
throw error2;
|
|
3955
|
+
} finally {
|
|
3956
|
+
setIsGenerating(false);
|
|
3957
|
+
}
|
|
3958
|
+
}, [sdk]);
|
|
3959
|
+
const signMessage = react.useCallback(
|
|
3960
|
+
async (message) => {
|
|
3961
|
+
if (!sdk?.wallet) {
|
|
3962
|
+
throw new Error("Wallet not available");
|
|
3963
|
+
}
|
|
3964
|
+
setIsSigning(true);
|
|
3965
|
+
setError(null);
|
|
3966
|
+
try {
|
|
3967
|
+
const result = await sdk.wallet.signMessage(message);
|
|
3968
|
+
return result;
|
|
3969
|
+
} catch (err) {
|
|
3970
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3971
|
+
setError(error2);
|
|
3972
|
+
throw error2;
|
|
3973
|
+
} finally {
|
|
3974
|
+
setIsSigning(false);
|
|
3975
|
+
}
|
|
3976
|
+
},
|
|
3977
|
+
[sdk]
|
|
3978
|
+
);
|
|
3979
|
+
const signTypedData = react.useCallback(
|
|
3980
|
+
async (domain, types, primaryType, value) => {
|
|
3981
|
+
if (!sdk?.wallet) {
|
|
3982
|
+
throw new Error("Wallet not available");
|
|
3983
|
+
}
|
|
3984
|
+
setIsSigning(true);
|
|
3985
|
+
setError(null);
|
|
3986
|
+
try {
|
|
3987
|
+
const result = await sdk.wallet.signTypedData(
|
|
3988
|
+
domain,
|
|
3989
|
+
types,
|
|
3990
|
+
primaryType,
|
|
3991
|
+
value
|
|
3992
|
+
);
|
|
3993
|
+
return result;
|
|
3994
|
+
} catch (err) {
|
|
3995
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3996
|
+
setError(error2);
|
|
3997
|
+
throw error2;
|
|
3998
|
+
} finally {
|
|
3999
|
+
setIsSigning(false);
|
|
4000
|
+
}
|
|
4001
|
+
},
|
|
4002
|
+
[sdk]
|
|
4003
|
+
);
|
|
4004
|
+
return {
|
|
4005
|
+
wallet: state.walletInfo,
|
|
4006
|
+
hasWallet: state.hasWallet,
|
|
4007
|
+
generateWallet,
|
|
4008
|
+
signMessage,
|
|
4009
|
+
signTypedData,
|
|
4010
|
+
isGenerating,
|
|
4011
|
+
isSigning,
|
|
4012
|
+
error
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
exports.NeroMpcAuthContext = NeroMpcAuthContext;
|
|
4017
|
+
exports.NeroMpcAuthProvider = NeroMpcAuthProvider;
|
|
4018
|
+
exports.ThemeProvider = ThemeProvider;
|
|
4019
|
+
exports.createTheme = createTheme;
|
|
4020
|
+
exports.darkTheme = darkTheme;
|
|
4021
|
+
exports.getDefaultTheme = getDefaultTheme;
|
|
4022
|
+
exports.lightTheme = lightTheme;
|
|
4023
|
+
exports.useNeroConnect = useNeroConnect;
|
|
4024
|
+
exports.useNeroDisconnect = useNeroDisconnect;
|
|
4025
|
+
exports.useNeroMpcAuth = useNeroMpcAuth;
|
|
4026
|
+
exports.useNeroMpcAuthContext = useNeroMpcAuthContext;
|
|
4027
|
+
exports.useNeroUser = useNeroUser;
|
|
4028
|
+
exports.useNeroWallet = useNeroWallet;
|
|
4029
|
+
exports.useResolvedMode = useResolvedMode;
|
|
4030
|
+
exports.useTheme = useTheme;
|
|
4031
|
+
exports.useThemeColors = useThemeColors;
|
|
4032
|
+
//# sourceMappingURL=react.js.map
|
|
4033
|
+
//# sourceMappingURL=react.js.map
|