@enclave-hq/wallet-sdk 1.0.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/README.md +203 -0
- package/dist/index.d.mts +441 -0
- package/dist/index.d.ts +441 -0
- package/dist/index.js +2130 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2064 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +218 -0
- package/dist/react/index.d.ts +218 -0
- package/dist/react/index.js +1782 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1770 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1782 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var EventEmitter = require('eventemitter3');
|
|
5
|
+
var viem = require('viem');
|
|
6
|
+
var accounts = require('viem/accounts');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
11
|
+
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
12
|
+
|
|
13
|
+
// src/react/WalletContext.tsx
|
|
14
|
+
var TypedEventEmitter = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.emitter = new EventEmitter__default.default();
|
|
17
|
+
}
|
|
18
|
+
on(event, handler) {
|
|
19
|
+
this.emitter.on(event, handler);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
once(event, handler) {
|
|
23
|
+
this.emitter.once(event, handler);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
off(event, handler) {
|
|
27
|
+
this.emitter.off(event, handler);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
emit(event, ...args) {
|
|
31
|
+
return this.emitter.emit(event, ...args);
|
|
32
|
+
}
|
|
33
|
+
removeAllListeners(event) {
|
|
34
|
+
if (event !== void 0) {
|
|
35
|
+
this.emitter.removeAllListeners(event);
|
|
36
|
+
} else {
|
|
37
|
+
this.emitter.removeAllListeners();
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/core/errors.ts
|
|
44
|
+
var WalletSDKError = class _WalletSDKError extends Error {
|
|
45
|
+
constructor(message, code, details) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.details = details;
|
|
49
|
+
this.name = "WalletSDKError";
|
|
50
|
+
Object.setPrototypeOf(this, _WalletSDKError.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var WalletNotConnectedError = class extends WalletSDKError {
|
|
54
|
+
constructor(walletType) {
|
|
55
|
+
super(
|
|
56
|
+
walletType ? `Wallet ${walletType} is not connected` : "No wallet is connected",
|
|
57
|
+
"WALLET_NOT_CONNECTED",
|
|
58
|
+
{ walletType }
|
|
59
|
+
);
|
|
60
|
+
this.name = "WalletNotConnectedError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var WalletNotAvailableError = class extends WalletSDKError {
|
|
64
|
+
constructor(walletType, downloadUrl) {
|
|
65
|
+
super(
|
|
66
|
+
`Wallet ${walletType} is not available. Please install it first.`,
|
|
67
|
+
"WALLET_NOT_AVAILABLE",
|
|
68
|
+
{ walletType, downloadUrl }
|
|
69
|
+
);
|
|
70
|
+
this.name = "WalletNotAvailableError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var ConnectionRejectedError = class extends WalletSDKError {
|
|
74
|
+
constructor(walletType) {
|
|
75
|
+
super(
|
|
76
|
+
`Connection to ${walletType} was rejected by user`,
|
|
77
|
+
"CONNECTION_REJECTED",
|
|
78
|
+
{ walletType }
|
|
79
|
+
);
|
|
80
|
+
this.name = "ConnectionRejectedError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var SignatureRejectedError = class extends WalletSDKError {
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super(
|
|
86
|
+
message || "Signature was rejected by user",
|
|
87
|
+
"SIGNATURE_REJECTED"
|
|
88
|
+
);
|
|
89
|
+
this.name = "SignatureRejectedError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var TransactionFailedError = class extends WalletSDKError {
|
|
93
|
+
constructor(txHash, reason) {
|
|
94
|
+
super(
|
|
95
|
+
`Transaction ${txHash} failed${reason ? `: ${reason}` : ""}`,
|
|
96
|
+
"TRANSACTION_FAILED",
|
|
97
|
+
{ txHash, reason }
|
|
98
|
+
);
|
|
99
|
+
this.name = "TransactionFailedError";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var MethodNotSupportedError = class extends WalletSDKError {
|
|
103
|
+
constructor(method, walletType) {
|
|
104
|
+
super(
|
|
105
|
+
`Method ${method} is not supported by ${walletType}`,
|
|
106
|
+
"METHOD_NOT_SUPPORTED",
|
|
107
|
+
{ method, walletType }
|
|
108
|
+
);
|
|
109
|
+
this.name = "MethodNotSupportedError";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/adapters/base/wallet-adapter.ts
|
|
114
|
+
var WalletAdapter = class extends EventEmitter__default.default {
|
|
115
|
+
constructor() {
|
|
116
|
+
super(...arguments);
|
|
117
|
+
// 状态
|
|
118
|
+
this.state = "disconnected" /* DISCONNECTED */;
|
|
119
|
+
this.currentAccount = null;
|
|
120
|
+
}
|
|
121
|
+
signTransaction(_transaction) {
|
|
122
|
+
throw new MethodNotSupportedError("signTransaction", this.type);
|
|
123
|
+
}
|
|
124
|
+
signTypedData(_typedData) {
|
|
125
|
+
throw new MethodNotSupportedError("signTypedData", this.type);
|
|
126
|
+
}
|
|
127
|
+
// 链切换(默认不支持)
|
|
128
|
+
switchChain(_chainId) {
|
|
129
|
+
throw new MethodNotSupportedError("switchChain", this.type);
|
|
130
|
+
}
|
|
131
|
+
addChain(_chainConfig) {
|
|
132
|
+
throw new MethodNotSupportedError("addChain", this.type);
|
|
133
|
+
}
|
|
134
|
+
// 合约调用(默认不支持)
|
|
135
|
+
async readContract(_params) {
|
|
136
|
+
throw new MethodNotSupportedError("readContract", this.type);
|
|
137
|
+
}
|
|
138
|
+
async writeContract(_params) {
|
|
139
|
+
throw new MethodNotSupportedError("writeContract", this.type);
|
|
140
|
+
}
|
|
141
|
+
async estimateGas(_params) {
|
|
142
|
+
throw new MethodNotSupportedError("estimateGas", this.type);
|
|
143
|
+
}
|
|
144
|
+
async waitForTransaction(_txHash, _confirmations) {
|
|
145
|
+
throw new MethodNotSupportedError("waitForTransaction", this.type);
|
|
146
|
+
}
|
|
147
|
+
getSigner() {
|
|
148
|
+
throw new MethodNotSupportedError("getSigner", this.type);
|
|
149
|
+
}
|
|
150
|
+
// 工具方法
|
|
151
|
+
ensureConnected() {
|
|
152
|
+
if (this.state !== "connected" /* CONNECTED */ || !this.currentAccount) {
|
|
153
|
+
throw new WalletNotConnectedError(this.type);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
setState(state) {
|
|
157
|
+
this.state = state;
|
|
158
|
+
}
|
|
159
|
+
setAccount(account) {
|
|
160
|
+
this.currentAccount = account;
|
|
161
|
+
}
|
|
162
|
+
emitAccountChanged(account) {
|
|
163
|
+
this.emit("accountChanged", account);
|
|
164
|
+
}
|
|
165
|
+
emitChainChanged(chainId) {
|
|
166
|
+
this.emit("chainChanged", chainId);
|
|
167
|
+
}
|
|
168
|
+
emitDisconnected() {
|
|
169
|
+
this.emit("disconnected");
|
|
170
|
+
}
|
|
171
|
+
emitError(error) {
|
|
172
|
+
this.emit("error", error);
|
|
173
|
+
}
|
|
174
|
+
// EventEmitter 方法(从 EventEmitter3 继承)
|
|
175
|
+
// removeAllListeners 已经由 EventEmitter 提供
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/adapters/base/browser-wallet-adapter.ts
|
|
179
|
+
var BrowserWalletAdapter = class extends WalletAdapter {
|
|
180
|
+
/**
|
|
181
|
+
* 检查钱包是否可用
|
|
182
|
+
*/
|
|
183
|
+
async isAvailable() {
|
|
184
|
+
if (typeof window === "undefined") {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
return this.getBrowserProvider() !== void 0;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 确保钱包已安装
|
|
191
|
+
*/
|
|
192
|
+
async ensureAvailable() {
|
|
193
|
+
const isAvailable = await this.isAvailable();
|
|
194
|
+
if (!isAvailable) {
|
|
195
|
+
throw new WalletNotAvailableError(
|
|
196
|
+
this.type,
|
|
197
|
+
this.getDownloadUrl()
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 断开连接时清理
|
|
203
|
+
*/
|
|
204
|
+
async disconnect() {
|
|
205
|
+
this.removeEventListeners();
|
|
206
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
207
|
+
this.setAccount(null);
|
|
208
|
+
this.emitDisconnected();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/utils/address/universal-address.ts
|
|
213
|
+
function createUniversalAddress(chainId, address) {
|
|
214
|
+
return `${chainId}:${address}`;
|
|
215
|
+
}
|
|
216
|
+
function formatEVMAddress(address) {
|
|
217
|
+
if (!viem.isAddress(address)) {
|
|
218
|
+
throw new Error(`Invalid EVM address: ${address}`);
|
|
219
|
+
}
|
|
220
|
+
return viem.getAddress(address);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/utils/chain-info.ts
|
|
224
|
+
var CHAIN_INFO = {
|
|
225
|
+
// EVM Mainnet
|
|
226
|
+
1: {
|
|
227
|
+
id: 1,
|
|
228
|
+
name: "Ethereum Mainnet",
|
|
229
|
+
chainType: "evm" /* EVM */,
|
|
230
|
+
nativeCurrency: {
|
|
231
|
+
name: "Ether",
|
|
232
|
+
symbol: "ETH",
|
|
233
|
+
decimals: 18
|
|
234
|
+
},
|
|
235
|
+
rpcUrls: ["https://eth.llamarpc.com"],
|
|
236
|
+
blockExplorerUrls: ["https://etherscan.io"]
|
|
237
|
+
},
|
|
238
|
+
// EVM Testnets
|
|
239
|
+
11155111: {
|
|
240
|
+
id: 11155111,
|
|
241
|
+
name: "Sepolia Testnet",
|
|
242
|
+
chainType: "evm" /* EVM */,
|
|
243
|
+
nativeCurrency: {
|
|
244
|
+
name: "Sepolia Ether",
|
|
245
|
+
symbol: "ETH",
|
|
246
|
+
decimals: 18
|
|
247
|
+
},
|
|
248
|
+
rpcUrls: ["https://rpc.sepolia.org"],
|
|
249
|
+
blockExplorerUrls: ["https://sepolia.etherscan.io"]
|
|
250
|
+
},
|
|
251
|
+
// Binance Smart Chain
|
|
252
|
+
56: {
|
|
253
|
+
id: 56,
|
|
254
|
+
name: "BNB Smart Chain",
|
|
255
|
+
chainType: "evm" /* EVM */,
|
|
256
|
+
nativeCurrency: {
|
|
257
|
+
name: "BNB",
|
|
258
|
+
symbol: "BNB",
|
|
259
|
+
decimals: 18
|
|
260
|
+
},
|
|
261
|
+
rpcUrls: ["https://bsc-dataseed.binance.org"],
|
|
262
|
+
blockExplorerUrls: ["https://bscscan.com"]
|
|
263
|
+
},
|
|
264
|
+
97: {
|
|
265
|
+
id: 97,
|
|
266
|
+
name: "BNB Smart Chain Testnet",
|
|
267
|
+
chainType: "evm" /* EVM */,
|
|
268
|
+
nativeCurrency: {
|
|
269
|
+
name: "BNB",
|
|
270
|
+
symbol: "BNB",
|
|
271
|
+
decimals: 18
|
|
272
|
+
},
|
|
273
|
+
rpcUrls: ["https://data-seed-prebsc-1-s1.binance.org:8545"],
|
|
274
|
+
blockExplorerUrls: ["https://testnet.bscscan.com"]
|
|
275
|
+
},
|
|
276
|
+
// Polygon
|
|
277
|
+
137: {
|
|
278
|
+
id: 137,
|
|
279
|
+
name: "Polygon Mainnet",
|
|
280
|
+
chainType: "evm" /* EVM */,
|
|
281
|
+
nativeCurrency: {
|
|
282
|
+
name: "MATIC",
|
|
283
|
+
symbol: "MATIC",
|
|
284
|
+
decimals: 18
|
|
285
|
+
},
|
|
286
|
+
rpcUrls: ["https://polygon-rpc.com"],
|
|
287
|
+
blockExplorerUrls: ["https://polygonscan.com"]
|
|
288
|
+
},
|
|
289
|
+
80002: {
|
|
290
|
+
id: 80002,
|
|
291
|
+
name: "Polygon Amoy Testnet",
|
|
292
|
+
chainType: "evm" /* EVM */,
|
|
293
|
+
nativeCurrency: {
|
|
294
|
+
name: "MATIC",
|
|
295
|
+
symbol: "MATIC",
|
|
296
|
+
decimals: 18
|
|
297
|
+
},
|
|
298
|
+
rpcUrls: ["https://rpc-amoy.polygon.technology"],
|
|
299
|
+
blockExplorerUrls: ["https://www.oklink.com/amoy"]
|
|
300
|
+
},
|
|
301
|
+
// Tron
|
|
302
|
+
195: {
|
|
303
|
+
id: 195,
|
|
304
|
+
name: "Tron Mainnet",
|
|
305
|
+
chainType: "tron" /* TRON */,
|
|
306
|
+
nativeCurrency: {
|
|
307
|
+
name: "TRX",
|
|
308
|
+
symbol: "TRX",
|
|
309
|
+
decimals: 6
|
|
310
|
+
},
|
|
311
|
+
rpcUrls: ["https://api.trongrid.io"],
|
|
312
|
+
blockExplorerUrls: ["https://tronscan.org"]
|
|
313
|
+
},
|
|
314
|
+
// Arbitrum
|
|
315
|
+
42161: {
|
|
316
|
+
id: 42161,
|
|
317
|
+
name: "Arbitrum One",
|
|
318
|
+
chainType: "evm" /* EVM */,
|
|
319
|
+
nativeCurrency: {
|
|
320
|
+
name: "Ether",
|
|
321
|
+
symbol: "ETH",
|
|
322
|
+
decimals: 18
|
|
323
|
+
},
|
|
324
|
+
rpcUrls: ["https://arb1.arbitrum.io/rpc"],
|
|
325
|
+
blockExplorerUrls: ["https://arbiscan.io"]
|
|
326
|
+
},
|
|
327
|
+
// Optimism
|
|
328
|
+
10: {
|
|
329
|
+
id: 10,
|
|
330
|
+
name: "Optimism",
|
|
331
|
+
chainType: "evm" /* EVM */,
|
|
332
|
+
nativeCurrency: {
|
|
333
|
+
name: "Ether",
|
|
334
|
+
symbol: "ETH",
|
|
335
|
+
decimals: 18
|
|
336
|
+
},
|
|
337
|
+
rpcUrls: ["https://mainnet.optimism.io"],
|
|
338
|
+
blockExplorerUrls: ["https://optimistic.etherscan.io"]
|
|
339
|
+
},
|
|
340
|
+
// Avalanche
|
|
341
|
+
43114: {
|
|
342
|
+
id: 43114,
|
|
343
|
+
name: "Avalanche C-Chain",
|
|
344
|
+
chainType: "evm" /* EVM */,
|
|
345
|
+
nativeCurrency: {
|
|
346
|
+
name: "AVAX",
|
|
347
|
+
symbol: "AVAX",
|
|
348
|
+
decimals: 18
|
|
349
|
+
},
|
|
350
|
+
rpcUrls: ["https://api.avax.network/ext/bc/C/rpc"],
|
|
351
|
+
blockExplorerUrls: ["https://snowtrace.io"]
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function getChainInfo(chainId) {
|
|
355
|
+
return CHAIN_INFO[chainId];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/adapters/evm/metamask.ts
|
|
359
|
+
var MetaMaskAdapter = class extends BrowserWalletAdapter {
|
|
360
|
+
constructor() {
|
|
361
|
+
super(...arguments);
|
|
362
|
+
this.type = "metamask" /* METAMASK */;
|
|
363
|
+
this.chainType = "evm" /* EVM */;
|
|
364
|
+
this.name = "MetaMask";
|
|
365
|
+
this.icon = "https://upload.wikimedia.org/wikipedia/commons/3/36/MetaMask_Fox.svg";
|
|
366
|
+
this.walletClient = null;
|
|
367
|
+
this.publicClient = null;
|
|
368
|
+
/**
|
|
369
|
+
* 处理账户变化
|
|
370
|
+
*/
|
|
371
|
+
this.handleAccountsChanged = (accounts) => {
|
|
372
|
+
if (accounts.length === 0) {
|
|
373
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
374
|
+
this.setAccount(null);
|
|
375
|
+
this.emitAccountChanged(null);
|
|
376
|
+
} else {
|
|
377
|
+
const address = formatEVMAddress(accounts[0]);
|
|
378
|
+
const account = {
|
|
379
|
+
universalAddress: createUniversalAddress(this.currentAccount.chainId, address),
|
|
380
|
+
nativeAddress: address,
|
|
381
|
+
chainId: this.currentAccount.chainId,
|
|
382
|
+
chainType: "evm" /* EVM */,
|
|
383
|
+
isActive: true
|
|
384
|
+
};
|
|
385
|
+
this.setAccount(account);
|
|
386
|
+
this.emitAccountChanged(account);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
/**
|
|
390
|
+
* 处理链变化
|
|
391
|
+
*/
|
|
392
|
+
this.handleChainChanged = (chainIdHex) => {
|
|
393
|
+
const chainId = parseInt(chainIdHex, 16);
|
|
394
|
+
if (this.currentAccount) {
|
|
395
|
+
const account = {
|
|
396
|
+
...this.currentAccount,
|
|
397
|
+
chainId,
|
|
398
|
+
universalAddress: createUniversalAddress(chainId, this.currentAccount.nativeAddress)
|
|
399
|
+
};
|
|
400
|
+
this.setAccount(account);
|
|
401
|
+
this.emitChainChanged(chainId);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
/**
|
|
405
|
+
* 处理断开连接
|
|
406
|
+
*/
|
|
407
|
+
this.handleDisconnect = () => {
|
|
408
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
409
|
+
this.setAccount(null);
|
|
410
|
+
this.emitDisconnected();
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 连接钱包
|
|
415
|
+
*/
|
|
416
|
+
async connect(chainId) {
|
|
417
|
+
await this.ensureAvailable();
|
|
418
|
+
try {
|
|
419
|
+
this.setState("connecting" /* CONNECTING */);
|
|
420
|
+
const provider = this.getBrowserProvider();
|
|
421
|
+
const accounts = await provider.request({
|
|
422
|
+
method: "eth_requestAccounts"
|
|
423
|
+
});
|
|
424
|
+
if (!accounts || accounts.length === 0) {
|
|
425
|
+
throw new ConnectionRejectedError(this.type);
|
|
426
|
+
}
|
|
427
|
+
const currentChainId = await provider.request({
|
|
428
|
+
method: "eth_chainId"
|
|
429
|
+
});
|
|
430
|
+
const parsedChainId = parseInt(currentChainId, 16);
|
|
431
|
+
if (chainId && chainId !== parsedChainId) {
|
|
432
|
+
await this.switchChain(chainId);
|
|
433
|
+
}
|
|
434
|
+
this.walletClient = viem.createWalletClient({
|
|
435
|
+
account: accounts[0],
|
|
436
|
+
transport: viem.custom(provider)
|
|
437
|
+
});
|
|
438
|
+
const finalChainId = chainId || parsedChainId;
|
|
439
|
+
this.publicClient = viem.createPublicClient({
|
|
440
|
+
chain: this.getViemChain(finalChainId),
|
|
441
|
+
transport: viem.http()
|
|
442
|
+
});
|
|
443
|
+
const address = formatEVMAddress(accounts[0]);
|
|
444
|
+
const account = {
|
|
445
|
+
universalAddress: createUniversalAddress(finalChainId, address),
|
|
446
|
+
nativeAddress: address,
|
|
447
|
+
chainId: finalChainId,
|
|
448
|
+
chainType: "evm" /* EVM */,
|
|
449
|
+
isActive: true
|
|
450
|
+
};
|
|
451
|
+
this.setState("connected" /* CONNECTED */);
|
|
452
|
+
this.setAccount(account);
|
|
453
|
+
this.setupEventListeners();
|
|
454
|
+
return account;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.setState("error" /* ERROR */);
|
|
457
|
+
this.setAccount(null);
|
|
458
|
+
if (error.code === 4001) {
|
|
459
|
+
throw new ConnectionRejectedError(this.type);
|
|
460
|
+
}
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* 签名消息
|
|
466
|
+
*/
|
|
467
|
+
async signMessage(message) {
|
|
468
|
+
this.ensureConnected();
|
|
469
|
+
try {
|
|
470
|
+
const provider = this.getBrowserProvider();
|
|
471
|
+
const signature = await provider.request({
|
|
472
|
+
method: "personal_sign",
|
|
473
|
+
params: [message, this.currentAccount.nativeAddress]
|
|
474
|
+
});
|
|
475
|
+
return signature;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (error.code === 4001) {
|
|
478
|
+
throw new SignatureRejectedError();
|
|
479
|
+
}
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* 签名 TypedData (EIP-712)
|
|
485
|
+
*/
|
|
486
|
+
async signTypedData(typedData) {
|
|
487
|
+
this.ensureConnected();
|
|
488
|
+
try {
|
|
489
|
+
const provider = this.getBrowserProvider();
|
|
490
|
+
const signature = await provider.request({
|
|
491
|
+
method: "eth_signTypedData_v4",
|
|
492
|
+
params: [this.currentAccount.nativeAddress, JSON.stringify(typedData)]
|
|
493
|
+
});
|
|
494
|
+
return signature;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error.code === 4001) {
|
|
497
|
+
throw new SignatureRejectedError();
|
|
498
|
+
}
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 切换链
|
|
504
|
+
*/
|
|
505
|
+
async switchChain(chainId) {
|
|
506
|
+
this.ensureConnected();
|
|
507
|
+
const provider = this.getBrowserProvider();
|
|
508
|
+
try {
|
|
509
|
+
await provider.request({
|
|
510
|
+
method: "wallet_switchEthereumChain",
|
|
511
|
+
params: [{ chainId: `0x${chainId.toString(16)}` }]
|
|
512
|
+
});
|
|
513
|
+
if (this.currentAccount) {
|
|
514
|
+
const updatedAccount = {
|
|
515
|
+
...this.currentAccount,
|
|
516
|
+
chainId,
|
|
517
|
+
universalAddress: createUniversalAddress(chainId, this.currentAccount.nativeAddress)
|
|
518
|
+
};
|
|
519
|
+
this.setAccount(updatedAccount);
|
|
520
|
+
this.emitChainChanged(chainId);
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (error.code === 4902) {
|
|
524
|
+
const chainInfo = getChainInfo(chainId);
|
|
525
|
+
if (chainInfo) {
|
|
526
|
+
await this.addChain({
|
|
527
|
+
chainId: chainInfo.id,
|
|
528
|
+
chainName: chainInfo.name,
|
|
529
|
+
nativeCurrency: chainInfo.nativeCurrency,
|
|
530
|
+
rpcUrls: chainInfo.rpcUrls,
|
|
531
|
+
blockExplorerUrls: chainInfo.blockExplorerUrls
|
|
532
|
+
});
|
|
533
|
+
await this.switchChain(chainId);
|
|
534
|
+
} else {
|
|
535
|
+
throw new Error(`Chain ${chainId} not supported`);
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* 添加链
|
|
544
|
+
*/
|
|
545
|
+
async addChain(chainConfig) {
|
|
546
|
+
const provider = this.getBrowserProvider();
|
|
547
|
+
await provider.request({
|
|
548
|
+
method: "wallet_addEthereumChain",
|
|
549
|
+
params: [{
|
|
550
|
+
chainId: `0x${chainConfig.chainId.toString(16)}`,
|
|
551
|
+
chainName: chainConfig.chainName,
|
|
552
|
+
nativeCurrency: chainConfig.nativeCurrency,
|
|
553
|
+
rpcUrls: chainConfig.rpcUrls,
|
|
554
|
+
blockExplorerUrls: chainConfig.blockExplorerUrls
|
|
555
|
+
}]
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 读取合约
|
|
560
|
+
*/
|
|
561
|
+
async readContract(params) {
|
|
562
|
+
if (!this.publicClient) {
|
|
563
|
+
throw new Error("Public client not initialized");
|
|
564
|
+
}
|
|
565
|
+
const result = await this.publicClient.readContract({
|
|
566
|
+
address: params.address,
|
|
567
|
+
abi: params.abi,
|
|
568
|
+
functionName: params.functionName,
|
|
569
|
+
...params.args ? { args: params.args } : {}
|
|
570
|
+
});
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 写入合约
|
|
575
|
+
*/
|
|
576
|
+
async writeContract(params) {
|
|
577
|
+
this.ensureConnected();
|
|
578
|
+
if (!this.walletClient) {
|
|
579
|
+
throw new Error("Wallet client not initialized");
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const txHash = await this.walletClient.writeContract({
|
|
583
|
+
address: params.address,
|
|
584
|
+
abi: params.abi,
|
|
585
|
+
functionName: params.functionName,
|
|
586
|
+
...params.args ? { args: params.args } : {},
|
|
587
|
+
value: params.value ? BigInt(params.value) : void 0,
|
|
588
|
+
gas: params.gas ? BigInt(params.gas) : void 0,
|
|
589
|
+
gasPrice: params.gasPrice ? BigInt(params.gasPrice) : void 0
|
|
590
|
+
});
|
|
591
|
+
return txHash;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (error.code === 4001) {
|
|
594
|
+
throw new SignatureRejectedError("Transaction was rejected by user");
|
|
595
|
+
}
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* 估算 gas
|
|
601
|
+
*/
|
|
602
|
+
async estimateGas(params) {
|
|
603
|
+
if (!this.publicClient) {
|
|
604
|
+
throw new Error("Public client not initialized");
|
|
605
|
+
}
|
|
606
|
+
const gas = await this.publicClient.estimateContractGas({
|
|
607
|
+
address: params.address,
|
|
608
|
+
abi: params.abi,
|
|
609
|
+
functionName: params.functionName,
|
|
610
|
+
...params.args ? { args: params.args } : {},
|
|
611
|
+
value: params.value ? BigInt(params.value) : void 0,
|
|
612
|
+
account: this.currentAccount.nativeAddress
|
|
613
|
+
});
|
|
614
|
+
return gas;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* 等待交易确认
|
|
618
|
+
*/
|
|
619
|
+
async waitForTransaction(txHash, confirmations = 1) {
|
|
620
|
+
if (!this.publicClient) {
|
|
621
|
+
throw new Error("Public client not initialized");
|
|
622
|
+
}
|
|
623
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
624
|
+
hash: txHash,
|
|
625
|
+
confirmations
|
|
626
|
+
});
|
|
627
|
+
if (receipt.status === "reverted") {
|
|
628
|
+
throw new TransactionFailedError(txHash, "Transaction reverted");
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
transactionHash: receipt.transactionHash,
|
|
632
|
+
blockNumber: Number(receipt.blockNumber),
|
|
633
|
+
blockHash: receipt.blockHash,
|
|
634
|
+
from: receipt.from,
|
|
635
|
+
to: receipt.to || void 0,
|
|
636
|
+
status: receipt.status === "success" ? "success" : "failed",
|
|
637
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
638
|
+
effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
|
|
639
|
+
logs: receipt.logs
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* 获取 Provider
|
|
644
|
+
*/
|
|
645
|
+
getProvider() {
|
|
646
|
+
return this.getBrowserProvider();
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* 获取 Signer
|
|
650
|
+
*/
|
|
651
|
+
getSigner() {
|
|
652
|
+
return this.walletClient;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* 获取浏览器中的 MetaMask provider
|
|
656
|
+
*/
|
|
657
|
+
getBrowserProvider() {
|
|
658
|
+
if (typeof window === "undefined") {
|
|
659
|
+
return void 0;
|
|
660
|
+
}
|
|
661
|
+
const w = window;
|
|
662
|
+
return w.ethereum && w.ethereum.isMetaMask ? w.ethereum : void 0;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* 获取下载链接
|
|
666
|
+
*/
|
|
667
|
+
getDownloadUrl() {
|
|
668
|
+
return "https://metamask.io/download/";
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* 设置事件监听
|
|
672
|
+
*/
|
|
673
|
+
setupEventListeners() {
|
|
674
|
+
const provider = this.getBrowserProvider();
|
|
675
|
+
if (!provider) return;
|
|
676
|
+
provider.on("accountsChanged", this.handleAccountsChanged);
|
|
677
|
+
provider.on("chainChanged", this.handleChainChanged);
|
|
678
|
+
provider.on("disconnect", this.handleDisconnect);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* 移除事件监听
|
|
682
|
+
*/
|
|
683
|
+
removeEventListeners() {
|
|
684
|
+
const provider = this.getBrowserProvider();
|
|
685
|
+
if (!provider) return;
|
|
686
|
+
provider.removeListener("accountsChanged", this.handleAccountsChanged);
|
|
687
|
+
provider.removeListener("chainChanged", this.handleChainChanged);
|
|
688
|
+
provider.removeListener("disconnect", this.handleDisconnect);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* 获取 viem chain 配置(简化版)
|
|
692
|
+
*/
|
|
693
|
+
getViemChain(chainId) {
|
|
694
|
+
const chainInfo = getChainInfo(chainId);
|
|
695
|
+
if (chainInfo) {
|
|
696
|
+
return {
|
|
697
|
+
id: chainId,
|
|
698
|
+
name: chainInfo.name,
|
|
699
|
+
network: chainInfo.name.toLowerCase().replace(/\s+/g, "-"),
|
|
700
|
+
nativeCurrency: chainInfo.nativeCurrency,
|
|
701
|
+
rpcUrls: {
|
|
702
|
+
default: { http: chainInfo.rpcUrls },
|
|
703
|
+
public: { http: chainInfo.rpcUrls }
|
|
704
|
+
},
|
|
705
|
+
blockExplorers: chainInfo.blockExplorerUrls ? {
|
|
706
|
+
default: { name: "Explorer", url: chainInfo.blockExplorerUrls[0] }
|
|
707
|
+
} : void 0
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
id: chainId,
|
|
712
|
+
name: `Chain ${chainId}`,
|
|
713
|
+
network: `chain-${chainId}`,
|
|
714
|
+
nativeCurrency: {
|
|
715
|
+
name: "ETH",
|
|
716
|
+
symbol: "ETH",
|
|
717
|
+
decimals: 18
|
|
718
|
+
},
|
|
719
|
+
rpcUrls: {
|
|
720
|
+
default: { http: [] },
|
|
721
|
+
public: { http: [] }
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/adapters/tron/tronlink.ts
|
|
728
|
+
var _TronLinkAdapter = class _TronLinkAdapter extends BrowserWalletAdapter {
|
|
729
|
+
constructor() {
|
|
730
|
+
super(...arguments);
|
|
731
|
+
this.type = "tronlink" /* TRONLINK */;
|
|
732
|
+
this.chainType = "tron" /* TRON */;
|
|
733
|
+
this.name = "TronLink";
|
|
734
|
+
this.icon = "https://www.tronlink.org/static/logoIcon.svg";
|
|
735
|
+
/**
|
|
736
|
+
* 处理账户变化
|
|
737
|
+
*/
|
|
738
|
+
this.handleAccountsChanged = (data) => {
|
|
739
|
+
if (!data || !data.address) {
|
|
740
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
741
|
+
this.setAccount(null);
|
|
742
|
+
this.emitAccountChanged(null);
|
|
743
|
+
} else {
|
|
744
|
+
const address = data.address.base58 || data.address;
|
|
745
|
+
const account = {
|
|
746
|
+
universalAddress: createUniversalAddress(this.currentAccount.chainId, address),
|
|
747
|
+
nativeAddress: address,
|
|
748
|
+
chainId: this.currentAccount.chainId,
|
|
749
|
+
chainType: "tron" /* TRON */,
|
|
750
|
+
isActive: true
|
|
751
|
+
};
|
|
752
|
+
this.setAccount(account);
|
|
753
|
+
this.emitAccountChanged(account);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
/**
|
|
757
|
+
* 处理断开连接
|
|
758
|
+
*/
|
|
759
|
+
this.handleDisconnect = () => {
|
|
760
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
761
|
+
this.setAccount(null);
|
|
762
|
+
this.emitDisconnected();
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* 连接钱包
|
|
767
|
+
*/
|
|
768
|
+
async connect(chainId) {
|
|
769
|
+
await this.ensureAvailable();
|
|
770
|
+
try {
|
|
771
|
+
this.setState("connecting" /* CONNECTING */);
|
|
772
|
+
const tronWeb = this.getTronWeb();
|
|
773
|
+
const result = await tronWeb.request({
|
|
774
|
+
method: "tron_requestAccounts"
|
|
775
|
+
});
|
|
776
|
+
if (!result || !result.code || result.code !== 200) {
|
|
777
|
+
throw new ConnectionRejectedError(this.type);
|
|
778
|
+
}
|
|
779
|
+
const address = tronWeb.defaultAddress?.base58;
|
|
780
|
+
if (!address) {
|
|
781
|
+
throw new Error("Failed to get Tron address");
|
|
782
|
+
}
|
|
783
|
+
const tronChainId = chainId || _TronLinkAdapter.TRON_MAINNET_CHAIN_ID;
|
|
784
|
+
const account = {
|
|
785
|
+
universalAddress: createUniversalAddress(tronChainId, address),
|
|
786
|
+
nativeAddress: address,
|
|
787
|
+
chainId: tronChainId,
|
|
788
|
+
chainType: "tron" /* TRON */,
|
|
789
|
+
isActive: true
|
|
790
|
+
};
|
|
791
|
+
this.setState("connected" /* CONNECTED */);
|
|
792
|
+
this.setAccount(account);
|
|
793
|
+
this.setupEventListeners();
|
|
794
|
+
return account;
|
|
795
|
+
} catch (error) {
|
|
796
|
+
this.setState("error" /* ERROR */);
|
|
797
|
+
this.setAccount(null);
|
|
798
|
+
if (error.code === 4001 || error.message?.includes("User rejected")) {
|
|
799
|
+
throw new ConnectionRejectedError(this.type);
|
|
800
|
+
}
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* 签名消息
|
|
806
|
+
*/
|
|
807
|
+
async signMessage(message) {
|
|
808
|
+
this.ensureConnected();
|
|
809
|
+
try {
|
|
810
|
+
const tronWeb = this.getTronWeb();
|
|
811
|
+
const signature = await tronWeb.trx.sign(message);
|
|
812
|
+
return signature;
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (error.message?.includes("User rejected") || error.message?.includes("Confirmation declined")) {
|
|
815
|
+
throw new SignatureRejectedError();
|
|
816
|
+
}
|
|
817
|
+
throw error;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* 获取 Provider
|
|
822
|
+
*/
|
|
823
|
+
getProvider() {
|
|
824
|
+
return this.getTronWeb();
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* 获取浏览器中的 TronWeb
|
|
828
|
+
*/
|
|
829
|
+
getBrowserProvider() {
|
|
830
|
+
if (typeof window === "undefined") {
|
|
831
|
+
return void 0;
|
|
832
|
+
}
|
|
833
|
+
const w = window;
|
|
834
|
+
return w.tronWeb || w.tronLink?.tronWeb;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* 获取 TronWeb 实例
|
|
838
|
+
*/
|
|
839
|
+
getTronWeb() {
|
|
840
|
+
const provider = this.getBrowserProvider();
|
|
841
|
+
if (!provider) {
|
|
842
|
+
throw new Error("TronWeb not found");
|
|
843
|
+
}
|
|
844
|
+
return provider;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* 获取下载链接
|
|
848
|
+
*/
|
|
849
|
+
getDownloadUrl() {
|
|
850
|
+
return "https://www.tronlink.org/";
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* 设置事件监听
|
|
854
|
+
*/
|
|
855
|
+
setupEventListeners() {
|
|
856
|
+
if (typeof window === "undefined") return;
|
|
857
|
+
const w = window;
|
|
858
|
+
if (w.tronLink) {
|
|
859
|
+
w.tronLink.on("accountsChanged", this.handleAccountsChanged);
|
|
860
|
+
w.tronLink.on("disconnect", this.handleDisconnect);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* 移除事件监听
|
|
865
|
+
*/
|
|
866
|
+
removeEventListeners() {
|
|
867
|
+
if (typeof window === "undefined") return;
|
|
868
|
+
const w = window;
|
|
869
|
+
if (w.tronLink) {
|
|
870
|
+
w.tronLink.off("accountsChanged", this.handleAccountsChanged);
|
|
871
|
+
w.tronLink.off("disconnect", this.handleDisconnect);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
// Tron 主网链 ID
|
|
876
|
+
_TronLinkAdapter.TRON_MAINNET_CHAIN_ID = 195;
|
|
877
|
+
var TronLinkAdapter = _TronLinkAdapter;
|
|
878
|
+
var EVMPrivateKeyAdapter = class extends WalletAdapter {
|
|
879
|
+
constructor() {
|
|
880
|
+
super(...arguments);
|
|
881
|
+
this.type = "private-key" /* PRIVATE_KEY */;
|
|
882
|
+
this.chainType = "evm" /* EVM */;
|
|
883
|
+
this.name = "Private Key (EVM)";
|
|
884
|
+
this.privateKey = null;
|
|
885
|
+
this.walletClient = null;
|
|
886
|
+
this.publicClient = null;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* 连接(导入私钥)
|
|
890
|
+
*/
|
|
891
|
+
async connect(chainId = 1) {
|
|
892
|
+
if (!this.privateKey) {
|
|
893
|
+
throw new Error("Private key not set. Call setPrivateKey() first.");
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
this.setState("connecting" /* CONNECTING */);
|
|
897
|
+
const account = accounts.privateKeyToAccount(this.privateKey);
|
|
898
|
+
this.walletClient = viem.createWalletClient({
|
|
899
|
+
account,
|
|
900
|
+
chain: this.getViemChain(chainId),
|
|
901
|
+
transport: viem.http()
|
|
902
|
+
});
|
|
903
|
+
this.publicClient = viem.createPublicClient({
|
|
904
|
+
chain: this.getViemChain(chainId),
|
|
905
|
+
transport: viem.http()
|
|
906
|
+
});
|
|
907
|
+
const address = formatEVMAddress(account.address);
|
|
908
|
+
const accountInfo = {
|
|
909
|
+
universalAddress: createUniversalAddress(chainId, address),
|
|
910
|
+
nativeAddress: address,
|
|
911
|
+
chainId,
|
|
912
|
+
chainType: "evm" /* EVM */,
|
|
913
|
+
isActive: true
|
|
914
|
+
};
|
|
915
|
+
this.setState("connected" /* CONNECTED */);
|
|
916
|
+
this.setAccount(accountInfo);
|
|
917
|
+
return accountInfo;
|
|
918
|
+
} catch (error) {
|
|
919
|
+
this.setState("error" /* ERROR */);
|
|
920
|
+
this.setAccount(null);
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* 断开连接
|
|
926
|
+
*/
|
|
927
|
+
async disconnect() {
|
|
928
|
+
this.privateKey = null;
|
|
929
|
+
this.walletClient = null;
|
|
930
|
+
this.publicClient = null;
|
|
931
|
+
this.setState("disconnected" /* DISCONNECTED */);
|
|
932
|
+
this.setAccount(null);
|
|
933
|
+
this.emitDisconnected();
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* 检查是否可用(私钥钱包总是可用)
|
|
937
|
+
*/
|
|
938
|
+
async isAvailable() {
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* 设置私钥
|
|
943
|
+
*/
|
|
944
|
+
setPrivateKey(privateKey) {
|
|
945
|
+
this.privateKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* 签名消息
|
|
949
|
+
*/
|
|
950
|
+
async signMessage(message) {
|
|
951
|
+
this.ensureConnected();
|
|
952
|
+
if (!this.walletClient || !this.walletClient.account) {
|
|
953
|
+
throw new Error("Wallet client not initialized");
|
|
954
|
+
}
|
|
955
|
+
const signature = await this.walletClient.signMessage({
|
|
956
|
+
message,
|
|
957
|
+
account: this.walletClient.account
|
|
958
|
+
});
|
|
959
|
+
return signature;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* 签名 TypedData
|
|
963
|
+
*/
|
|
964
|
+
async signTypedData(typedData) {
|
|
965
|
+
this.ensureConnected();
|
|
966
|
+
if (!this.walletClient || !this.walletClient.account) {
|
|
967
|
+
throw new Error("Wallet client not initialized");
|
|
968
|
+
}
|
|
969
|
+
const signature = await this.walletClient.signTypedData({
|
|
970
|
+
...typedData,
|
|
971
|
+
account: this.walletClient.account
|
|
972
|
+
});
|
|
973
|
+
return signature;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* 切换链
|
|
977
|
+
*/
|
|
978
|
+
async switchChain(chainId) {
|
|
979
|
+
this.ensureConnected();
|
|
980
|
+
const account = this.walletClient.account;
|
|
981
|
+
this.walletClient = viem.createWalletClient({
|
|
982
|
+
account,
|
|
983
|
+
chain: this.getViemChain(chainId),
|
|
984
|
+
transport: viem.http()
|
|
985
|
+
});
|
|
986
|
+
this.publicClient = viem.createPublicClient({
|
|
987
|
+
chain: this.getViemChain(chainId),
|
|
988
|
+
transport: viem.http()
|
|
989
|
+
});
|
|
990
|
+
const updatedAccount = {
|
|
991
|
+
...this.currentAccount,
|
|
992
|
+
chainId,
|
|
993
|
+
universalAddress: createUniversalAddress(chainId, this.currentAccount.nativeAddress)
|
|
994
|
+
};
|
|
995
|
+
this.setAccount(updatedAccount);
|
|
996
|
+
this.emitChainChanged(chainId);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* 读取合约
|
|
1000
|
+
*/
|
|
1001
|
+
async readContract(params) {
|
|
1002
|
+
if (!this.publicClient) {
|
|
1003
|
+
throw new Error("Public client not initialized");
|
|
1004
|
+
}
|
|
1005
|
+
const result = await this.publicClient.readContract({
|
|
1006
|
+
address: params.address,
|
|
1007
|
+
abi: params.abi,
|
|
1008
|
+
functionName: params.functionName,
|
|
1009
|
+
...params.args ? { args: params.args } : {}
|
|
1010
|
+
});
|
|
1011
|
+
return result;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 写入合约
|
|
1015
|
+
*/
|
|
1016
|
+
async writeContract(params) {
|
|
1017
|
+
this.ensureConnected();
|
|
1018
|
+
if (!this.walletClient) {
|
|
1019
|
+
throw new Error("Wallet client not initialized");
|
|
1020
|
+
}
|
|
1021
|
+
const txHash = await this.walletClient.writeContract({
|
|
1022
|
+
address: params.address,
|
|
1023
|
+
abi: params.abi,
|
|
1024
|
+
functionName: params.functionName,
|
|
1025
|
+
...params.args ? { args: params.args } : {},
|
|
1026
|
+
value: params.value ? BigInt(params.value) : void 0,
|
|
1027
|
+
gas: params.gas ? BigInt(params.gas) : void 0,
|
|
1028
|
+
gasPrice: params.gasPrice ? BigInt(params.gasPrice) : void 0
|
|
1029
|
+
});
|
|
1030
|
+
return txHash;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* 估算 gas
|
|
1034
|
+
*/
|
|
1035
|
+
async estimateGas(params) {
|
|
1036
|
+
if (!this.publicClient || !this.currentAccount) {
|
|
1037
|
+
throw new Error("Client not initialized");
|
|
1038
|
+
}
|
|
1039
|
+
const gas = await this.publicClient.estimateContractGas({
|
|
1040
|
+
address: params.address,
|
|
1041
|
+
abi: params.abi,
|
|
1042
|
+
functionName: params.functionName,
|
|
1043
|
+
...params.args ? { args: params.args } : {},
|
|
1044
|
+
value: params.value ? BigInt(params.value) : void 0,
|
|
1045
|
+
account: this.currentAccount.nativeAddress
|
|
1046
|
+
});
|
|
1047
|
+
return gas;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* 等待交易确认
|
|
1051
|
+
*/
|
|
1052
|
+
async waitForTransaction(txHash, confirmations = 1) {
|
|
1053
|
+
if (!this.publicClient) {
|
|
1054
|
+
throw new Error("Public client not initialized");
|
|
1055
|
+
}
|
|
1056
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
1057
|
+
hash: txHash,
|
|
1058
|
+
confirmations
|
|
1059
|
+
});
|
|
1060
|
+
if (receipt.status === "reverted") {
|
|
1061
|
+
throw new TransactionFailedError(txHash, "Transaction reverted");
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
transactionHash: receipt.transactionHash,
|
|
1065
|
+
blockNumber: Number(receipt.blockNumber),
|
|
1066
|
+
blockHash: receipt.blockHash,
|
|
1067
|
+
from: receipt.from,
|
|
1068
|
+
to: receipt.to || void 0,
|
|
1069
|
+
status: receipt.status === "success" ? "success" : "failed",
|
|
1070
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
1071
|
+
effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
|
|
1072
|
+
logs: receipt.logs
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* 获取 Provider
|
|
1077
|
+
*/
|
|
1078
|
+
getProvider() {
|
|
1079
|
+
return this.publicClient;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* 获取 Signer
|
|
1083
|
+
*/
|
|
1084
|
+
getSigner() {
|
|
1085
|
+
return this.walletClient;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* 获取 viem chain 配置
|
|
1089
|
+
*/
|
|
1090
|
+
getViemChain(chainId) {
|
|
1091
|
+
const chainInfo = getChainInfo(chainId);
|
|
1092
|
+
if (chainInfo) {
|
|
1093
|
+
return {
|
|
1094
|
+
id: chainId,
|
|
1095
|
+
name: chainInfo.name,
|
|
1096
|
+
network: chainInfo.name.toLowerCase().replace(/\s+/g, "-"),
|
|
1097
|
+
nativeCurrency: chainInfo.nativeCurrency,
|
|
1098
|
+
rpcUrls: {
|
|
1099
|
+
default: { http: chainInfo.rpcUrls },
|
|
1100
|
+
public: { http: chainInfo.rpcUrls }
|
|
1101
|
+
},
|
|
1102
|
+
blockExplorers: chainInfo.blockExplorerUrls ? {
|
|
1103
|
+
default: { name: "Explorer", url: chainInfo.blockExplorerUrls[0] }
|
|
1104
|
+
} : void 0
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
id: chainId,
|
|
1109
|
+
name: `Chain ${chainId}`,
|
|
1110
|
+
network: `chain-${chainId}`,
|
|
1111
|
+
nativeCurrency: {
|
|
1112
|
+
name: "ETH",
|
|
1113
|
+
symbol: "ETH",
|
|
1114
|
+
decimals: 18
|
|
1115
|
+
},
|
|
1116
|
+
rpcUrls: {
|
|
1117
|
+
default: { http: [] },
|
|
1118
|
+
public: { http: [] }
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
// src/core/adapter-registry.ts
|
|
1125
|
+
var AdapterRegistry = class {
|
|
1126
|
+
constructor() {
|
|
1127
|
+
this.adapters = /* @__PURE__ */ new Map();
|
|
1128
|
+
this.registerDefaultAdapters();
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* 注册默认适配器
|
|
1132
|
+
*/
|
|
1133
|
+
registerDefaultAdapters() {
|
|
1134
|
+
this.register("metamask" /* METAMASK */, () => new MetaMaskAdapter());
|
|
1135
|
+
this.register("private-key" /* PRIVATE_KEY */, () => new EVMPrivateKeyAdapter());
|
|
1136
|
+
this.register("tronlink" /* TRONLINK */, () => new TronLinkAdapter());
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* 注册适配器
|
|
1140
|
+
*/
|
|
1141
|
+
register(type, factory) {
|
|
1142
|
+
this.adapters.set(type, factory);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* 获取适配器
|
|
1146
|
+
*/
|
|
1147
|
+
getAdapter(type) {
|
|
1148
|
+
const factory = this.adapters.get(type);
|
|
1149
|
+
if (!factory) {
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
return factory();
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* 判断适配器是否已注册
|
|
1156
|
+
*/
|
|
1157
|
+
has(type) {
|
|
1158
|
+
return this.adapters.has(type);
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* 获取所有已注册的适配器类型
|
|
1162
|
+
*/
|
|
1163
|
+
getRegisteredTypes() {
|
|
1164
|
+
return Array.from(this.adapters.keys());
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* 根据链类型获取适配器类型列表
|
|
1168
|
+
*/
|
|
1169
|
+
getAdapterTypesByChainType(chainType) {
|
|
1170
|
+
const types = [];
|
|
1171
|
+
for (const type of this.adapters.keys()) {
|
|
1172
|
+
const adapter = this.getAdapter(type);
|
|
1173
|
+
if (adapter && adapter.chainType === chainType) {
|
|
1174
|
+
types.push(type);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return types;
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/core/wallet-manager.ts
|
|
1182
|
+
var WalletManager = class extends TypedEventEmitter {
|
|
1183
|
+
constructor(config = {}) {
|
|
1184
|
+
super();
|
|
1185
|
+
// 主钱包
|
|
1186
|
+
this.primaryWallet = null;
|
|
1187
|
+
// 已连接钱包池
|
|
1188
|
+
this.connectedWallets = /* @__PURE__ */ new Map();
|
|
1189
|
+
this.config = {
|
|
1190
|
+
enableStorage: config.enableStorage ?? true,
|
|
1191
|
+
storagePrefix: config.storagePrefix ?? "enclave_wallet_",
|
|
1192
|
+
defaultChainId: config.defaultChainId ?? 1,
|
|
1193
|
+
defaultTronChainId: config.defaultTronChainId ?? 195,
|
|
1194
|
+
walletConnectProjectId: config.walletConnectProjectId ?? ""
|
|
1195
|
+
};
|
|
1196
|
+
this.registry = new AdapterRegistry();
|
|
1197
|
+
if (this.config.enableStorage) {
|
|
1198
|
+
this.restoreFromStorage();
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// ===== 连接管理 =====
|
|
1202
|
+
/**
|
|
1203
|
+
* 连接主钱包
|
|
1204
|
+
*/
|
|
1205
|
+
async connect(type, chainId) {
|
|
1206
|
+
const adapter = this.registry.getAdapter(type);
|
|
1207
|
+
if (!adapter) {
|
|
1208
|
+
throw new WalletNotAvailableError(type);
|
|
1209
|
+
}
|
|
1210
|
+
const isAvailable = await adapter.isAvailable();
|
|
1211
|
+
if (!isAvailable) {
|
|
1212
|
+
throw new WalletNotAvailableError(type);
|
|
1213
|
+
}
|
|
1214
|
+
const account = await adapter.connect(chainId);
|
|
1215
|
+
this.setPrimaryWallet(adapter);
|
|
1216
|
+
this.connectedWallets.set(adapter.chainType, adapter);
|
|
1217
|
+
this.setupAdapterListeners(adapter, true);
|
|
1218
|
+
if (this.config.enableStorage) {
|
|
1219
|
+
this.saveToStorage();
|
|
1220
|
+
}
|
|
1221
|
+
return account;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* 连接额外的钱包(不改变主钱包)
|
|
1225
|
+
*/
|
|
1226
|
+
async connectAdditional(type, chainId) {
|
|
1227
|
+
const adapter = this.registry.getAdapter(type);
|
|
1228
|
+
if (!adapter) {
|
|
1229
|
+
throw new WalletNotAvailableError(type);
|
|
1230
|
+
}
|
|
1231
|
+
const isAvailable = await adapter.isAvailable();
|
|
1232
|
+
if (!isAvailable) {
|
|
1233
|
+
throw new WalletNotAvailableError(type);
|
|
1234
|
+
}
|
|
1235
|
+
const account = await adapter.connect(chainId);
|
|
1236
|
+
this.connectedWallets.set(adapter.chainType, adapter);
|
|
1237
|
+
this.setupAdapterListeners(adapter, false);
|
|
1238
|
+
if (this.config.enableStorage) {
|
|
1239
|
+
this.saveToStorage();
|
|
1240
|
+
}
|
|
1241
|
+
return account;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* 使用私钥连接(仅用于开发/测试)
|
|
1245
|
+
*/
|
|
1246
|
+
async connectWithPrivateKey(privateKey, chainId) {
|
|
1247
|
+
const adapter = new EVMPrivateKeyAdapter();
|
|
1248
|
+
adapter.setPrivateKey(privateKey);
|
|
1249
|
+
const account = await adapter.connect(chainId || this.config.defaultChainId);
|
|
1250
|
+
this.setPrimaryWallet(adapter);
|
|
1251
|
+
this.connectedWallets.set(adapter.chainType, adapter);
|
|
1252
|
+
this.setupAdapterListeners(adapter, true);
|
|
1253
|
+
return account;
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* 断开主钱包
|
|
1257
|
+
*/
|
|
1258
|
+
async disconnect() {
|
|
1259
|
+
if (!this.primaryWallet) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
await this.primaryWallet.disconnect();
|
|
1263
|
+
this.removeAdapterListeners(this.primaryWallet);
|
|
1264
|
+
this.connectedWallets.delete(this.primaryWallet.chainType);
|
|
1265
|
+
this.primaryWallet = null;
|
|
1266
|
+
if (this.config.enableStorage) {
|
|
1267
|
+
this.saveToStorage();
|
|
1268
|
+
}
|
|
1269
|
+
this.emit("disconnected");
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* 断开所有钱包
|
|
1273
|
+
*/
|
|
1274
|
+
async disconnectAll() {
|
|
1275
|
+
const wallets = Array.from(this.connectedWallets.values());
|
|
1276
|
+
for (const wallet of wallets) {
|
|
1277
|
+
await wallet.disconnect();
|
|
1278
|
+
this.removeAdapterListeners(wallet);
|
|
1279
|
+
}
|
|
1280
|
+
this.primaryWallet = null;
|
|
1281
|
+
this.connectedWallets.clear();
|
|
1282
|
+
if (this.config.enableStorage) {
|
|
1283
|
+
this.clearStorage();
|
|
1284
|
+
}
|
|
1285
|
+
this.emit("disconnected");
|
|
1286
|
+
}
|
|
1287
|
+
// ===== 主钱包管理 =====
|
|
1288
|
+
/**
|
|
1289
|
+
* 切换主钱包
|
|
1290
|
+
*/
|
|
1291
|
+
async switchPrimaryWallet(chainType) {
|
|
1292
|
+
const adapter = this.connectedWallets.get(chainType);
|
|
1293
|
+
if (!adapter || !adapter.currentAccount) {
|
|
1294
|
+
throw new WalletNotConnectedError(`Wallet for chain type ${chainType} not connected`);
|
|
1295
|
+
}
|
|
1296
|
+
const oldPrimary = this.primaryWallet?.currentAccount || null;
|
|
1297
|
+
if (this.primaryWallet) {
|
|
1298
|
+
this.removeAdapterListeners(this.primaryWallet);
|
|
1299
|
+
}
|
|
1300
|
+
this.setPrimaryWallet(adapter);
|
|
1301
|
+
this.setupAdapterListeners(adapter, true);
|
|
1302
|
+
if (this.config.enableStorage) {
|
|
1303
|
+
this.saveToStorage();
|
|
1304
|
+
}
|
|
1305
|
+
this.emit("primaryWalletSwitched", adapter.currentAccount, oldPrimary, chainType);
|
|
1306
|
+
return adapter.currentAccount;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* 获取主钱包账户
|
|
1310
|
+
*/
|
|
1311
|
+
getPrimaryAccount() {
|
|
1312
|
+
return this.primaryWallet?.currentAccount || null;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* 获取所有已连接的钱包
|
|
1316
|
+
*/
|
|
1317
|
+
getConnectedWallets() {
|
|
1318
|
+
return Array.from(this.connectedWallets.values()).map((adapter) => ({
|
|
1319
|
+
account: adapter.currentAccount,
|
|
1320
|
+
walletType: adapter.type,
|
|
1321
|
+
chainType: adapter.chainType,
|
|
1322
|
+
isPrimary: adapter === this.primaryWallet,
|
|
1323
|
+
canSwitchChain: this.canSwitchChain(adapter),
|
|
1324
|
+
adapter
|
|
1325
|
+
}));
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* 根据链类型获取钱包
|
|
1329
|
+
*/
|
|
1330
|
+
getWalletByChainType(chainType) {
|
|
1331
|
+
return this.connectedWallets.get(chainType) || null;
|
|
1332
|
+
}
|
|
1333
|
+
// ===== 签名 =====
|
|
1334
|
+
/**
|
|
1335
|
+
* 使用主钱包签名
|
|
1336
|
+
*/
|
|
1337
|
+
async signMessage(message) {
|
|
1338
|
+
if (!this.primaryWallet) {
|
|
1339
|
+
throw new WalletNotConnectedError();
|
|
1340
|
+
}
|
|
1341
|
+
return this.primaryWallet.signMessage(message);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* 使用指定链类型的钱包签名
|
|
1345
|
+
*/
|
|
1346
|
+
async signMessageWithChainType(message, chainType) {
|
|
1347
|
+
if (!chainType) {
|
|
1348
|
+
return this.signMessage(message);
|
|
1349
|
+
}
|
|
1350
|
+
const adapter = this.connectedWallets.get(chainType);
|
|
1351
|
+
if (!adapter) {
|
|
1352
|
+
throw new WalletNotConnectedError(`Wallet for chain type ${chainType}`);
|
|
1353
|
+
}
|
|
1354
|
+
return adapter.signMessage(message);
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* 签名 TypedData(仅 EVM)
|
|
1358
|
+
*/
|
|
1359
|
+
async signTypedData(typedData, chainType) {
|
|
1360
|
+
const adapter = chainType ? this.connectedWallets.get(chainType) : this.primaryWallet;
|
|
1361
|
+
if (!adapter) {
|
|
1362
|
+
throw new WalletNotConnectedError();
|
|
1363
|
+
}
|
|
1364
|
+
if (!adapter.signTypedData) {
|
|
1365
|
+
throw new Error(`signTypedData not supported by ${adapter.type}`);
|
|
1366
|
+
}
|
|
1367
|
+
return adapter.signTypedData(typedData);
|
|
1368
|
+
}
|
|
1369
|
+
// ===== 链切换 =====
|
|
1370
|
+
/**
|
|
1371
|
+
* 请求切换链(仅 EVM)
|
|
1372
|
+
*/
|
|
1373
|
+
async requestSwitchChain(chainId, options) {
|
|
1374
|
+
if (!this.primaryWallet) {
|
|
1375
|
+
throw new WalletNotConnectedError();
|
|
1376
|
+
}
|
|
1377
|
+
if (!this.primaryWallet.switchChain) {
|
|
1378
|
+
throw new Error(`Chain switching not supported by ${this.primaryWallet.type}`);
|
|
1379
|
+
}
|
|
1380
|
+
try {
|
|
1381
|
+
await this.primaryWallet.switchChain(chainId);
|
|
1382
|
+
return this.primaryWallet.currentAccount;
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
if (options?.addChainIfNotExists && options.chainConfig && this.primaryWallet.addChain) {
|
|
1385
|
+
await this.primaryWallet.addChain(options.chainConfig);
|
|
1386
|
+
await this.primaryWallet.switchChain(chainId);
|
|
1387
|
+
return this.primaryWallet.currentAccount;
|
|
1388
|
+
}
|
|
1389
|
+
throw error;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// ===== 合约调用 =====
|
|
1393
|
+
/**
|
|
1394
|
+
* 读取合约
|
|
1395
|
+
*/
|
|
1396
|
+
async readContract(address, abi, functionName, args, chainType) {
|
|
1397
|
+
const adapter = chainType ? this.connectedWallets.get(chainType) : this.primaryWallet;
|
|
1398
|
+
if (!adapter) {
|
|
1399
|
+
throw new WalletNotConnectedError();
|
|
1400
|
+
}
|
|
1401
|
+
if (!adapter.readContract) {
|
|
1402
|
+
throw new Error(`readContract not supported by ${adapter.type}`);
|
|
1403
|
+
}
|
|
1404
|
+
return adapter.readContract({ address, abi, functionName, args });
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* 写入合约
|
|
1408
|
+
*/
|
|
1409
|
+
async writeContract(address, abi, functionName, args, options, chainType) {
|
|
1410
|
+
const adapter = chainType ? this.connectedWallets.get(chainType) : this.primaryWallet;
|
|
1411
|
+
if (!adapter) {
|
|
1412
|
+
throw new WalletNotConnectedError();
|
|
1413
|
+
}
|
|
1414
|
+
if (!adapter.writeContract) {
|
|
1415
|
+
throw new Error(`writeContract not supported by ${adapter.type}`);
|
|
1416
|
+
}
|
|
1417
|
+
return adapter.writeContract({
|
|
1418
|
+
address,
|
|
1419
|
+
abi,
|
|
1420
|
+
functionName,
|
|
1421
|
+
args,
|
|
1422
|
+
...options
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* 估算 Gas
|
|
1427
|
+
*/
|
|
1428
|
+
async estimateGas(address, abi, functionName, args, chainType) {
|
|
1429
|
+
const adapter = chainType ? this.connectedWallets.get(chainType) : this.primaryWallet;
|
|
1430
|
+
if (!adapter) {
|
|
1431
|
+
throw new WalletNotConnectedError();
|
|
1432
|
+
}
|
|
1433
|
+
if (!adapter.estimateGas) {
|
|
1434
|
+
throw new Error(`estimateGas not supported by ${adapter.type}`);
|
|
1435
|
+
}
|
|
1436
|
+
return adapter.estimateGas({ address, abi, functionName, args });
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* 等待交易确认
|
|
1440
|
+
*/
|
|
1441
|
+
async waitForTransaction(txHash, confirmations, chainType) {
|
|
1442
|
+
const adapter = chainType ? this.connectedWallets.get(chainType) : this.primaryWallet;
|
|
1443
|
+
if (!adapter) {
|
|
1444
|
+
throw new WalletNotConnectedError();
|
|
1445
|
+
}
|
|
1446
|
+
if (!adapter.waitForTransaction) {
|
|
1447
|
+
throw new Error(`waitForTransaction not supported by ${adapter.type}`);
|
|
1448
|
+
}
|
|
1449
|
+
return adapter.waitForTransaction(txHash, confirmations);
|
|
1450
|
+
}
|
|
1451
|
+
// ===== Provider 访问 =====
|
|
1452
|
+
/**
|
|
1453
|
+
* 获取主钱包 Provider
|
|
1454
|
+
*/
|
|
1455
|
+
getProvider() {
|
|
1456
|
+
if (!this.primaryWallet) {
|
|
1457
|
+
throw new WalletNotConnectedError();
|
|
1458
|
+
}
|
|
1459
|
+
return this.primaryWallet.getProvider();
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* 获取指定链类型的 Provider
|
|
1463
|
+
*/
|
|
1464
|
+
getProviderByChainType(chainType) {
|
|
1465
|
+
const adapter = this.connectedWallets.get(chainType);
|
|
1466
|
+
if (!adapter) {
|
|
1467
|
+
throw new WalletNotConnectedError(`Wallet for chain type ${chainType}`);
|
|
1468
|
+
}
|
|
1469
|
+
return adapter.getProvider();
|
|
1470
|
+
}
|
|
1471
|
+
// ===== 私有方法 =====
|
|
1472
|
+
/**
|
|
1473
|
+
* 设置主钱包
|
|
1474
|
+
*/
|
|
1475
|
+
setPrimaryWallet(adapter) {
|
|
1476
|
+
this.primaryWallet = adapter;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* 判断钱包是否支持链切换
|
|
1480
|
+
*/
|
|
1481
|
+
canSwitchChain(adapter) {
|
|
1482
|
+
return !!adapter.switchChain;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* 设置适配器事件监听
|
|
1486
|
+
*/
|
|
1487
|
+
setupAdapterListeners(adapter, isPrimary) {
|
|
1488
|
+
adapter.on("accountChanged", (account) => {
|
|
1489
|
+
if (isPrimary) {
|
|
1490
|
+
this.emit("accountChanged", account);
|
|
1491
|
+
}
|
|
1492
|
+
this.emit("walletAccountChanged", adapter.chainType, account, isPrimary);
|
|
1493
|
+
if (this.config.enableStorage) {
|
|
1494
|
+
this.saveToStorage();
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
adapter.on("chainChanged", (chainId) => {
|
|
1498
|
+
if (isPrimary && adapter.currentAccount) {
|
|
1499
|
+
this.emit("chainChanged", chainId, adapter.currentAccount);
|
|
1500
|
+
}
|
|
1501
|
+
if (adapter.currentAccount) {
|
|
1502
|
+
this.emit("walletChainChanged", adapter.chainType, chainId, adapter.currentAccount, isPrimary);
|
|
1503
|
+
}
|
|
1504
|
+
if (this.config.enableStorage) {
|
|
1505
|
+
this.saveToStorage();
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
adapter.on("disconnected", () => {
|
|
1509
|
+
if (isPrimary) {
|
|
1510
|
+
this.emit("disconnected");
|
|
1511
|
+
}
|
|
1512
|
+
this.emit("walletDisconnected", adapter.chainType, isPrimary);
|
|
1513
|
+
this.connectedWallets.delete(adapter.chainType);
|
|
1514
|
+
if (adapter === this.primaryWallet) {
|
|
1515
|
+
this.primaryWallet = null;
|
|
1516
|
+
}
|
|
1517
|
+
if (this.config.enableStorage) {
|
|
1518
|
+
this.saveToStorage();
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
adapter.on("error", (error) => {
|
|
1522
|
+
this.emit("error", error);
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* 移除适配器事件监听
|
|
1527
|
+
*/
|
|
1528
|
+
removeAdapterListeners(adapter) {
|
|
1529
|
+
adapter.removeAllListeners();
|
|
1530
|
+
}
|
|
1531
|
+
// ===== 存储 =====
|
|
1532
|
+
/**
|
|
1533
|
+
* 保存到存储
|
|
1534
|
+
*/
|
|
1535
|
+
saveToStorage() {
|
|
1536
|
+
if (typeof window === "undefined" || !this.config.enableStorage) {
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const data = {
|
|
1540
|
+
current: this.primaryWallet?.currentAccount?.universalAddress || null,
|
|
1541
|
+
history: this.getHistoryRecords()
|
|
1542
|
+
};
|
|
1543
|
+
try {
|
|
1544
|
+
localStorage.setItem(
|
|
1545
|
+
`${this.config.storagePrefix}data`,
|
|
1546
|
+
JSON.stringify(data)
|
|
1547
|
+
);
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
console.error("Failed to save wallet data to storage:", error);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* 从存储恢复
|
|
1554
|
+
*/
|
|
1555
|
+
restoreFromStorage() {
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* 清除存储
|
|
1559
|
+
*/
|
|
1560
|
+
clearStorage() {
|
|
1561
|
+
if (typeof window === "undefined") {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
try {
|
|
1565
|
+
localStorage.removeItem(`${this.config.storagePrefix}data`);
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
console.error("Failed to clear wallet data from storage:", error);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* 获取历史记录
|
|
1572
|
+
*/
|
|
1573
|
+
getHistoryRecords() {
|
|
1574
|
+
const records = [];
|
|
1575
|
+
for (const adapter of this.connectedWallets.values()) {
|
|
1576
|
+
if (adapter.currentAccount) {
|
|
1577
|
+
records.push({
|
|
1578
|
+
universalAddress: adapter.currentAccount.universalAddress,
|
|
1579
|
+
nativeAddress: adapter.currentAccount.nativeAddress,
|
|
1580
|
+
chainId: adapter.currentAccount.chainId,
|
|
1581
|
+
chainType: adapter.chainType,
|
|
1582
|
+
walletType: adapter.type,
|
|
1583
|
+
lastConnected: Date.now(),
|
|
1584
|
+
name: adapter.currentAccount.name
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return records;
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
// src/react/WalletContext.tsx
|
|
1593
|
+
var WalletContext = React.createContext(null);
|
|
1594
|
+
function WalletProvider({ children, walletManager: externalWalletManager }) {
|
|
1595
|
+
const [walletManager] = React.useState(() => externalWalletManager || new WalletManager());
|
|
1596
|
+
const [account, setAccount] = React.useState(null);
|
|
1597
|
+
const [connectedWallets, setConnectedWallets] = React.useState([]);
|
|
1598
|
+
const updateConnectedWallets = React.useCallback(() => {
|
|
1599
|
+
setConnectedWallets(walletManager.getConnectedWallets());
|
|
1600
|
+
}, [walletManager]);
|
|
1601
|
+
const connect = React.useCallback(async (type, chainId) => {
|
|
1602
|
+
const account2 = await walletManager.connect(type, chainId);
|
|
1603
|
+
setAccount(account2);
|
|
1604
|
+
updateConnectedWallets();
|
|
1605
|
+
return account2;
|
|
1606
|
+
}, [walletManager, updateConnectedWallets]);
|
|
1607
|
+
const connectAdditional = React.useCallback(async (type, chainId) => {
|
|
1608
|
+
const account2 = await walletManager.connectAdditional(type, chainId);
|
|
1609
|
+
updateConnectedWallets();
|
|
1610
|
+
return account2;
|
|
1611
|
+
}, [walletManager, updateConnectedWallets]);
|
|
1612
|
+
const disconnect = React.useCallback(async () => {
|
|
1613
|
+
await walletManager.disconnect();
|
|
1614
|
+
setAccount(null);
|
|
1615
|
+
updateConnectedWallets();
|
|
1616
|
+
}, [walletManager, updateConnectedWallets]);
|
|
1617
|
+
const switchPrimaryWallet = React.useCallback(async (chainType) => {
|
|
1618
|
+
const account2 = await walletManager.switchPrimaryWallet(chainType);
|
|
1619
|
+
setAccount(account2);
|
|
1620
|
+
updateConnectedWallets();
|
|
1621
|
+
return account2;
|
|
1622
|
+
}, [walletManager, updateConnectedWallets]);
|
|
1623
|
+
const signMessage = React.useCallback(async (message) => {
|
|
1624
|
+
return walletManager.signMessage(message);
|
|
1625
|
+
}, [walletManager]);
|
|
1626
|
+
React.useEffect(() => {
|
|
1627
|
+
const handleAccountChanged = (newAccount) => {
|
|
1628
|
+
setAccount(newAccount);
|
|
1629
|
+
updateConnectedWallets();
|
|
1630
|
+
};
|
|
1631
|
+
const handleChainChanged = (_chainId, newAccount) => {
|
|
1632
|
+
setAccount(newAccount);
|
|
1633
|
+
updateConnectedWallets();
|
|
1634
|
+
};
|
|
1635
|
+
const handleDisconnected = () => {
|
|
1636
|
+
setAccount(null);
|
|
1637
|
+
updateConnectedWallets();
|
|
1638
|
+
};
|
|
1639
|
+
const handlePrimaryWalletSwitched = (newPrimary) => {
|
|
1640
|
+
setAccount(newPrimary);
|
|
1641
|
+
updateConnectedWallets();
|
|
1642
|
+
};
|
|
1643
|
+
walletManager.on("accountChanged", handleAccountChanged);
|
|
1644
|
+
walletManager.on("chainChanged", handleChainChanged);
|
|
1645
|
+
walletManager.on("disconnected", handleDisconnected);
|
|
1646
|
+
walletManager.on("primaryWalletSwitched", handlePrimaryWalletSwitched);
|
|
1647
|
+
setAccount(walletManager.getPrimaryAccount());
|
|
1648
|
+
updateConnectedWallets();
|
|
1649
|
+
return () => {
|
|
1650
|
+
walletManager.off("accountChanged", handleAccountChanged);
|
|
1651
|
+
walletManager.off("chainChanged", handleChainChanged);
|
|
1652
|
+
walletManager.off("disconnected", handleDisconnected);
|
|
1653
|
+
walletManager.off("primaryWalletSwitched", handlePrimaryWalletSwitched);
|
|
1654
|
+
};
|
|
1655
|
+
}, [walletManager, updateConnectedWallets]);
|
|
1656
|
+
const value = {
|
|
1657
|
+
walletManager,
|
|
1658
|
+
account,
|
|
1659
|
+
isConnected: account !== null,
|
|
1660
|
+
connectedWallets,
|
|
1661
|
+
connect,
|
|
1662
|
+
connectAdditional,
|
|
1663
|
+
disconnect,
|
|
1664
|
+
switchPrimaryWallet,
|
|
1665
|
+
signMessage
|
|
1666
|
+
};
|
|
1667
|
+
return /* @__PURE__ */ React__default.default.createElement(WalletContext.Provider, { value }, children);
|
|
1668
|
+
}
|
|
1669
|
+
function useWallet() {
|
|
1670
|
+
const context = React.useContext(WalletContext);
|
|
1671
|
+
if (!context) {
|
|
1672
|
+
throw new Error("useWallet must be used within a WalletProvider");
|
|
1673
|
+
}
|
|
1674
|
+
return context;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/react/hooks/useAccount.ts
|
|
1678
|
+
function useAccount() {
|
|
1679
|
+
const { account, isConnected } = useWallet();
|
|
1680
|
+
return {
|
|
1681
|
+
account,
|
|
1682
|
+
isConnected,
|
|
1683
|
+
address: account?.nativeAddress || null,
|
|
1684
|
+
chainId: account?.chainId || null,
|
|
1685
|
+
universalAddress: account?.universalAddress || null
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
function useConnect() {
|
|
1689
|
+
const { connect: contextConnect, connectAdditional: contextConnectAdditional } = useWallet();
|
|
1690
|
+
const [isConnecting, setIsConnecting] = React.useState(false);
|
|
1691
|
+
const [error, setError] = React.useState(null);
|
|
1692
|
+
const connect = async (type, chainId) => {
|
|
1693
|
+
setIsConnecting(true);
|
|
1694
|
+
setError(null);
|
|
1695
|
+
try {
|
|
1696
|
+
const account = await contextConnect(type, chainId);
|
|
1697
|
+
return account;
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
1700
|
+
setError(error2);
|
|
1701
|
+
throw error2;
|
|
1702
|
+
} finally {
|
|
1703
|
+
setIsConnecting(false);
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
const connectAdditional = async (type, chainId) => {
|
|
1707
|
+
setIsConnecting(true);
|
|
1708
|
+
setError(null);
|
|
1709
|
+
try {
|
|
1710
|
+
const account = await contextConnectAdditional(type, chainId);
|
|
1711
|
+
return account;
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
1714
|
+
setError(error2);
|
|
1715
|
+
throw error2;
|
|
1716
|
+
} finally {
|
|
1717
|
+
setIsConnecting(false);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
return {
|
|
1721
|
+
connect,
|
|
1722
|
+
connectAdditional,
|
|
1723
|
+
isConnecting,
|
|
1724
|
+
error
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function useDisconnect() {
|
|
1728
|
+
const { disconnect: contextDisconnect } = useWallet();
|
|
1729
|
+
const [isDisconnecting, setIsDisconnecting] = React.useState(false);
|
|
1730
|
+
const [error, setError] = React.useState(null);
|
|
1731
|
+
const disconnect = async () => {
|
|
1732
|
+
setIsDisconnecting(true);
|
|
1733
|
+
setError(null);
|
|
1734
|
+
try {
|
|
1735
|
+
await contextDisconnect();
|
|
1736
|
+
} catch (err) {
|
|
1737
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
1738
|
+
setError(error2);
|
|
1739
|
+
throw error2;
|
|
1740
|
+
} finally {
|
|
1741
|
+
setIsDisconnecting(false);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
return {
|
|
1745
|
+
disconnect,
|
|
1746
|
+
isDisconnecting,
|
|
1747
|
+
error
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
function useSignMessage() {
|
|
1751
|
+
const { signMessage: contextSignMessage } = useWallet();
|
|
1752
|
+
const [isSigning, setIsSigning] = React.useState(false);
|
|
1753
|
+
const [error, setError] = React.useState(null);
|
|
1754
|
+
const signMessage = async (message) => {
|
|
1755
|
+
setIsSigning(true);
|
|
1756
|
+
setError(null);
|
|
1757
|
+
try {
|
|
1758
|
+
const signature = await contextSignMessage(message);
|
|
1759
|
+
return signature;
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
1762
|
+
setError(error2);
|
|
1763
|
+
throw error2;
|
|
1764
|
+
} finally {
|
|
1765
|
+
setIsSigning(false);
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
return {
|
|
1769
|
+
signMessage,
|
|
1770
|
+
isSigning,
|
|
1771
|
+
error
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
exports.WalletProvider = WalletProvider;
|
|
1776
|
+
exports.useAccount = useAccount;
|
|
1777
|
+
exports.useConnect = useConnect;
|
|
1778
|
+
exports.useDisconnect = useDisconnect;
|
|
1779
|
+
exports.useSignMessage = useSignMessage;
|
|
1780
|
+
exports.useWallet = useWallet;
|
|
1781
|
+
//# sourceMappingURL=index.js.map
|
|
1782
|
+
//# sourceMappingURL=index.js.map
|