@solana/connector 0.0.0 → 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/README.md +1460 -0
- package/dist/chunk-52WUWW5R.mjs +2533 -0
- package/dist/chunk-52WUWW5R.mjs.map +1 -0
- package/dist/chunk-5NSUFMCB.js +393 -0
- package/dist/chunk-5NSUFMCB.js.map +1 -0
- package/dist/chunk-5ZUVZZWU.mjs +180 -0
- package/dist/chunk-5ZUVZZWU.mjs.map +1 -0
- package/dist/chunk-7TADXRFD.mjs +298 -0
- package/dist/chunk-7TADXRFD.mjs.map +1 -0
- package/dist/chunk-ACFSCMUI.mjs +359 -0
- package/dist/chunk-ACFSCMUI.mjs.map +1 -0
- package/dist/chunk-SGAIPK7Q.js +314 -0
- package/dist/chunk-SGAIPK7Q.js.map +1 -0
- package/dist/chunk-SMUUAKC3.js +186 -0
- package/dist/chunk-SMUUAKC3.js.map +1 -0
- package/dist/chunk-ZLPQUOFK.js +2594 -0
- package/dist/chunk-ZLPQUOFK.js.map +1 -0
- package/dist/compat.d.mts +106 -0
- package/dist/compat.d.ts +106 -0
- package/dist/compat.js +98 -0
- package/dist/compat.js.map +1 -0
- package/dist/compat.mjs +94 -0
- package/dist/compat.mjs.map +1 -0
- package/dist/headless.d.mts +400 -0
- package/dist/headless.d.ts +400 -0
- package/dist/headless.js +325 -0
- package/dist/headless.js.map +1 -0
- package/dist/headless.mjs +4 -0
- package/dist/headless.mjs.map +1 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +645 -0
- package/dist/react.d.ts +645 -0
- package/dist/react.js +65 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +4 -0
- package/dist/react.mjs.map +1 -0
- package/dist/transaction-signer-BtJPGXIg.d.mts +373 -0
- package/dist/transaction-signer-BtJPGXIg.d.ts +373 -0
- package/dist/wallet-standard-shim-Af7ejSld.d.mts +1090 -0
- package/dist/wallet-standard-shim-BGlvGRbB.d.ts +1090 -0
- package/package.json +87 -10
- package/index.js +0 -1
|
@@ -0,0 +1,2594 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkSMUUAKC3_js = require('./chunk-SMUUAKC3.js');
|
|
4
|
+
var gill = require('gill');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var webcryptoEd25519Polyfill = require('@solana/webcrypto-ed25519-polyfill');
|
|
8
|
+
|
|
9
|
+
// src/lib/adapters/wallet-standard-shim.ts
|
|
10
|
+
var registry = null;
|
|
11
|
+
function getWalletsRegistry() {
|
|
12
|
+
if (typeof window > "u")
|
|
13
|
+
return {
|
|
14
|
+
get: () => [],
|
|
15
|
+
on: () => () => {
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (!registry) {
|
|
19
|
+
let nav = window.navigator;
|
|
20
|
+
nav.wallets && typeof nav.wallets.get == "function" ? registry = nav.wallets : import('@wallet-standard/app').then((mod) => {
|
|
21
|
+
let walletStandardRegistry = mod.getWallets?.();
|
|
22
|
+
walletStandardRegistry && (registry = walletStandardRegistry);
|
|
23
|
+
}).catch(() => {
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
get: () => {
|
|
28
|
+
try {
|
|
29
|
+
let activeRegistry = window.navigator.wallets || registry;
|
|
30
|
+
if (activeRegistry && typeof activeRegistry.get == "function") {
|
|
31
|
+
let wallets = activeRegistry.get();
|
|
32
|
+
return Array.isArray(wallets) ? wallets : [];
|
|
33
|
+
}
|
|
34
|
+
return [];
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
on: (event, callback) => {
|
|
40
|
+
try {
|
|
41
|
+
let activeRegistry = window.navigator.wallets || registry;
|
|
42
|
+
return activeRegistry && typeof activeRegistry.on == "function" ? activeRegistry.on(event, callback) : () => {
|
|
43
|
+
};
|
|
44
|
+
} catch {
|
|
45
|
+
return () => {
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/lib/errors/index.ts
|
|
53
|
+
var ConnectorError = class extends Error {
|
|
54
|
+
constructor(message, context, originalError) {
|
|
55
|
+
super(message);
|
|
56
|
+
/**
|
|
57
|
+
* Additional context about the error
|
|
58
|
+
*/
|
|
59
|
+
chunkSMUUAKC3_js.__publicField(this, "context");
|
|
60
|
+
/**
|
|
61
|
+
* The underlying error that caused this error
|
|
62
|
+
*/
|
|
63
|
+
chunkSMUUAKC3_js.__publicField(this, "originalError");
|
|
64
|
+
/**
|
|
65
|
+
* Timestamp when error occurred
|
|
66
|
+
*/
|
|
67
|
+
chunkSMUUAKC3_js.__publicField(this, "timestamp");
|
|
68
|
+
this.name = this.constructor.name, this.context = context, this.originalError = originalError, this.timestamp = (/* @__PURE__ */ new Date()).toISOString(), Error.captureStackTrace && Error.captureStackTrace(this, this.constructor);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get a JSON representation of the error
|
|
72
|
+
*/
|
|
73
|
+
toJSON() {
|
|
74
|
+
return {
|
|
75
|
+
name: this.name,
|
|
76
|
+
code: this.code,
|
|
77
|
+
message: this.message,
|
|
78
|
+
recoverable: this.recoverable,
|
|
79
|
+
context: this.context,
|
|
80
|
+
timestamp: this.timestamp,
|
|
81
|
+
originalError: this.originalError?.message
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}, ConnectionError = class extends ConnectorError {
|
|
85
|
+
constructor(code, message, context, originalError) {
|
|
86
|
+
super(message, context, originalError);
|
|
87
|
+
chunkSMUUAKC3_js.__publicField(this, "code");
|
|
88
|
+
chunkSMUUAKC3_js.__publicField(this, "recoverable", true);
|
|
89
|
+
this.code = code;
|
|
90
|
+
}
|
|
91
|
+
}, ValidationError = class extends ConnectorError {
|
|
92
|
+
constructor(code, message, context, originalError) {
|
|
93
|
+
super(message, context, originalError);
|
|
94
|
+
chunkSMUUAKC3_js.__publicField(this, "code");
|
|
95
|
+
chunkSMUUAKC3_js.__publicField(this, "recoverable", false);
|
|
96
|
+
this.code = code;
|
|
97
|
+
}
|
|
98
|
+
}, ConfigurationError = class extends ConnectorError {
|
|
99
|
+
constructor(code, message, context, originalError) {
|
|
100
|
+
super(message, context, originalError);
|
|
101
|
+
chunkSMUUAKC3_js.__publicField(this, "code");
|
|
102
|
+
chunkSMUUAKC3_js.__publicField(this, "recoverable", false);
|
|
103
|
+
this.code = code;
|
|
104
|
+
}
|
|
105
|
+
}, NetworkError = class extends ConnectorError {
|
|
106
|
+
constructor(code, message, context, originalError) {
|
|
107
|
+
super(message, context, originalError);
|
|
108
|
+
chunkSMUUAKC3_js.__publicField(this, "code");
|
|
109
|
+
chunkSMUUAKC3_js.__publicField(this, "recoverable", true);
|
|
110
|
+
this.code = code;
|
|
111
|
+
}
|
|
112
|
+
}, TransactionError = class extends ConnectorError {
|
|
113
|
+
constructor(code, message, context, originalError) {
|
|
114
|
+
super(message, context, originalError);
|
|
115
|
+
chunkSMUUAKC3_js.__publicField(this, "code");
|
|
116
|
+
chunkSMUUAKC3_js.__publicField(this, "recoverable");
|
|
117
|
+
this.code = code, this.recoverable = ["USER_REJECTED", "SEND_FAILED", "SIMULATION_FAILED"].includes(code);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
function isConnectorError(error) {
|
|
121
|
+
return error instanceof ConnectorError;
|
|
122
|
+
}
|
|
123
|
+
function isConnectionError(error) {
|
|
124
|
+
return error instanceof ConnectionError;
|
|
125
|
+
}
|
|
126
|
+
function isValidationError(error) {
|
|
127
|
+
return error instanceof ValidationError;
|
|
128
|
+
}
|
|
129
|
+
function isConfigurationError(error) {
|
|
130
|
+
return error instanceof ConfigurationError;
|
|
131
|
+
}
|
|
132
|
+
function isNetworkError(error) {
|
|
133
|
+
return error instanceof NetworkError;
|
|
134
|
+
}
|
|
135
|
+
function isTransactionError(error) {
|
|
136
|
+
return error instanceof TransactionError;
|
|
137
|
+
}
|
|
138
|
+
var Errors = {
|
|
139
|
+
// Connection errors
|
|
140
|
+
walletNotConnected: (context) => new ConnectionError("WALLET_NOT_CONNECTED", "No wallet connected", context),
|
|
141
|
+
walletNotFound: (walletName) => new ConnectionError("WALLET_NOT_FOUND", `Wallet not found${walletName ? `: ${walletName}` : ""}`, {
|
|
142
|
+
walletName
|
|
143
|
+
}),
|
|
144
|
+
connectionFailed: (originalError) => new ConnectionError("CONNECTION_FAILED", "Failed to connect to wallet", void 0, originalError),
|
|
145
|
+
accountNotAvailable: (address) => new ConnectionError("ACCOUNT_NOT_AVAILABLE", "Requested account not available", { address }),
|
|
146
|
+
// Validation errors
|
|
147
|
+
invalidTransaction: (reason, context) => new ValidationError("INVALID_TRANSACTION", `Invalid transaction: ${reason}`, context),
|
|
148
|
+
invalidFormat: (expectedFormat, actualFormat) => new ValidationError("INVALID_FORMAT", `Invalid format: expected ${expectedFormat}`, {
|
|
149
|
+
expectedFormat,
|
|
150
|
+
actualFormat
|
|
151
|
+
}),
|
|
152
|
+
unsupportedFormat: (format) => new ValidationError("UNSUPPORTED_FORMAT", `Unsupported format: ${format}`, { format }),
|
|
153
|
+
// Configuration errors
|
|
154
|
+
missingProvider: (hookName) => new ConfigurationError(
|
|
155
|
+
"MISSING_PROVIDER",
|
|
156
|
+
`${hookName} must be used within ConnectorProvider. Wrap your app with <ConnectorProvider> or <UnifiedProvider>.`,
|
|
157
|
+
{ hookName }
|
|
158
|
+
),
|
|
159
|
+
clusterNotFound: (clusterId, availableClusters) => new ConfigurationError(
|
|
160
|
+
"CLUSTER_NOT_FOUND",
|
|
161
|
+
`Cluster ${clusterId} not found. Available clusters: ${availableClusters.join(", ")}`,
|
|
162
|
+
{ clusterId, availableClusters }
|
|
163
|
+
),
|
|
164
|
+
// Network errors
|
|
165
|
+
rpcError: (message, originalError) => new NetworkError("RPC_ERROR", message, void 0, originalError),
|
|
166
|
+
networkTimeout: () => new NetworkError("NETWORK_TIMEOUT", "Network request timed out"),
|
|
167
|
+
// Transaction errors
|
|
168
|
+
signingFailed: (originalError) => new TransactionError("SIGNING_FAILED", "Failed to sign transaction", void 0, originalError),
|
|
169
|
+
featureNotSupported: (feature) => new TransactionError("FEATURE_NOT_SUPPORTED", `Wallet does not support ${feature}`, { feature }),
|
|
170
|
+
userRejected: (operation) => new TransactionError("USER_REJECTED", `User rejected ${operation}`, { operation })
|
|
171
|
+
};
|
|
172
|
+
function toConnectorError(error, defaultMessage = "An unexpected error occurred") {
|
|
173
|
+
if (isConnectorError(error))
|
|
174
|
+
return error;
|
|
175
|
+
if (error instanceof Error) {
|
|
176
|
+
let message = error.message.toLowerCase();
|
|
177
|
+
return message.includes("user rejected") || message.includes("user denied") ? Errors.userRejected("transaction") : message.includes("wallet not found") || message.includes("not installed") ? Errors.walletNotFound() : message.includes("not connected") ? Errors.walletNotConnected() : message.includes("network") || message.includes("fetch") ? Errors.rpcError(error.message, error) : message.includes("invalid") ? new ValidationError("VALIDATION_FAILED", error.message, void 0, error) : new TransactionError("SIGNING_FAILED", error.message, void 0, error);
|
|
178
|
+
}
|
|
179
|
+
return new TransactionError("SIGNING_FAILED", defaultMessage, { originalError: String(error) });
|
|
180
|
+
}
|
|
181
|
+
function getUserFriendlyMessage(error) {
|
|
182
|
+
return isConnectorError(error) ? {
|
|
183
|
+
WALLET_NOT_CONNECTED: "Please connect your wallet to continue.",
|
|
184
|
+
WALLET_NOT_FOUND: "Wallet not found. Please install a supported wallet.",
|
|
185
|
+
CONNECTION_FAILED: "Failed to connect to wallet. Please try again.",
|
|
186
|
+
USER_REJECTED: "Transaction was cancelled.",
|
|
187
|
+
FEATURE_NOT_SUPPORTED: "This wallet does not support this feature.",
|
|
188
|
+
SIGNING_FAILED: "Failed to sign transaction. Please try again.",
|
|
189
|
+
SEND_FAILED: "Failed to send transaction. Please try again.",
|
|
190
|
+
INVALID_CLUSTER: "Invalid network configuration.",
|
|
191
|
+
CLUSTER_NOT_FOUND: "Network not found.",
|
|
192
|
+
MISSING_PROVIDER: "Application not properly configured.",
|
|
193
|
+
RPC_ERROR: "Network error. Please check your connection.",
|
|
194
|
+
NETWORK_TIMEOUT: "Request timed out. Please try again."
|
|
195
|
+
}[error.code] || error.message || "An error occurred." : "An unexpected error occurred. Please try again.";
|
|
196
|
+
}
|
|
197
|
+
var PUBLIC_RPC_ENDPOINTS = {
|
|
198
|
+
mainnet: "https://api.mainnet-beta.solana.com",
|
|
199
|
+
devnet: "https://api.devnet.solana.com",
|
|
200
|
+
testnet: "https://api.testnet.solana.com",
|
|
201
|
+
localnet: "http://localhost:8899"
|
|
202
|
+
};
|
|
203
|
+
function normalizeNetwork(network) {
|
|
204
|
+
switch (network.toLowerCase().replace("-beta", "")) {
|
|
205
|
+
case "mainnet":
|
|
206
|
+
return "mainnet";
|
|
207
|
+
case "devnet":
|
|
208
|
+
return "devnet";
|
|
209
|
+
case "testnet":
|
|
210
|
+
return "testnet";
|
|
211
|
+
case "localnet":
|
|
212
|
+
return "localnet";
|
|
213
|
+
default:
|
|
214
|
+
return "mainnet";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function toClusterId(network) {
|
|
218
|
+
return `solana:${normalizeNetwork(network)}`;
|
|
219
|
+
}
|
|
220
|
+
function getDefaultRpcUrl(network) {
|
|
221
|
+
let normalized = normalizeNetwork(network);
|
|
222
|
+
try {
|
|
223
|
+
return gill.getPublicSolanaRpcUrl(normalized);
|
|
224
|
+
} catch {
|
|
225
|
+
return PUBLIC_RPC_ENDPOINTS[normalized] ?? PUBLIC_RPC_ENDPOINTS.localnet;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function isMainnet(network) {
|
|
229
|
+
return normalizeNetwork(network) === "mainnet";
|
|
230
|
+
}
|
|
231
|
+
function isDevnet(network) {
|
|
232
|
+
return normalizeNetwork(network) === "devnet";
|
|
233
|
+
}
|
|
234
|
+
function isTestnet(network) {
|
|
235
|
+
return normalizeNetwork(network) === "testnet";
|
|
236
|
+
}
|
|
237
|
+
function isLocalnet(network) {
|
|
238
|
+
return normalizeNetwork(network) === "localnet";
|
|
239
|
+
}
|
|
240
|
+
function getNetworkDisplayName(network) {
|
|
241
|
+
let normalized = normalizeNetwork(network);
|
|
242
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
243
|
+
}
|
|
244
|
+
function getClusterRpcUrl(cluster) {
|
|
245
|
+
if (!cluster)
|
|
246
|
+
throw new Error("Invalid cluster configuration: unable to determine RPC URL for cluster unknown");
|
|
247
|
+
let url;
|
|
248
|
+
if (typeof cluster == "string" ? url = cluster : typeof cluster == "object" && cluster !== null ? "url" in cluster && typeof cluster.url == "string" ? url = cluster.url : "rpcUrl" in cluster && typeof cluster.rpcUrl == "string" ? url = cluster.rpcUrl : url = "" : url = "", url?.startsWith("http://") || url?.startsWith("https://"))
|
|
249
|
+
return url;
|
|
250
|
+
let presets = {
|
|
251
|
+
...PUBLIC_RPC_ENDPOINTS,
|
|
252
|
+
"mainnet-beta": PUBLIC_RPC_ENDPOINTS.mainnet
|
|
253
|
+
};
|
|
254
|
+
if (url && presets[url])
|
|
255
|
+
return presets[url];
|
|
256
|
+
if (!url || url === "[object Object]")
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Invalid cluster configuration: unable to determine RPC URL for cluster ${cluster?.id ?? "unknown"}`
|
|
259
|
+
);
|
|
260
|
+
return url;
|
|
261
|
+
}
|
|
262
|
+
function getClusterExplorerUrl(cluster, path) {
|
|
263
|
+
if (!cluster || !cluster.id)
|
|
264
|
+
return path ? `https://explorer.solana.com/${path}?cluster=devnet` : "https://explorer.solana.com?cluster=devnet";
|
|
265
|
+
let parts = cluster.id.split(":"), clusterSegment = parts.length >= 2 && parts[1] ? parts[1] : "devnet", isMainnet2 = cluster.id === "solana:mainnet" || cluster.id === "solana:mainnet-beta" || clusterSegment === "mainnet" || clusterSegment === "mainnet-beta", base = isMainnet2 ? "https://explorer.solana.com" : `https://explorer.solana.com?cluster=${clusterSegment}`;
|
|
266
|
+
return path ? isMainnet2 ? `https://explorer.solana.com/${path}` : `https://explorer.solana.com/${path}?cluster=${clusterSegment}` : base;
|
|
267
|
+
}
|
|
268
|
+
function getTransactionUrl(signature, cluster) {
|
|
269
|
+
let clusterType = getClusterType(cluster), explorerCluster = clusterType === "custom" || clusterType === "localnet" ? "devnet" : clusterType;
|
|
270
|
+
return gill.getExplorerLink({
|
|
271
|
+
transaction: signature,
|
|
272
|
+
cluster: explorerCluster === "mainnet" ? "mainnet" : explorerCluster
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function getAddressUrl(address, cluster) {
|
|
276
|
+
let clusterType = getClusterType(cluster), explorerCluster = clusterType === "custom" || clusterType === "localnet" ? "devnet" : clusterType;
|
|
277
|
+
return gill.getExplorerLink({
|
|
278
|
+
address,
|
|
279
|
+
cluster: explorerCluster === "mainnet" ? "mainnet" : explorerCluster
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function getTokenUrl(tokenAddress, cluster) {
|
|
283
|
+
return getClusterExplorerUrl(cluster, `token/${tokenAddress}`);
|
|
284
|
+
}
|
|
285
|
+
function getBlockUrl(slot, cluster) {
|
|
286
|
+
return getClusterExplorerUrl(cluster, `block/${slot}`);
|
|
287
|
+
}
|
|
288
|
+
function isMainnetCluster(cluster) {
|
|
289
|
+
return cluster.id === "solana:mainnet" || cluster.id === "solana:mainnet-beta";
|
|
290
|
+
}
|
|
291
|
+
function isDevnetCluster(cluster) {
|
|
292
|
+
return cluster.id === "solana:devnet";
|
|
293
|
+
}
|
|
294
|
+
function isTestnetCluster(cluster) {
|
|
295
|
+
return cluster.id === "solana:testnet";
|
|
296
|
+
}
|
|
297
|
+
function isLocalCluster(cluster) {
|
|
298
|
+
let url = "";
|
|
299
|
+
return "url" in cluster && typeof cluster.url == "string" ? url = cluster.url : "rpcUrl" in cluster && typeof cluster.rpcUrl == "string" && (url = cluster.rpcUrl), cluster.id === "solana:localnet" || url.includes("localhost") || url.includes("127.0.0.1");
|
|
300
|
+
}
|
|
301
|
+
function getClusterName(cluster) {
|
|
302
|
+
if (cluster.label) return cluster.label;
|
|
303
|
+
if ("name" in cluster && typeof cluster.name == "string")
|
|
304
|
+
return cluster.name;
|
|
305
|
+
let parts = cluster.id.split(":");
|
|
306
|
+
return parts.length >= 2 && parts[1] ? parts.slice(1).join(":") : "Unknown";
|
|
307
|
+
}
|
|
308
|
+
function getClusterType(cluster) {
|
|
309
|
+
return isMainnetCluster(cluster) ? "mainnet" : isDevnetCluster(cluster) ? "devnet" : isTestnetCluster(cluster) ? "testnet" : isLocalCluster(cluster) ? "localnet" : "custom";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/lib/constants.ts
|
|
313
|
+
var POLL_INTERVALS_MS = [1e3, 2e3, 3e3, 5e3, 5e3], DEFAULT_MAX_RETRIES = 3, DEFAULT_MAX_TRACKED_TRANSACTIONS = 20;
|
|
314
|
+
|
|
315
|
+
// src/lib/core/state-manager.ts
|
|
316
|
+
var StateManager = class {
|
|
317
|
+
constructor(initialState) {
|
|
318
|
+
chunkSMUUAKC3_js.__publicField(this, "state");
|
|
319
|
+
chunkSMUUAKC3_js.__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
320
|
+
chunkSMUUAKC3_js.__publicField(this, "notifyTimeout");
|
|
321
|
+
this.state = initialState;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Optimized state update with structural sharing
|
|
325
|
+
* Only updates if values actually changed
|
|
326
|
+
*/
|
|
327
|
+
updateState(updates, immediate = false) {
|
|
328
|
+
let hasChanges = false, nextState = { ...this.state };
|
|
329
|
+
for (let [key, value] of Object.entries(updates)) {
|
|
330
|
+
let stateKey = key, currentValue = nextState[stateKey];
|
|
331
|
+
Array.isArray(value) && Array.isArray(currentValue) ? this.arraysEqual(value, currentValue) || (nextState[stateKey] = value, hasChanges = true) : value && typeof value == "object" && currentValue && typeof currentValue == "object" ? this.objectsEqual(value, currentValue) || (nextState[stateKey] = value, hasChanges = true) : currentValue !== value && (nextState[stateKey] = value, hasChanges = true);
|
|
332
|
+
}
|
|
333
|
+
return hasChanges && (this.state = nextState, immediate ? this.notifyImmediate() : this.notify()), hasChanges;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Fast array equality check for wallet/account arrays
|
|
337
|
+
*/
|
|
338
|
+
arraysEqual(a, b) {
|
|
339
|
+
return a.length !== b.length ? false : a[0] && typeof a[0] == "object" && "name" in a[0] && b[0] && typeof b[0] == "object" && "name" in b[0] ? a.every((item, i) => {
|
|
340
|
+
let aItem = item, bItem = b[i];
|
|
341
|
+
if (!bItem || typeof bItem != "object") return false;
|
|
342
|
+
let keysA = Object.keys(aItem), keysB = Object.keys(bItem);
|
|
343
|
+
return keysA.length !== keysB.length ? false : keysA.every((key) => aItem[key] === bItem[key]);
|
|
344
|
+
}) : a[0] && typeof a[0] == "object" && "address" in a[0] && b[0] && typeof b[0] == "object" && "address" in b[0] ? a.every((item, i) => {
|
|
345
|
+
let aItem = item, bItem = b[i];
|
|
346
|
+
return aItem.address === bItem?.address;
|
|
347
|
+
}) : a === b;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Deep equality check for objects
|
|
351
|
+
* Used to prevent unnecessary state updates when object contents haven't changed
|
|
352
|
+
*/
|
|
353
|
+
objectsEqual(a, b) {
|
|
354
|
+
if (a === b) return true;
|
|
355
|
+
if (!a || !b || typeof a != "object" || typeof b != "object") return false;
|
|
356
|
+
let keysA = Object.keys(a), keysB = Object.keys(b);
|
|
357
|
+
return keysA.length !== keysB.length ? false : keysA.every((key) => a[key] === b[key]);
|
|
358
|
+
}
|
|
359
|
+
subscribe(listener) {
|
|
360
|
+
return this.listeners.add(listener), () => this.listeners.delete(listener);
|
|
361
|
+
}
|
|
362
|
+
getSnapshot() {
|
|
363
|
+
return this.state;
|
|
364
|
+
}
|
|
365
|
+
notify() {
|
|
366
|
+
this.notifyTimeout && clearTimeout(this.notifyTimeout), this.notifyTimeout = setTimeout(() => {
|
|
367
|
+
this.listeners.forEach((l) => l(this.state)), this.notifyTimeout = void 0;
|
|
368
|
+
}, 16);
|
|
369
|
+
}
|
|
370
|
+
notifyImmediate() {
|
|
371
|
+
this.notifyTimeout && (clearTimeout(this.notifyTimeout), this.notifyTimeout = void 0), this.listeners.forEach((l) => l(this.state));
|
|
372
|
+
}
|
|
373
|
+
clear() {
|
|
374
|
+
this.listeners.clear();
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/lib/core/event-emitter.ts
|
|
379
|
+
var logger = chunkSMUUAKC3_js.createLogger("EventEmitter"), EventEmitter = class {
|
|
380
|
+
constructor(debug = false) {
|
|
381
|
+
chunkSMUUAKC3_js.__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
382
|
+
chunkSMUUAKC3_js.__publicField(this, "debug");
|
|
383
|
+
this.debug = debug;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Subscribe to connector events
|
|
387
|
+
*/
|
|
388
|
+
on(listener) {
|
|
389
|
+
return this.listeners.add(listener), () => this.listeners.delete(listener);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Remove a specific event listener
|
|
393
|
+
*/
|
|
394
|
+
off(listener) {
|
|
395
|
+
this.listeners.delete(listener);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Remove all event listeners
|
|
399
|
+
*/
|
|
400
|
+
offAll() {
|
|
401
|
+
this.listeners.clear();
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Emit an event to all listeners
|
|
405
|
+
* Automatically adds timestamp if not already present
|
|
406
|
+
*/
|
|
407
|
+
emit(event) {
|
|
408
|
+
let eventWithTimestamp = {
|
|
409
|
+
...event,
|
|
410
|
+
timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
411
|
+
};
|
|
412
|
+
this.debug && logger.debug("Event emitted", { type: eventWithTimestamp.type, event: eventWithTimestamp }), this.listeners.forEach((listener) => {
|
|
413
|
+
try {
|
|
414
|
+
listener(eventWithTimestamp);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
logger.error("Event listener error", { error });
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get the number of active listeners
|
|
422
|
+
*/
|
|
423
|
+
getListenerCount() {
|
|
424
|
+
return this.listeners.size;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Generate ISO timestamp for events
|
|
428
|
+
* Utility method for creating timestamps consistently
|
|
429
|
+
*/
|
|
430
|
+
static timestamp() {
|
|
431
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/lib/core/debug-metrics.ts
|
|
436
|
+
var DebugMetrics = class {
|
|
437
|
+
constructor() {
|
|
438
|
+
chunkSMUUAKC3_js.__publicField(this, "stateUpdates", 0);
|
|
439
|
+
chunkSMUUAKC3_js.__publicField(this, "noopUpdates", 0);
|
|
440
|
+
chunkSMUUAKC3_js.__publicField(this, "updateTimes", []);
|
|
441
|
+
chunkSMUUAKC3_js.__publicField(this, "lastUpdateTime", 0);
|
|
442
|
+
chunkSMUUAKC3_js.__publicField(this, "eventListenerCount", 0);
|
|
443
|
+
chunkSMUUAKC3_js.__publicField(this, "subscriptionCount", 0);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Record a state update attempt
|
|
447
|
+
*/
|
|
448
|
+
recordUpdate(duration, hadChanges) {
|
|
449
|
+
hadChanges ? this.stateUpdates++ : this.noopUpdates++, this.updateTimes.push(duration), this.updateTimes.length > 100 && this.updateTimes.shift(), this.lastUpdateTime = Date.now();
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Update listener counts
|
|
453
|
+
*/
|
|
454
|
+
updateListenerCounts(eventListeners, subscriptions) {
|
|
455
|
+
this.eventListenerCount = eventListeners, this.subscriptionCount = subscriptions;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get current metrics
|
|
459
|
+
*/
|
|
460
|
+
getMetrics() {
|
|
461
|
+
let totalUpdates = this.stateUpdates + this.noopUpdates, optimizationRate = totalUpdates > 0 ? Math.round(this.noopUpdates / totalUpdates * 100) : 0, avgUpdateTime = this.updateTimes.length > 0 ? this.updateTimes.reduce((a, b) => a + b, 0) / this.updateTimes.length : 0;
|
|
462
|
+
return {
|
|
463
|
+
stateUpdates: this.stateUpdates,
|
|
464
|
+
noopUpdates: this.noopUpdates,
|
|
465
|
+
optimizationRate,
|
|
466
|
+
eventListenerCount: this.eventListenerCount,
|
|
467
|
+
subscriptionCount: this.subscriptionCount,
|
|
468
|
+
avgUpdateTimeMs: Math.round(avgUpdateTime * 100) / 100,
|
|
469
|
+
lastUpdateTime: this.lastUpdateTime
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Reset all metrics
|
|
474
|
+
*/
|
|
475
|
+
resetMetrics() {
|
|
476
|
+
this.stateUpdates = 0, this.noopUpdates = 0, this.updateTimes = [], this.lastUpdateTime = 0;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/lib/core/base-collaborator.ts
|
|
481
|
+
var BaseCollaborator = class {
|
|
482
|
+
constructor(config, loggerPrefix) {
|
|
483
|
+
chunkSMUUAKC3_js.__publicField(this, "stateManager");
|
|
484
|
+
chunkSMUUAKC3_js.__publicField(this, "eventEmitter");
|
|
485
|
+
chunkSMUUAKC3_js.__publicField(this, "debug");
|
|
486
|
+
chunkSMUUAKC3_js.__publicField(this, "logger");
|
|
487
|
+
this.stateManager = config.stateManager, this.eventEmitter = config.eventEmitter, this.debug = config.debug ?? false, this.logger = chunkSMUUAKC3_js.createLogger(loggerPrefix);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Log debug message if debug mode is enabled
|
|
491
|
+
*/
|
|
492
|
+
log(message, data) {
|
|
493
|
+
this.debug && this.logger.debug(message, data);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Log error message if debug mode is enabled
|
|
497
|
+
*/
|
|
498
|
+
error(message, data) {
|
|
499
|
+
this.debug && this.logger.error(message, data);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get current state snapshot
|
|
503
|
+
*/
|
|
504
|
+
getState() {
|
|
505
|
+
return this.stateManager.getSnapshot();
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// src/lib/connection/wallet-authenticity-verifier.ts
|
|
510
|
+
var logger2 = chunkSMUUAKC3_js.createLogger("WalletAuthenticity"), WalletAuthenticityVerifier = class {
|
|
511
|
+
/**
|
|
512
|
+
* Verify a wallet's authenticity using dynamic heuristics
|
|
513
|
+
*
|
|
514
|
+
* @param wallet - The wallet object to verify
|
|
515
|
+
* @param walletName - Expected wallet name
|
|
516
|
+
* @returns Verification result with confidence score
|
|
517
|
+
*/
|
|
518
|
+
static verify(wallet, walletName) {
|
|
519
|
+
let warnings = [], name = walletName.toLowerCase();
|
|
520
|
+
logger2.debug("Verifying wallet with dynamic heuristics", { name });
|
|
521
|
+
let securityScore = {
|
|
522
|
+
walletStandardCompliance: 0,
|
|
523
|
+
methodIntegrity: 0,
|
|
524
|
+
chainSupport: 0,
|
|
525
|
+
maliciousPatterns: 1,
|
|
526
|
+
// Start at 1, deduct for issues
|
|
527
|
+
identityConsistency: 0
|
|
528
|
+
}, walletStandardScore = this.checkWalletStandardCompliance(wallet);
|
|
529
|
+
securityScore.walletStandardCompliance = walletStandardScore.score, warnings.push(...walletStandardScore.warnings);
|
|
530
|
+
let methodIntegrityScore = this.checkMethodIntegrity(wallet);
|
|
531
|
+
securityScore.methodIntegrity = methodIntegrityScore.score, warnings.push(...methodIntegrityScore.warnings);
|
|
532
|
+
let chainSupportScore = this.checkChainSupport(wallet);
|
|
533
|
+
securityScore.chainSupport = chainSupportScore.score, warnings.push(...chainSupportScore.warnings);
|
|
534
|
+
let maliciousPatterns = this.detectMaliciousPatterns(wallet, name);
|
|
535
|
+
securityScore.maliciousPatterns = maliciousPatterns.score, warnings.push(...maliciousPatterns.warnings);
|
|
536
|
+
let identityScore = this.checkIdentityConsistency(wallet, name);
|
|
537
|
+
securityScore.identityConsistency = identityScore.score, warnings.push(...identityScore.warnings);
|
|
538
|
+
let weights = {
|
|
539
|
+
walletStandardCompliance: 0.25,
|
|
540
|
+
methodIntegrity: 0.2,
|
|
541
|
+
chainSupport: 0.15,
|
|
542
|
+
maliciousPatterns: 0.3,
|
|
543
|
+
// Highest weight - security critical
|
|
544
|
+
identityConsistency: 0.1
|
|
545
|
+
}, confidence = securityScore.walletStandardCompliance * weights.walletStandardCompliance + securityScore.methodIntegrity * weights.methodIntegrity + securityScore.chainSupport * weights.chainSupport + securityScore.maliciousPatterns * weights.maliciousPatterns + securityScore.identityConsistency * weights.identityConsistency, authentic = confidence >= 0.6 && securityScore.maliciousPatterns > 0.5, reason = authentic ? "Wallet passed all security checks" : `Wallet failed security checks (confidence: ${Math.round(confidence * 100)}%)`;
|
|
546
|
+
return logger2.debug("Wallet verification complete", {
|
|
547
|
+
name,
|
|
548
|
+
authentic,
|
|
549
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
550
|
+
warnings: warnings.length
|
|
551
|
+
}), {
|
|
552
|
+
authentic,
|
|
553
|
+
confidence,
|
|
554
|
+
reason,
|
|
555
|
+
warnings,
|
|
556
|
+
securityScore
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Check Wallet Standard compliance
|
|
561
|
+
* Returns score 0-1 based on how well the wallet implements the standard
|
|
562
|
+
*/
|
|
563
|
+
static checkWalletStandardCompliance(wallet) {
|
|
564
|
+
let warnings = [], score = 0;
|
|
565
|
+
if (wallet.features && typeof wallet.features == "object") {
|
|
566
|
+
score += 0.3;
|
|
567
|
+
let requiredFeatures = ["standard:connect", "standard:disconnect", "standard:events"], presentFeatures = requiredFeatures.filter((feature) => feature in wallet.features);
|
|
568
|
+
score += presentFeatures.length / requiredFeatures.length * 0.4;
|
|
569
|
+
let solanaFeatures = [
|
|
570
|
+
"solana:signTransaction",
|
|
571
|
+
"solana:signAndSendTransaction",
|
|
572
|
+
"solana:signMessage",
|
|
573
|
+
"solana:signAllTransactions"
|
|
574
|
+
], presentSolanaFeatures = solanaFeatures.filter((feature) => feature in wallet.features);
|
|
575
|
+
score += presentSolanaFeatures.length / solanaFeatures.length * 0.3, presentFeatures.length < requiredFeatures.length && warnings.push("Wallet missing some standard features");
|
|
576
|
+
} else
|
|
577
|
+
warnings.push("Wallet does not implement Wallet Standard");
|
|
578
|
+
return { score: Math.min(score, 1), warnings };
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Validate method integrity
|
|
582
|
+
* Checks if methods appear to be genuine and not tampered with
|
|
583
|
+
*/
|
|
584
|
+
static checkMethodIntegrity(wallet) {
|
|
585
|
+
let warnings = [], score = 0, walletObj = wallet, criticalMethods = ["connect", "disconnect"], existingMethods = criticalMethods.filter((method) => typeof walletObj[method] == "function");
|
|
586
|
+
if (existingMethods.length === 0)
|
|
587
|
+
return warnings.push("Wallet missing critical methods"), { score: 0, warnings };
|
|
588
|
+
score += existingMethods.length / criticalMethods.length * 0.5;
|
|
589
|
+
let suspiciousMethodCount = 0;
|
|
590
|
+
for (let method of existingMethods) {
|
|
591
|
+
let funcStr = walletObj[method].toString();
|
|
592
|
+
funcStr.length > 1e3 && (suspiciousMethodCount++, warnings.push(`Method ${method} has unusually long implementation`));
|
|
593
|
+
let suspiciousKeywords = ["fetch(", "XMLHttpRequest", "sendToServer", "exfiltrate", "steal", "phish"];
|
|
594
|
+
for (let keyword of suspiciousKeywords)
|
|
595
|
+
if (funcStr.includes(keyword)) {
|
|
596
|
+
suspiciousMethodCount++, warnings.push(`Method ${method} contains suspicious code pattern`);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
let suspiciousRatio = suspiciousMethodCount / existingMethods.length;
|
|
601
|
+
return score += (1 - suspiciousRatio) * 0.5, { score: Math.max(0, Math.min(score, 1)), warnings };
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Verify Solana chain support
|
|
605
|
+
*/
|
|
606
|
+
static checkChainSupport(wallet) {
|
|
607
|
+
let warnings = [], score = 0;
|
|
608
|
+
return Array.isArray(wallet.chains) ? wallet.chains.some((chain) => String(chain).toLowerCase().includes("solana")) ? score = 1 : (warnings.push("Wallet does not explicitly support Solana chain"), score = 0.3) : wallet.chains === void 0 && (warnings.push("Wallet does not declare supported chains"), score = 0.5), { score, warnings };
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Detect common patterns used by malicious wallet extensions
|
|
612
|
+
*/
|
|
613
|
+
static detectMaliciousPatterns(wallet, expectedName) {
|
|
614
|
+
let warnings = [], score = 1, walletObj = wallet, identityFlags = Object.keys(walletObj).filter(
|
|
615
|
+
(key) => key.startsWith("is") && (key.endsWith("Wallet") || key.endsWith("wallet") || /^is[A-Z]/.test(key)) && walletObj[key] === true
|
|
616
|
+
);
|
|
617
|
+
identityFlags.length > 2 && (score -= 0.3, warnings.push(`Multiple wallet identity flags detected: ${identityFlags.join(", ")}`));
|
|
618
|
+
let explicitlyMaliciousProps = [
|
|
619
|
+
"stealPrivateKey",
|
|
620
|
+
"getPrivateKey",
|
|
621
|
+
"exportPrivateKey",
|
|
622
|
+
"sendToAttacker",
|
|
623
|
+
"phishingUrl",
|
|
624
|
+
"malware",
|
|
625
|
+
"backdoor"
|
|
626
|
+
], lowerCaseKeys = Object.keys(walletObj).map((k) => k.toLowerCase());
|
|
627
|
+
for (let prop of explicitlyMaliciousProps)
|
|
628
|
+
lowerCaseKeys.includes(prop.toLowerCase()) && (score = 0, warnings.push(`Explicitly malicious property detected: ${prop}`));
|
|
629
|
+
let urlProps = ["iconUrl", "url", "homepage", "website"];
|
|
630
|
+
for (let prop of urlProps) {
|
|
631
|
+
let value = walletObj[prop];
|
|
632
|
+
typeof value == "string" && this.isSuspiciousUrl(value) && (score -= 0.2, warnings.push(`Suspicious URL in ${prop}: ${value}`));
|
|
633
|
+
}
|
|
634
|
+
if ("__proto__" in walletObj || "constructor" in walletObj) {
|
|
635
|
+
let proto = Object.getPrototypeOf(walletObj);
|
|
636
|
+
proto !== Object.prototype && proto !== null && (score -= 0.1, warnings.push("Wallet has unusual prototype chain"));
|
|
637
|
+
}
|
|
638
|
+
let propCount = Object.keys(walletObj).length;
|
|
639
|
+
return propCount > 100 && (score -= 0.1, warnings.push(`Wallet has unusually many properties (${propCount})`)), { score: Math.max(0, score), warnings };
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Check if wallet identity is consistent with expected name
|
|
643
|
+
*/
|
|
644
|
+
static checkIdentityConsistency(wallet, expectedName) {
|
|
645
|
+
let warnings = [], score = 0, walletObj = wallet, name = expectedName.toLowerCase(), identityIndicators = [
|
|
646
|
+
walletObj.name,
|
|
647
|
+
walletObj.providerName,
|
|
648
|
+
walletObj.metadata?.name,
|
|
649
|
+
walletObj._metadata?.name
|
|
650
|
+
].filter(Boolean), hasMatch = false;
|
|
651
|
+
for (let indicator of identityIndicators)
|
|
652
|
+
if (typeof indicator == "string" && indicator.toLowerCase().includes(name)) {
|
|
653
|
+
hasMatch = true, score += 0.5;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
let capitalizedName = name.charAt(0).toUpperCase() + name.slice(1), identityFlags = [
|
|
657
|
+
`is${capitalizedName}`,
|
|
658
|
+
`is${capitalizedName}Wallet`,
|
|
659
|
+
`is${capitalizedName.toLowerCase()}`
|
|
660
|
+
];
|
|
661
|
+
for (let flag of identityFlags)
|
|
662
|
+
if (walletObj[flag] === true) {
|
|
663
|
+
hasMatch = true, score += 0.5;
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
return hasMatch || warnings.push(`Wallet identity does not match expected name: ${expectedName}`), { score: Math.min(score, 1), warnings };
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Check if a URL looks suspicious
|
|
670
|
+
*/
|
|
671
|
+
static isSuspiciousUrl(url) {
|
|
672
|
+
try {
|
|
673
|
+
let parsed = new URL(url), suspiciousPatterns = [
|
|
674
|
+
"bit.ly",
|
|
675
|
+
"tinyurl.com",
|
|
676
|
+
"tiny.cc",
|
|
677
|
+
"is.gd",
|
|
678
|
+
"goo.gl",
|
|
679
|
+
"t.co",
|
|
680
|
+
".tk",
|
|
681
|
+
// Free domain TLDs
|
|
682
|
+
".ml",
|
|
683
|
+
".ga",
|
|
684
|
+
".cf",
|
|
685
|
+
".gq"
|
|
686
|
+
], hostname = parsed.hostname.toLowerCase();
|
|
687
|
+
return !!(suspiciousPatterns.some((pattern) => hostname.includes(pattern)) || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) || hostname.split(".").length > 4);
|
|
688
|
+
} catch {
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Batch verify multiple wallets
|
|
694
|
+
*
|
|
695
|
+
* @param wallets - Array of wallet objects with their names
|
|
696
|
+
* @returns Map of wallet names to verification results
|
|
697
|
+
*/
|
|
698
|
+
static verifyBatch(wallets) {
|
|
699
|
+
let results = /* @__PURE__ */ new Map();
|
|
700
|
+
for (let { wallet, name } of wallets)
|
|
701
|
+
results.set(name, this.verify(wallet, name));
|
|
702
|
+
return results;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get a human-readable security report for a wallet
|
|
706
|
+
*
|
|
707
|
+
* @param result - Verification result
|
|
708
|
+
* @returns Formatted security report
|
|
709
|
+
*/
|
|
710
|
+
static getSecurityReport(result) {
|
|
711
|
+
let lines = [];
|
|
712
|
+
return lines.push(`Security Assessment: ${result.authentic ? "\u2705 PASSED" : "\u274C FAILED"}`), lines.push(`Overall Confidence: ${Math.round(result.confidence * 100)}%`), lines.push(""), lines.push("Score Breakdown:"), lines.push(
|
|
713
|
+
` - Wallet Standard Compliance: ${Math.round(result.securityScore.walletStandardCompliance * 100)}%`
|
|
714
|
+
), lines.push(` - Method Integrity: ${Math.round(result.securityScore.methodIntegrity * 100)}%`), lines.push(` - Chain Support: ${Math.round(result.securityScore.chainSupport * 100)}%`), lines.push(` - Malicious Patterns: ${Math.round(result.securityScore.maliciousPatterns * 100)}%`), lines.push(` - Identity Consistency: ${Math.round(result.securityScore.identityConsistency * 100)}%`), result.warnings.length > 0 && (lines.push(""), lines.push("Warnings:"), result.warnings.forEach((w) => lines.push(` \u26A0\uFE0F ${w}`))), lines.join(`
|
|
715
|
+
`);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// src/lib/connection/wallet-detector.ts
|
|
720
|
+
var logger3 = chunkSMUUAKC3_js.createLogger("WalletDetector");
|
|
721
|
+
function hasFeature(wallet, featureName) {
|
|
722
|
+
return wallet.features != null && wallet.features[featureName] !== void 0;
|
|
723
|
+
}
|
|
724
|
+
function verifyWalletName(wallet, requestedName) {
|
|
725
|
+
let name = requestedName.toLowerCase(), walletObj = wallet, nameFields = [
|
|
726
|
+
walletObj.name,
|
|
727
|
+
walletObj.providerName,
|
|
728
|
+
walletObj.metadata?.name
|
|
729
|
+
].filter(Boolean);
|
|
730
|
+
for (let field of nameFields)
|
|
731
|
+
if (typeof field == "string" && field.toLowerCase().includes(name))
|
|
732
|
+
return true;
|
|
733
|
+
let capitalizedName = name.charAt(0).toUpperCase() + name.slice(1), commonFlagPatterns = [`is${capitalizedName}`, `is${capitalizedName}Wallet`];
|
|
734
|
+
for (let flagName of commonFlagPatterns)
|
|
735
|
+
if (walletObj[flagName] === true)
|
|
736
|
+
return true;
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
var WalletDetector = class extends BaseCollaborator {
|
|
740
|
+
constructor(stateManager, eventEmitter, debug = false) {
|
|
741
|
+
super({ stateManager, eventEmitter, debug }, "WalletDetector");
|
|
742
|
+
chunkSMUUAKC3_js.__publicField(this, "unsubscribers", []);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Initialize wallet detection
|
|
746
|
+
*/
|
|
747
|
+
initialize() {
|
|
748
|
+
if (!(typeof window > "u"))
|
|
749
|
+
try {
|
|
750
|
+
let walletsApi = getWalletsRegistry(), update = () => {
|
|
751
|
+
let ws = walletsApi.get(), previousCount = this.getState().wallets.length, newCount = ws.length;
|
|
752
|
+
newCount !== previousCount && this.log("\u{1F50D} WalletDetector: found wallets:", newCount);
|
|
753
|
+
let unique = this.deduplicateWallets(ws);
|
|
754
|
+
this.stateManager.updateState({
|
|
755
|
+
wallets: unique.map((w) => this.mapToWalletInfo(w))
|
|
756
|
+
}), newCount !== previousCount && newCount > 0 && this.eventEmitter.emit({
|
|
757
|
+
type: "wallets:detected",
|
|
758
|
+
count: newCount,
|
|
759
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
760
|
+
});
|
|
761
|
+
};
|
|
762
|
+
update(), this.unsubscribers.push(walletsApi.on("register", update)), this.unsubscribers.push(walletsApi.on("unregister", update)), setTimeout(() => {
|
|
763
|
+
this.getState().connected || update();
|
|
764
|
+
}, 1e3);
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Check if a specific wallet is available immediately via direct window object detection
|
|
770
|
+
*/
|
|
771
|
+
detectDirectWallet(walletName) {
|
|
772
|
+
if (typeof window > "u") return null;
|
|
773
|
+
let name = walletName.toLowerCase(), windowObj = window, checks = [
|
|
774
|
+
() => windowObj[name],
|
|
775
|
+
() => windowObj[`${name}Wallet`],
|
|
776
|
+
() => windowObj.solana,
|
|
777
|
+
() => {
|
|
778
|
+
let keys = Object.keys(window).filter((k) => k.toLowerCase().includes(name));
|
|
779
|
+
return keys.length > 0 ? windowObj[keys[0]] : null;
|
|
780
|
+
}
|
|
781
|
+
];
|
|
782
|
+
for (let check of checks)
|
|
783
|
+
try {
|
|
784
|
+
let result = check();
|
|
785
|
+
if (result && typeof result == "object") {
|
|
786
|
+
let wallet = result;
|
|
787
|
+
if (!verifyWalletName(wallet, walletName))
|
|
788
|
+
continue;
|
|
789
|
+
let verification = WalletAuthenticityVerifier.verify(wallet, walletName);
|
|
790
|
+
if (!verification.authentic) {
|
|
791
|
+
logger3.warn("Rejecting potentially malicious wallet", {
|
|
792
|
+
name: walletName,
|
|
793
|
+
reason: verification.reason,
|
|
794
|
+
confidence: verification.confidence
|
|
795
|
+
});
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
verification.warnings.length > 0 && logger3.warn("Wallet verification warnings", {
|
|
799
|
+
name: walletName,
|
|
800
|
+
warnings: verification.warnings
|
|
801
|
+
});
|
|
802
|
+
let hasStandardConnect = wallet.features?.["standard:connect"], hasLegacyConnect = typeof wallet.connect == "function";
|
|
803
|
+
if (hasStandardConnect || hasLegacyConnect)
|
|
804
|
+
return logger3.debug("Authentic wallet detected", {
|
|
805
|
+
name: walletName,
|
|
806
|
+
confidence: verification.confidence
|
|
807
|
+
}), wallet;
|
|
808
|
+
}
|
|
809
|
+
} catch {
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Get currently detected wallets
|
|
816
|
+
*/
|
|
817
|
+
getDetectedWallets() {
|
|
818
|
+
return this.getState().wallets;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Convert a Wallet Standard wallet to WalletInfo with capability checks
|
|
822
|
+
*/
|
|
823
|
+
mapToWalletInfo(wallet) {
|
|
824
|
+
let hasConnect = hasFeature(wallet, "standard:connect"), hasDisconnect = hasFeature(wallet, "standard:disconnect"), isSolana = Array.isArray(wallet.chains) && wallet.chains.some((c) => typeof c == "string" && c.includes("solana"));
|
|
825
|
+
return {
|
|
826
|
+
wallet,
|
|
827
|
+
installed: true,
|
|
828
|
+
connectable: hasConnect && hasDisconnect && isSolana
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Deduplicate wallets by name (keeps first occurrence)
|
|
833
|
+
*/
|
|
834
|
+
deduplicateWallets(wallets) {
|
|
835
|
+
let seen = /* @__PURE__ */ new Map();
|
|
836
|
+
for (let wallet of wallets)
|
|
837
|
+
seen.has(wallet.name) || seen.set(wallet.name, wallet);
|
|
838
|
+
return Array.from(seen.values());
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Cleanup resources
|
|
842
|
+
*/
|
|
843
|
+
destroy() {
|
|
844
|
+
for (let unsubscribe of this.unsubscribers)
|
|
845
|
+
try {
|
|
846
|
+
unsubscribe();
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
this.unsubscribers = [];
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/lib/connection/connection-manager.ts
|
|
854
|
+
function getConnectFeature(wallet) {
|
|
855
|
+
return wallet.features["standard:connect"]?.connect ?? null;
|
|
856
|
+
}
|
|
857
|
+
function getDisconnectFeature(wallet) {
|
|
858
|
+
return wallet.features["standard:disconnect"]?.disconnect ?? null;
|
|
859
|
+
}
|
|
860
|
+
function getEventsFeature(wallet) {
|
|
861
|
+
return wallet.features["standard:events"]?.on ?? null;
|
|
862
|
+
}
|
|
863
|
+
var ConnectionManager = class extends BaseCollaborator {
|
|
864
|
+
constructor(stateManager, eventEmitter, walletStorage, debug = false) {
|
|
865
|
+
super({ stateManager, eventEmitter, debug }, "ConnectionManager");
|
|
866
|
+
chunkSMUUAKC3_js.__publicField(this, "walletStorage");
|
|
867
|
+
chunkSMUUAKC3_js.__publicField(this, "walletChangeUnsub", null);
|
|
868
|
+
chunkSMUUAKC3_js.__publicField(this, "pollTimer", null);
|
|
869
|
+
chunkSMUUAKC3_js.__publicField(this, "pollAttempts", 0);
|
|
870
|
+
this.walletStorage = walletStorage;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Connect to a wallet
|
|
874
|
+
*/
|
|
875
|
+
async connect(wallet, walletName) {
|
|
876
|
+
if (typeof window > "u") return;
|
|
877
|
+
let name = walletName || wallet.name;
|
|
878
|
+
this.eventEmitter.emit({
|
|
879
|
+
type: "connecting",
|
|
880
|
+
wallet: name,
|
|
881
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
882
|
+
}), this.stateManager.updateState({ connecting: true }, true);
|
|
883
|
+
try {
|
|
884
|
+
let connect = getConnectFeature(wallet);
|
|
885
|
+
if (!connect) throw new Error(`Wallet ${name} does not support standard connect`);
|
|
886
|
+
let result = await connect({ silent: false }), walletAccounts = wallet.accounts, accountMap = /* @__PURE__ */ new Map();
|
|
887
|
+
for (let a of [...walletAccounts, ...result.accounts]) accountMap.set(a.address, a);
|
|
888
|
+
let accounts = Array.from(accountMap.values()).map((a) => this.toAccountInfo(a)), state = this.getState(), previouslySelected = state.selectedAccount, previousAddresses = new Set(state.accounts.map((a) => a.address)), selected = accounts.find((a) => !previousAddresses.has(a.address))?.address ?? previouslySelected ?? accounts[0]?.address ?? null;
|
|
889
|
+
this.stateManager.updateState(
|
|
890
|
+
{
|
|
891
|
+
selectedWallet: wallet,
|
|
892
|
+
connected: true,
|
|
893
|
+
connecting: false,
|
|
894
|
+
accounts,
|
|
895
|
+
selectedAccount: selected
|
|
896
|
+
},
|
|
897
|
+
true
|
|
898
|
+
), this.log("\u2705 Connection successful - state updated:", {
|
|
899
|
+
connected: true,
|
|
900
|
+
selectedWallet: wallet.name,
|
|
901
|
+
selectedAccount: selected,
|
|
902
|
+
accountsCount: accounts.length
|
|
903
|
+
}), this.eventEmitter.emit({
|
|
904
|
+
type: "wallet:connected",
|
|
905
|
+
wallet: name,
|
|
906
|
+
account: selected || "",
|
|
907
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
908
|
+
}), this.walletStorage && (!("isAvailable" in this.walletStorage) || typeof this.walletStorage.isAvailable != "function" || this.walletStorage.isAvailable() ? this.walletStorage.set(name) : this.log("Storage not available (private browsing?), skipping wallet persistence")), this.subscribeToWalletEvents();
|
|
909
|
+
} catch (e) {
|
|
910
|
+
let errorMessage = e instanceof Error ? e.message : String(e);
|
|
911
|
+
throw this.eventEmitter.emit({
|
|
912
|
+
type: "connection:failed",
|
|
913
|
+
wallet: name,
|
|
914
|
+
error: errorMessage,
|
|
915
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
916
|
+
}), this.eventEmitter.emit({
|
|
917
|
+
type: "error",
|
|
918
|
+
error: e instanceof Error ? e : new Error(errorMessage),
|
|
919
|
+
context: "wallet-connection",
|
|
920
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
921
|
+
}), this.stateManager.updateState(
|
|
922
|
+
{
|
|
923
|
+
selectedWallet: null,
|
|
924
|
+
connected: false,
|
|
925
|
+
connecting: false,
|
|
926
|
+
accounts: [],
|
|
927
|
+
selectedAccount: null
|
|
928
|
+
},
|
|
929
|
+
true
|
|
930
|
+
), e;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Disconnect from wallet
|
|
935
|
+
*/
|
|
936
|
+
async disconnect() {
|
|
937
|
+
if (this.walletChangeUnsub) {
|
|
938
|
+
try {
|
|
939
|
+
this.walletChangeUnsub();
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
this.walletChangeUnsub = null;
|
|
943
|
+
}
|
|
944
|
+
this.stopPollingWalletAccounts();
|
|
945
|
+
let wallet = this.getState().selectedWallet;
|
|
946
|
+
if (wallet) {
|
|
947
|
+
let disconnect = getDisconnectFeature(wallet);
|
|
948
|
+
if (disconnect)
|
|
949
|
+
try {
|
|
950
|
+
await disconnect();
|
|
951
|
+
} catch {
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
this.stateManager.updateState(
|
|
955
|
+
{
|
|
956
|
+
selectedWallet: null,
|
|
957
|
+
connected: false,
|
|
958
|
+
accounts: [],
|
|
959
|
+
selectedAccount: null
|
|
960
|
+
},
|
|
961
|
+
true
|
|
962
|
+
), this.eventEmitter.emit({
|
|
963
|
+
type: "wallet:disconnected",
|
|
964
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
965
|
+
}), this.walletStorage && "clear" in this.walletStorage && typeof this.walletStorage.clear == "function" ? this.walletStorage.clear() : this.walletStorage?.set(void 0);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Select a different account
|
|
969
|
+
*/
|
|
970
|
+
async selectAccount(address) {
|
|
971
|
+
let state = this.getState(), current = state.selectedWallet;
|
|
972
|
+
if (!current) throw new Error("No wallet connected");
|
|
973
|
+
if (!address || address.length < 5)
|
|
974
|
+
throw new Error(`Invalid address format: ${address}`);
|
|
975
|
+
let target = state.accounts.find((acc) => acc.address === address)?.raw ?? null;
|
|
976
|
+
if (!target)
|
|
977
|
+
try {
|
|
978
|
+
let connect = getConnectFeature(current);
|
|
979
|
+
if (connect) {
|
|
980
|
+
let accounts = (await connect()).accounts.map((a) => this.toAccountInfo(a));
|
|
981
|
+
target = accounts.find((acc) => acc.address === address)?.raw ?? null, this.stateManager.updateState({ accounts });
|
|
982
|
+
}
|
|
983
|
+
} catch {
|
|
984
|
+
throw new Error("Failed to reconnect wallet for account selection");
|
|
985
|
+
}
|
|
986
|
+
if (!target) throw new Error(`Requested account not available: ${address}`);
|
|
987
|
+
this.stateManager.updateState({ selectedAccount: target.address }), this.eventEmitter.emit({
|
|
988
|
+
type: "account:changed",
|
|
989
|
+
account: target.address,
|
|
990
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Convert wallet account to AccountInfo
|
|
995
|
+
*/
|
|
996
|
+
toAccountInfo(account) {
|
|
997
|
+
return {
|
|
998
|
+
address: account.address,
|
|
999
|
+
icon: account.icon,
|
|
1000
|
+
raw: account
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Subscribe to wallet change events
|
|
1005
|
+
*/
|
|
1006
|
+
subscribeToWalletEvents() {
|
|
1007
|
+
if (this.walletChangeUnsub) {
|
|
1008
|
+
try {
|
|
1009
|
+
this.walletChangeUnsub();
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
this.walletChangeUnsub = null;
|
|
1013
|
+
}
|
|
1014
|
+
this.stopPollingWalletAccounts();
|
|
1015
|
+
let wallet = this.getState().selectedWallet;
|
|
1016
|
+
if (!wallet) return;
|
|
1017
|
+
let eventsOn = getEventsFeature(wallet);
|
|
1018
|
+
if (!eventsOn) {
|
|
1019
|
+
this.startPollingWalletAccounts();
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
try {
|
|
1023
|
+
this.walletChangeUnsub = eventsOn("change", (properties) => {
|
|
1024
|
+
let changeAccounts = properties?.accounts ?? [];
|
|
1025
|
+
if (changeAccounts.length === 0) return;
|
|
1026
|
+
let nextAccounts = changeAccounts.map((a) => this.toAccountInfo(a));
|
|
1027
|
+
nextAccounts.length > 0 && this.stateManager.updateState({
|
|
1028
|
+
accounts: nextAccounts
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
} catch {
|
|
1032
|
+
this.startPollingWalletAccounts();
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Start polling wallet accounts (fallback when events not available)
|
|
1037
|
+
* Uses exponential backoff to reduce polling frequency over time
|
|
1038
|
+
*/
|
|
1039
|
+
startPollingWalletAccounts() {
|
|
1040
|
+
if (this.pollTimer) return;
|
|
1041
|
+
let wallet = this.getState().selectedWallet;
|
|
1042
|
+
if (!wallet) return;
|
|
1043
|
+
this.pollAttempts = 0;
|
|
1044
|
+
let poll = () => {
|
|
1045
|
+
if (this.pollAttempts >= 20) {
|
|
1046
|
+
this.stopPollingWalletAccounts(), this.log("Stopped wallet polling after max attempts");
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
try {
|
|
1050
|
+
let state = this.getState(), nextAccounts = wallet.accounts.map((a) => this.toAccountInfo(a));
|
|
1051
|
+
state.accounts.length === 0 && nextAccounts.length > 0 && (this.stateManager.updateState({
|
|
1052
|
+
accounts: nextAccounts,
|
|
1053
|
+
selectedAccount: state.selectedAccount || nextAccounts[0]?.address || null
|
|
1054
|
+
}), this.pollAttempts = 0);
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
this.log("Wallet polling error:", error);
|
|
1057
|
+
}
|
|
1058
|
+
this.pollAttempts++;
|
|
1059
|
+
let intervalIndex = Math.min(this.pollAttempts, POLL_INTERVALS_MS.length - 1), interval = POLL_INTERVALS_MS[intervalIndex];
|
|
1060
|
+
this.pollTimer = setTimeout(poll, interval);
|
|
1061
|
+
};
|
|
1062
|
+
poll();
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Stop polling wallet accounts
|
|
1066
|
+
*/
|
|
1067
|
+
stopPollingWalletAccounts() {
|
|
1068
|
+
this.pollTimer && (clearTimeout(this.pollTimer), this.pollTimer = null, this.pollAttempts = 0);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Get stored wallet name
|
|
1072
|
+
*/
|
|
1073
|
+
getStoredWallet() {
|
|
1074
|
+
return this.walletStorage?.get() ?? null;
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
// src/lib/connection/auto-connector.ts
|
|
1079
|
+
var logger4 = chunkSMUUAKC3_js.createLogger("AutoConnector"), MIN_ADDRESS_LENGTH = 30, AutoConnector = class {
|
|
1080
|
+
constructor(walletDetector, connectionManager, stateManager, walletStorage, debug = false) {
|
|
1081
|
+
chunkSMUUAKC3_js.__publicField(this, "walletDetector");
|
|
1082
|
+
chunkSMUUAKC3_js.__publicField(this, "connectionManager");
|
|
1083
|
+
chunkSMUUAKC3_js.__publicField(this, "stateManager");
|
|
1084
|
+
chunkSMUUAKC3_js.__publicField(this, "walletStorage");
|
|
1085
|
+
chunkSMUUAKC3_js.__publicField(this, "debug");
|
|
1086
|
+
this.walletDetector = walletDetector, this.connectionManager = connectionManager, this.stateManager = stateManager, this.walletStorage = walletStorage, this.debug = debug;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Attempt auto-connection using both instant and fallback strategies
|
|
1090
|
+
*/
|
|
1091
|
+
async attemptAutoConnect() {
|
|
1092
|
+
return await this.attemptInstantConnect() ? true : (await this.attemptStandardConnect(), this.stateManager.getSnapshot().connected);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Attempt instant auto-connection using direct wallet detection
|
|
1096
|
+
* Bypasses wallet standard initialization for maximum speed
|
|
1097
|
+
*/
|
|
1098
|
+
async attemptInstantConnect() {
|
|
1099
|
+
let storedWalletName = this.walletStorage?.get();
|
|
1100
|
+
if (!storedWalletName) return false;
|
|
1101
|
+
let directWallet = this.walletDetector.detectDirectWallet(storedWalletName);
|
|
1102
|
+
if (!directWallet) return false;
|
|
1103
|
+
this.debug && logger4.info("Instant auto-connect: found wallet directly in window", { walletName: storedWalletName });
|
|
1104
|
+
try {
|
|
1105
|
+
let features = {};
|
|
1106
|
+
if (directWallet.connect && (features["standard:connect"] = {
|
|
1107
|
+
connect: async (...args) => {
|
|
1108
|
+
let options = args[0], result = await directWallet.connect(options);
|
|
1109
|
+
if (this.debug && (logger4.debug("Direct wallet connect result", { result }), logger4.debug("Direct wallet publicKey property", { publicKey: directWallet.publicKey })), result && typeof result == "object" && "accounts" in result && Array.isArray(result.accounts))
|
|
1110
|
+
return result;
|
|
1111
|
+
let legacyResult = result;
|
|
1112
|
+
if (legacyResult?.publicKey && typeof legacyResult.publicKey.toString == "function")
|
|
1113
|
+
return {
|
|
1114
|
+
accounts: [
|
|
1115
|
+
{
|
|
1116
|
+
address: legacyResult.publicKey.toString(),
|
|
1117
|
+
publicKey: legacyResult.publicKey.toBytes ? legacyResult.publicKey.toBytes() : new Uint8Array(),
|
|
1118
|
+
chains: ["solana:mainnet", "solana:devnet", "solana:testnet"],
|
|
1119
|
+
features: []
|
|
1120
|
+
}
|
|
1121
|
+
]
|
|
1122
|
+
};
|
|
1123
|
+
if (directWallet.publicKey && typeof directWallet.publicKey.toString == "function") {
|
|
1124
|
+
let address = directWallet.publicKey.toString();
|
|
1125
|
+
return this.debug && logger4.debug("Using legacy wallet pattern - publicKey from wallet object"), {
|
|
1126
|
+
accounts: [
|
|
1127
|
+
{
|
|
1128
|
+
address,
|
|
1129
|
+
publicKey: directWallet.publicKey.toBytes ? directWallet.publicKey.toBytes() : new Uint8Array(),
|
|
1130
|
+
chains: ["solana:mainnet", "solana:devnet", "solana:testnet"],
|
|
1131
|
+
features: []
|
|
1132
|
+
}
|
|
1133
|
+
]
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
let publicKeyResult = result;
|
|
1137
|
+
return publicKeyResult && typeof publicKeyResult.toString == "function" && publicKeyResult.toString().length > MIN_ADDRESS_LENGTH ? {
|
|
1138
|
+
accounts: [
|
|
1139
|
+
{
|
|
1140
|
+
address: publicKeyResult.toString(),
|
|
1141
|
+
publicKey: publicKeyResult.toBytes ? publicKeyResult.toBytes() : new Uint8Array(),
|
|
1142
|
+
chains: ["solana:mainnet", "solana:devnet", "solana:testnet"],
|
|
1143
|
+
features: []
|
|
1144
|
+
}
|
|
1145
|
+
]
|
|
1146
|
+
} : (this.debug && logger4.error("Legacy wallet: No valid publicKey found in any expected location"), { accounts: [] });
|
|
1147
|
+
}
|
|
1148
|
+
}), directWallet.disconnect) {
|
|
1149
|
+
let disconnectFn = directWallet.disconnect;
|
|
1150
|
+
features["standard:disconnect"] = {
|
|
1151
|
+
disconnect: () => disconnectFn.call(directWallet)
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
if (directWallet.signTransaction) {
|
|
1155
|
+
let signTransactionFn = directWallet.signTransaction;
|
|
1156
|
+
features["standard:signTransaction"] = {
|
|
1157
|
+
signTransaction: (tx) => signTransactionFn.call(directWallet, tx)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (directWallet.signMessage) {
|
|
1161
|
+
let signMessageFn = directWallet.signMessage;
|
|
1162
|
+
features["standard:signMessage"] = {
|
|
1163
|
+
signMessage: (...args) => {
|
|
1164
|
+
let msg = args[0];
|
|
1165
|
+
return signMessageFn.call(directWallet, msg);
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
directWallet.features && Object.assign(features, directWallet.features);
|
|
1170
|
+
let walletIcon = directWallet.icon || directWallet._metadata?.icon || directWallet.adapter?.icon || directWallet.metadata?.icon || directWallet.iconUrl, wallet = {
|
|
1171
|
+
version: "1.0.0",
|
|
1172
|
+
name: storedWalletName,
|
|
1173
|
+
icon: walletIcon,
|
|
1174
|
+
chains: directWallet.chains || [
|
|
1175
|
+
"solana:mainnet",
|
|
1176
|
+
"solana:devnet",
|
|
1177
|
+
"solana:testnet"
|
|
1178
|
+
],
|
|
1179
|
+
features,
|
|
1180
|
+
accounts: []
|
|
1181
|
+
};
|
|
1182
|
+
return this.stateManager.updateState(
|
|
1183
|
+
{
|
|
1184
|
+
wallets: [
|
|
1185
|
+
{
|
|
1186
|
+
wallet,
|
|
1187
|
+
installed: true,
|
|
1188
|
+
connectable: true
|
|
1189
|
+
}
|
|
1190
|
+
]
|
|
1191
|
+
},
|
|
1192
|
+
true
|
|
1193
|
+
), this.debug && logger4.info("Attempting to connect via instant auto-connect", { walletName: storedWalletName }), await this.connectionManager.connect(wallet, storedWalletName), this.debug && logger4.info("Instant auto-connect successful", { walletName: storedWalletName }), setTimeout(() => {
|
|
1194
|
+
let ws = getWalletsRegistry().get();
|
|
1195
|
+
this.debug && logger4.debug("Checking for wallet standard update", {
|
|
1196
|
+
wsLength: ws.length,
|
|
1197
|
+
currentWalletsLength: this.stateManager.getSnapshot().wallets.length,
|
|
1198
|
+
shouldUpdate: ws.length > 1
|
|
1199
|
+
}), ws.length > 1 && this.walletDetector.initialize();
|
|
1200
|
+
}, 500), true;
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
return this.debug && logger4.error("Instant auto-connect failed", {
|
|
1203
|
+
walletName: storedWalletName,
|
|
1204
|
+
error: error instanceof Error ? error.message : error
|
|
1205
|
+
}), false;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Attempt auto-connection via standard wallet detection (fallback)
|
|
1210
|
+
*/
|
|
1211
|
+
async attemptStandardConnect() {
|
|
1212
|
+
try {
|
|
1213
|
+
if (this.stateManager.getSnapshot().connected) {
|
|
1214
|
+
this.debug && logger4.info("Auto-connect: Already connected, skipping fallback auto-connect");
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
let storedWalletName = this.walletStorage?.get();
|
|
1218
|
+
if (this.debug && (logger4.debug("Auto-connect: stored wallet", { storedWalletName }), logger4.debug("Auto-connect: available wallets", {
|
|
1219
|
+
wallets: this.stateManager.getSnapshot().wallets.map((w) => w.wallet.name)
|
|
1220
|
+
})), !storedWalletName) return;
|
|
1221
|
+
let walletInfo = this.stateManager.getSnapshot().wallets.find((w) => w.wallet.name === storedWalletName);
|
|
1222
|
+
walletInfo ? (this.debug && logger4.info("Auto-connect: Found stored wallet, connecting"), await this.connectionManager.connect(walletInfo.wallet, storedWalletName)) : setTimeout(() => {
|
|
1223
|
+
let retryWallet = this.stateManager.getSnapshot().wallets.find((w) => w.wallet.name === storedWalletName);
|
|
1224
|
+
retryWallet && (this.debug && logger4.info("Auto-connect: Retry successful"), this.connectionManager.connect(retryWallet.wallet, storedWalletName).catch((err) => {
|
|
1225
|
+
logger4.error("Auto-connect retry connection failed", { error: err });
|
|
1226
|
+
}));
|
|
1227
|
+
}, 1e3);
|
|
1228
|
+
} catch (e) {
|
|
1229
|
+
this.debug && logger4.error("Auto-connect failed", { error: e }), this.walletStorage?.set(void 0);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
// src/lib/cluster/cluster-manager.ts
|
|
1235
|
+
var ClusterManager = class extends BaseCollaborator {
|
|
1236
|
+
constructor(stateManager, eventEmitter, clusterStorage, config, debug = false) {
|
|
1237
|
+
super({ stateManager, eventEmitter, debug }, "ClusterManager");
|
|
1238
|
+
chunkSMUUAKC3_js.__publicField(this, "clusterStorage");
|
|
1239
|
+
if (this.clusterStorage = clusterStorage, config) {
|
|
1240
|
+
let clusters = config.clusters ?? [], initialClusterId = this.clusterStorage?.get() ?? config.initialCluster ?? "solana:mainnet", initialCluster = clusters.find((c) => c.id === initialClusterId) ?? clusters[0] ?? null;
|
|
1241
|
+
this.stateManager.updateState({
|
|
1242
|
+
cluster: initialCluster,
|
|
1243
|
+
clusters
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Set the active cluster (network)
|
|
1249
|
+
*/
|
|
1250
|
+
async setCluster(clusterId) {
|
|
1251
|
+
let state = this.getState(), previousClusterId = state.cluster?.id || null, cluster = state.clusters.find((c) => c.id === clusterId);
|
|
1252
|
+
if (!cluster)
|
|
1253
|
+
throw Errors.clusterNotFound(
|
|
1254
|
+
clusterId,
|
|
1255
|
+
state.clusters.map((c) => c.id)
|
|
1256
|
+
);
|
|
1257
|
+
this.stateManager.updateState({ cluster }, true), this.clusterStorage && (!("isAvailable" in this.clusterStorage) || typeof this.clusterStorage.isAvailable != "function" || this.clusterStorage.isAvailable() ? this.clusterStorage.set(clusterId) : this.log("Storage not available (private browsing?), skipping cluster persistence")), previousClusterId !== clusterId && this.eventEmitter.emit({
|
|
1258
|
+
type: "cluster:changed",
|
|
1259
|
+
cluster: clusterId,
|
|
1260
|
+
previousCluster: previousClusterId,
|
|
1261
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1262
|
+
}), this.log("\u{1F310} Cluster changed:", { from: previousClusterId, to: clusterId });
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Get the currently active cluster
|
|
1266
|
+
*/
|
|
1267
|
+
getCluster() {
|
|
1268
|
+
return this.getState().cluster;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Get all available clusters
|
|
1272
|
+
*/
|
|
1273
|
+
getClusters() {
|
|
1274
|
+
return this.getState().clusters;
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// src/lib/transaction/transaction-tracker.ts
|
|
1279
|
+
var TransactionTracker = class extends BaseCollaborator {
|
|
1280
|
+
constructor(stateManager, eventEmitter, maxTransactions = 20, debug = false) {
|
|
1281
|
+
super({ stateManager, eventEmitter, debug }, "TransactionTracker");
|
|
1282
|
+
chunkSMUUAKC3_js.__publicField(this, "transactions", []);
|
|
1283
|
+
chunkSMUUAKC3_js.__publicField(this, "totalTransactions", 0);
|
|
1284
|
+
chunkSMUUAKC3_js.__publicField(this, "maxTransactions");
|
|
1285
|
+
this.maxTransactions = maxTransactions;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Track a transaction for debugging and monitoring
|
|
1289
|
+
*/
|
|
1290
|
+
trackTransaction(activity) {
|
|
1291
|
+
let state = this.getState(), fullActivity = {
|
|
1292
|
+
...activity,
|
|
1293
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1294
|
+
cluster: state.cluster?.id || "solana:devnet"
|
|
1295
|
+
};
|
|
1296
|
+
this.transactions.unshift(fullActivity), this.transactions.length > this.maxTransactions && this.transactions.pop(), this.totalTransactions++, this.eventEmitter.emit({
|
|
1297
|
+
type: "transaction:tracked",
|
|
1298
|
+
signature: fullActivity.signature,
|
|
1299
|
+
status: fullActivity.status,
|
|
1300
|
+
timestamp: fullActivity.timestamp
|
|
1301
|
+
}), this.log("[Connector] Transaction tracked:", fullActivity);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Update transaction status (e.g., from pending to confirmed/failed)
|
|
1305
|
+
*/
|
|
1306
|
+
updateStatus(signature, status, error) {
|
|
1307
|
+
let tx = this.transactions.find((t) => t.signature === signature);
|
|
1308
|
+
tx && (tx.status = status, error && (tx.error = error), this.eventEmitter.emit({
|
|
1309
|
+
type: "transaction:updated",
|
|
1310
|
+
signature,
|
|
1311
|
+
status,
|
|
1312
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1313
|
+
}), this.log("[Connector] Transaction updated:", { signature, status, error }));
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Get transaction history
|
|
1317
|
+
*/
|
|
1318
|
+
getTransactions() {
|
|
1319
|
+
return [...this.transactions];
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Get total transaction count
|
|
1323
|
+
*/
|
|
1324
|
+
getTotalCount() {
|
|
1325
|
+
return this.totalTransactions;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Clear transaction history
|
|
1329
|
+
*/
|
|
1330
|
+
clearHistory() {
|
|
1331
|
+
this.transactions = [];
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
// src/lib/health/health-monitor.ts
|
|
1336
|
+
var HealthMonitor = class {
|
|
1337
|
+
constructor(stateManager, walletStorage, clusterStorage, isInitialized) {
|
|
1338
|
+
chunkSMUUAKC3_js.__publicField(this, "stateManager");
|
|
1339
|
+
chunkSMUUAKC3_js.__publicField(this, "walletStorage");
|
|
1340
|
+
chunkSMUUAKC3_js.__publicField(this, "clusterStorage");
|
|
1341
|
+
chunkSMUUAKC3_js.__publicField(this, "isInitialized");
|
|
1342
|
+
this.stateManager = stateManager, this.walletStorage = walletStorage, this.clusterStorage = clusterStorage, this.isInitialized = isInitialized ?? (() => true);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Check connector health and availability
|
|
1346
|
+
*/
|
|
1347
|
+
getHealth() {
|
|
1348
|
+
let errors = [], walletStandardAvailable = false;
|
|
1349
|
+
try {
|
|
1350
|
+
let registry2 = getWalletsRegistry();
|
|
1351
|
+
walletStandardAvailable = !!(registry2 && typeof registry2.get == "function"), walletStandardAvailable || errors.push("Wallet Standard registry not properly initialized");
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
errors.push(`Wallet Standard error: ${error instanceof Error ? error.message : "Unknown error"}`), walletStandardAvailable = false;
|
|
1354
|
+
}
|
|
1355
|
+
let storageAvailable = false;
|
|
1356
|
+
try {
|
|
1357
|
+
if (!this.walletStorage || !this.clusterStorage)
|
|
1358
|
+
errors.push("Storage adapters not configured"), storageAvailable = false;
|
|
1359
|
+
else {
|
|
1360
|
+
if ("isAvailable" in this.walletStorage && typeof this.walletStorage.isAvailable == "function")
|
|
1361
|
+
storageAvailable = this.walletStorage.isAvailable();
|
|
1362
|
+
else if (typeof window < "u")
|
|
1363
|
+
try {
|
|
1364
|
+
let testKey = "__connector_storage_test__";
|
|
1365
|
+
window.localStorage.setItem(testKey, "test"), window.localStorage.removeItem(testKey), storageAvailable = true;
|
|
1366
|
+
} catch {
|
|
1367
|
+
storageAvailable = false;
|
|
1368
|
+
}
|
|
1369
|
+
storageAvailable || errors.push("localStorage unavailable (private browsing mode or quota exceeded)");
|
|
1370
|
+
}
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
errors.push(`Storage error: ${error instanceof Error ? error.message : "Unknown error"}`), storageAvailable = false;
|
|
1373
|
+
}
|
|
1374
|
+
let state = this.stateManager.getSnapshot();
|
|
1375
|
+
return state.connected && !state.selectedWallet && errors.push("Inconsistent state: marked as connected but no wallet selected"), state.connected && !state.selectedAccount && errors.push("Inconsistent state: marked as connected but no account selected"), state.connecting && state.connected && errors.push("Inconsistent state: both connecting and connected flags are true"), {
|
|
1376
|
+
initialized: this.isInitialized(),
|
|
1377
|
+
walletStandardAvailable,
|
|
1378
|
+
storageAvailable,
|
|
1379
|
+
walletsDetected: state.wallets.length,
|
|
1380
|
+
errors,
|
|
1381
|
+
connectionState: {
|
|
1382
|
+
connected: state.connected,
|
|
1383
|
+
connecting: state.connecting,
|
|
1384
|
+
hasSelectedWallet: !!state.selectedWallet,
|
|
1385
|
+
hasSelectedAccount: !!state.selectedAccount
|
|
1386
|
+
},
|
|
1387
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
// src/lib/core/connector-client.ts
|
|
1393
|
+
var logger5 = chunkSMUUAKC3_js.createLogger("ConnectorClient"), ConnectorClient = class {
|
|
1394
|
+
constructor(config = {}) {
|
|
1395
|
+
chunkSMUUAKC3_js.__publicField(this, "stateManager");
|
|
1396
|
+
chunkSMUUAKC3_js.__publicField(this, "eventEmitter");
|
|
1397
|
+
chunkSMUUAKC3_js.__publicField(this, "walletDetector");
|
|
1398
|
+
chunkSMUUAKC3_js.__publicField(this, "connectionManager");
|
|
1399
|
+
chunkSMUUAKC3_js.__publicField(this, "autoConnector");
|
|
1400
|
+
chunkSMUUAKC3_js.__publicField(this, "clusterManager");
|
|
1401
|
+
chunkSMUUAKC3_js.__publicField(this, "transactionTracker");
|
|
1402
|
+
chunkSMUUAKC3_js.__publicField(this, "debugMetrics");
|
|
1403
|
+
chunkSMUUAKC3_js.__publicField(this, "healthMonitor");
|
|
1404
|
+
chunkSMUUAKC3_js.__publicField(this, "initialized", false);
|
|
1405
|
+
chunkSMUUAKC3_js.__publicField(this, "config");
|
|
1406
|
+
this.config = config;
|
|
1407
|
+
let initialState = {
|
|
1408
|
+
wallets: [],
|
|
1409
|
+
selectedWallet: null,
|
|
1410
|
+
connected: false,
|
|
1411
|
+
connecting: false,
|
|
1412
|
+
accounts: [],
|
|
1413
|
+
selectedAccount: null,
|
|
1414
|
+
cluster: null,
|
|
1415
|
+
clusters: []
|
|
1416
|
+
};
|
|
1417
|
+
this.stateManager = new StateManager(initialState), this.eventEmitter = new EventEmitter(config.debug), this.debugMetrics = new DebugMetrics(), this.walletDetector = new WalletDetector(this.stateManager, this.eventEmitter, config.debug ?? false), this.connectionManager = new ConnectionManager(
|
|
1418
|
+
this.stateManager,
|
|
1419
|
+
this.eventEmitter,
|
|
1420
|
+
config.storage?.wallet,
|
|
1421
|
+
config.debug ?? false
|
|
1422
|
+
), this.autoConnector = new AutoConnector(
|
|
1423
|
+
this.walletDetector,
|
|
1424
|
+
this.connectionManager,
|
|
1425
|
+
this.stateManager,
|
|
1426
|
+
config.storage?.wallet,
|
|
1427
|
+
config.debug ?? false
|
|
1428
|
+
), this.clusterManager = new ClusterManager(
|
|
1429
|
+
this.stateManager,
|
|
1430
|
+
this.eventEmitter,
|
|
1431
|
+
config.storage?.cluster,
|
|
1432
|
+
config.cluster,
|
|
1433
|
+
config.debug ?? false
|
|
1434
|
+
), this.transactionTracker = new TransactionTracker(
|
|
1435
|
+
this.stateManager,
|
|
1436
|
+
this.eventEmitter,
|
|
1437
|
+
DEFAULT_MAX_TRACKED_TRANSACTIONS,
|
|
1438
|
+
config.debug ?? false
|
|
1439
|
+
), this.healthMonitor = new HealthMonitor(
|
|
1440
|
+
this.stateManager,
|
|
1441
|
+
config.storage?.wallet,
|
|
1442
|
+
config.storage?.cluster,
|
|
1443
|
+
() => this.initialized
|
|
1444
|
+
), this.initialize();
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Initialize the connector
|
|
1448
|
+
*/
|
|
1449
|
+
initialize() {
|
|
1450
|
+
if (!(typeof window > "u") && !this.initialized)
|
|
1451
|
+
try {
|
|
1452
|
+
this.walletDetector.initialize(), this.config.autoConnect && setTimeout(() => {
|
|
1453
|
+
this.autoConnector.attemptAutoConnect().catch((err) => {
|
|
1454
|
+
this.config.debug && logger5.error("Auto-connect error", { error: err });
|
|
1455
|
+
});
|
|
1456
|
+
}, 100), this.initialized = true;
|
|
1457
|
+
} catch (e) {
|
|
1458
|
+
this.config.debug && logger5.error("Connector initialization failed", { error: e });
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
// ============================================================================
|
|
1462
|
+
// Public API - Delegates to collaborators
|
|
1463
|
+
// ============================================================================
|
|
1464
|
+
/**
|
|
1465
|
+
* Connect to a wallet by name
|
|
1466
|
+
*/
|
|
1467
|
+
async select(walletName) {
|
|
1468
|
+
let wallet = this.stateManager.getSnapshot().wallets.find((w) => w.wallet.name === walletName)?.wallet;
|
|
1469
|
+
if (!wallet) throw new Error(`Wallet ${walletName} not found`);
|
|
1470
|
+
await this.connectionManager.connect(wallet, walletName);
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Disconnect from the current wallet
|
|
1474
|
+
*/
|
|
1475
|
+
async disconnect() {
|
|
1476
|
+
await this.connectionManager.disconnect();
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Select a different account
|
|
1480
|
+
*/
|
|
1481
|
+
async selectAccount(address) {
|
|
1482
|
+
await this.connectionManager.selectAccount(address);
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Set the active cluster (network)
|
|
1486
|
+
*/
|
|
1487
|
+
async setCluster(clusterId) {
|
|
1488
|
+
await this.clusterManager.setCluster(clusterId);
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Get the currently active cluster
|
|
1492
|
+
*/
|
|
1493
|
+
getCluster() {
|
|
1494
|
+
return this.clusterManager.getCluster();
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Get all available clusters
|
|
1498
|
+
*/
|
|
1499
|
+
getClusters() {
|
|
1500
|
+
return this.clusterManager.getClusters();
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Get the RPC URL for the current cluster
|
|
1504
|
+
* @returns RPC URL or null if no cluster is selected
|
|
1505
|
+
*/
|
|
1506
|
+
getRpcUrl() {
|
|
1507
|
+
let cluster = this.clusterManager.getCluster();
|
|
1508
|
+
if (!cluster) return null;
|
|
1509
|
+
try {
|
|
1510
|
+
return getClusterRpcUrl(cluster);
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
return this.config.debug && logger5.error("Failed to get RPC URL", { error }), null;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Subscribe to state changes
|
|
1517
|
+
*/
|
|
1518
|
+
subscribe(listener) {
|
|
1519
|
+
return this.stateManager.subscribe(listener);
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Get current state snapshot
|
|
1523
|
+
*/
|
|
1524
|
+
getSnapshot() {
|
|
1525
|
+
return this.stateManager.getSnapshot();
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Reset all storage to initial values
|
|
1529
|
+
* Useful for "logout", "forget this device", or clearing user data
|
|
1530
|
+
*
|
|
1531
|
+
* This will:
|
|
1532
|
+
* - Clear saved wallet name
|
|
1533
|
+
* - Clear saved account address
|
|
1534
|
+
* - Reset cluster to initial value (does not clear)
|
|
1535
|
+
*
|
|
1536
|
+
* Note: This does NOT disconnect the wallet. Call disconnect() separately if needed.
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* ```ts
|
|
1540
|
+
* // Complete logout flow
|
|
1541
|
+
* await client.disconnect();
|
|
1542
|
+
* client.resetStorage();
|
|
1543
|
+
* ```
|
|
1544
|
+
*/
|
|
1545
|
+
resetStorage() {
|
|
1546
|
+
this.config.debug && logger5.info("Resetting all storage to initial values");
|
|
1547
|
+
let storageKeys = ["account", "wallet", "cluster"];
|
|
1548
|
+
for (let key of storageKeys) {
|
|
1549
|
+
let storage = this.config.storage?.[key];
|
|
1550
|
+
if (storage && "reset" in storage && typeof storage.reset == "function")
|
|
1551
|
+
try {
|
|
1552
|
+
storage.reset(), this.config.debug && logger5.debug("Reset storage", { key });
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
this.config.debug && logger5.error("Failed to reset storage", { key, error });
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
this.eventEmitter.emit({
|
|
1558
|
+
type: "storage:reset",
|
|
1559
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Subscribe to connector events
|
|
1564
|
+
*/
|
|
1565
|
+
on(listener) {
|
|
1566
|
+
return this.eventEmitter.on(listener);
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Remove a specific event listener
|
|
1570
|
+
*/
|
|
1571
|
+
off(listener) {
|
|
1572
|
+
this.eventEmitter.off(listener);
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Remove all event listeners
|
|
1576
|
+
*/
|
|
1577
|
+
offAll() {
|
|
1578
|
+
this.eventEmitter.offAll();
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Emit a connector event
|
|
1582
|
+
* Internal method used by transaction signer and other components
|
|
1583
|
+
* @internal
|
|
1584
|
+
*/
|
|
1585
|
+
emitEvent(event) {
|
|
1586
|
+
this.eventEmitter.emit(event);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Track a transaction for debugging and monitoring
|
|
1590
|
+
*/
|
|
1591
|
+
trackTransaction(activity) {
|
|
1592
|
+
this.transactionTracker.trackTransaction(activity);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Update transaction status
|
|
1596
|
+
*/
|
|
1597
|
+
updateTransactionStatus(signature, status, error) {
|
|
1598
|
+
this.transactionTracker.updateStatus(signature, status, error);
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Clear transaction history
|
|
1602
|
+
*/
|
|
1603
|
+
clearTransactionHistory() {
|
|
1604
|
+
this.transactionTracker.clearHistory();
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Get connector health and diagnostics
|
|
1608
|
+
*/
|
|
1609
|
+
getHealth() {
|
|
1610
|
+
return this.healthMonitor.getHealth();
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Get performance and debug metrics
|
|
1614
|
+
*/
|
|
1615
|
+
getDebugMetrics() {
|
|
1616
|
+
this.stateManager.getSnapshot();
|
|
1617
|
+
return this.debugMetrics.updateListenerCounts(this.eventEmitter.getListenerCount(), 0), this.debugMetrics.getMetrics();
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Get debug state including transactions
|
|
1621
|
+
*/
|
|
1622
|
+
getDebugState() {
|
|
1623
|
+
return {
|
|
1624
|
+
...this.getDebugMetrics(),
|
|
1625
|
+
transactions: this.transactionTracker.getTransactions(),
|
|
1626
|
+
totalTransactions: this.transactionTracker.getTotalCount()
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Reset debug metrics
|
|
1631
|
+
*/
|
|
1632
|
+
resetDebugMetrics() {
|
|
1633
|
+
this.debugMetrics.resetMetrics();
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Cleanup resources
|
|
1637
|
+
*/
|
|
1638
|
+
destroy() {
|
|
1639
|
+
this.connectionManager.disconnect().catch(() => {
|
|
1640
|
+
}), this.walletDetector.destroy(), this.eventEmitter.offAll(), this.stateManager.clear();
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
var logger6 = chunkSMUUAKC3_js.createLogger("ErrorBoundary"), WalletErrorType = /* @__PURE__ */ ((WalletErrorType2) => (WalletErrorType2.CONNECTION_FAILED = "CONNECTION_FAILED", WalletErrorType2.TRANSACTION_FAILED = "TRANSACTION_FAILED", WalletErrorType2.NETWORK_ERROR = "NETWORK_ERROR", WalletErrorType2.WALLET_NOT_FOUND = "WALLET_NOT_FOUND", WalletErrorType2.USER_REJECTED = "USER_REJECTED", WalletErrorType2.INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS", WalletErrorType2.UNKNOWN_ERROR = "UNKNOWN_ERROR", WalletErrorType2))(WalletErrorType || {}), ErrorLogger = class {
|
|
1644
|
+
static log(error, errorInfo, context) {
|
|
1645
|
+
if (process.env.NODE_ENV === "development" && logger6.error(error.message, {
|
|
1646
|
+
error,
|
|
1647
|
+
errorInfo,
|
|
1648
|
+
context
|
|
1649
|
+
}), process.env.NODE_ENV === "production" && typeof window < "u")
|
|
1650
|
+
try {
|
|
1651
|
+
let gtag = window.gtag;
|
|
1652
|
+
typeof gtag == "function" && gtag("event", "exception", {
|
|
1653
|
+
description: error.message,
|
|
1654
|
+
fatal: false,
|
|
1655
|
+
custom_map: { error_type: "wallet_error", ...context }
|
|
1656
|
+
});
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
function classifyError(error) {
|
|
1662
|
+
if (isConnectorError(error))
|
|
1663
|
+
return {
|
|
1664
|
+
...error,
|
|
1665
|
+
type: {
|
|
1666
|
+
WALLET_NOT_CONNECTED: "CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
1667
|
+
WALLET_NOT_FOUND: "WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1668
|
+
CONNECTION_FAILED: "CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
1669
|
+
USER_REJECTED: "USER_REJECTED" /* USER_REJECTED */,
|
|
1670
|
+
RPC_ERROR: "NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1671
|
+
NETWORK_TIMEOUT: "NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1672
|
+
SIGNING_FAILED: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
1673
|
+
SEND_FAILED: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */
|
|
1674
|
+
}[error.code] || "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
1675
|
+
recoverable: error.recoverable,
|
|
1676
|
+
context: error.context
|
|
1677
|
+
};
|
|
1678
|
+
let walletError = error;
|
|
1679
|
+
if (walletError.type) return walletError;
|
|
1680
|
+
let type = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, recoverable = false;
|
|
1681
|
+
return error.message.includes("User rejected") || error.message.includes("User denied") ? (type = "USER_REJECTED" /* USER_REJECTED */, recoverable = true) : error.message.includes("Insufficient funds") ? (type = "INSUFFICIENT_FUNDS" /* INSUFFICIENT_FUNDS */, recoverable = false) : error.message.includes("Network") || error.message.includes("fetch") ? (type = "NETWORK_ERROR" /* NETWORK_ERROR */, recoverable = true) : error.message.includes("Wallet not found") || error.message.includes("not installed") ? (type = "WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */, recoverable = true) : (error.message.includes("Failed to connect") || error.message.includes("Connection")) && (type = "CONNECTION_FAILED" /* CONNECTION_FAILED */, recoverable = true), {
|
|
1682
|
+
...error,
|
|
1683
|
+
type,
|
|
1684
|
+
recoverable,
|
|
1685
|
+
context: { originalMessage: error.message }
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
var ConnectorErrorBoundary = class extends react.Component {
|
|
1689
|
+
constructor(props) {
|
|
1690
|
+
super(props);
|
|
1691
|
+
chunkSMUUAKC3_js.__publicField(this, "retryTimeouts", /* @__PURE__ */ new Set());
|
|
1692
|
+
chunkSMUUAKC3_js.__publicField(this, "retry", () => {
|
|
1693
|
+
let { maxRetries = 3 } = this.props;
|
|
1694
|
+
this.state.retryCount >= maxRetries || this.setState((prevState) => ({
|
|
1695
|
+
hasError: false,
|
|
1696
|
+
error: null,
|
|
1697
|
+
errorInfo: null,
|
|
1698
|
+
retryCount: prevState.retryCount + 1
|
|
1699
|
+
}));
|
|
1700
|
+
});
|
|
1701
|
+
this.state = {
|
|
1702
|
+
hasError: false,
|
|
1703
|
+
error: null,
|
|
1704
|
+
errorInfo: null,
|
|
1705
|
+
errorId: "",
|
|
1706
|
+
retryCount: 0
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
static getDerivedStateFromError(error) {
|
|
1710
|
+
return {
|
|
1711
|
+
hasError: true,
|
|
1712
|
+
error,
|
|
1713
|
+
errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
componentDidCatch(error, errorInfo) {
|
|
1717
|
+
this.setState({ errorInfo }), ErrorLogger.log(error, errorInfo, {
|
|
1718
|
+
retryCount: this.state.retryCount,
|
|
1719
|
+
errorId: this.state.errorId,
|
|
1720
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1721
|
+
}), this.props.onError?.(error, errorInfo);
|
|
1722
|
+
}
|
|
1723
|
+
componentWillUnmount() {
|
|
1724
|
+
this.retryTimeouts.forEach((timeout) => clearTimeout(timeout));
|
|
1725
|
+
}
|
|
1726
|
+
render() {
|
|
1727
|
+
if (this.state.hasError && this.state.error) {
|
|
1728
|
+
let walletError = classifyError(this.state.error);
|
|
1729
|
+
return this.props.fallback ? this.props.fallback(walletError, this.retry) : /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorFallback, { error: walletError, onRetry: this.retry });
|
|
1730
|
+
}
|
|
1731
|
+
return this.props.children;
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
function DefaultErrorFallback({ error, onRetry }) {
|
|
1735
|
+
let [isPending, startTransition] = react.useTransition(), [isRetrying, setIsRetrying] = react.useState(false), handleRetry = react.useCallback(() => {
|
|
1736
|
+
setIsRetrying(true), startTransition(() => {
|
|
1737
|
+
setTimeout(() => {
|
|
1738
|
+
onRetry(), setIsRetrying(false);
|
|
1739
|
+
}, 500);
|
|
1740
|
+
});
|
|
1741
|
+
}, [onRetry]), { title, message, actionText, showRetry } = react.useMemo(() => {
|
|
1742
|
+
switch (error.type) {
|
|
1743
|
+
case "USER_REJECTED" /* USER_REJECTED */:
|
|
1744
|
+
return {
|
|
1745
|
+
title: "Transaction Cancelled",
|
|
1746
|
+
message: "You cancelled the transaction. No problem!",
|
|
1747
|
+
actionText: "Try Again",
|
|
1748
|
+
showRetry: true
|
|
1749
|
+
};
|
|
1750
|
+
case "WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */:
|
|
1751
|
+
return {
|
|
1752
|
+
title: "Wallet Not Found",
|
|
1753
|
+
message: "Please install a supported Solana wallet to continue.",
|
|
1754
|
+
actionText: "Check Wallets",
|
|
1755
|
+
showRetry: true
|
|
1756
|
+
};
|
|
1757
|
+
case "NETWORK_ERROR" /* NETWORK_ERROR */:
|
|
1758
|
+
return {
|
|
1759
|
+
title: "Network Error",
|
|
1760
|
+
message: "Having trouble connecting. Please check your internet connection.",
|
|
1761
|
+
actionText: "Retry",
|
|
1762
|
+
showRetry: true
|
|
1763
|
+
};
|
|
1764
|
+
case "INSUFFICIENT_FUNDS" /* INSUFFICIENT_FUNDS */:
|
|
1765
|
+
return {
|
|
1766
|
+
title: "Insufficient Funds",
|
|
1767
|
+
message: "You don't have enough SOL for this transaction.",
|
|
1768
|
+
actionText: "Add Funds",
|
|
1769
|
+
showRetry: false
|
|
1770
|
+
};
|
|
1771
|
+
default:
|
|
1772
|
+
return {
|
|
1773
|
+
title: "Something went wrong",
|
|
1774
|
+
message: "An unexpected error occurred. Please try again.",
|
|
1775
|
+
actionText: "Retry",
|
|
1776
|
+
showRetry: error.recoverable
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
}, [error.type, error.recoverable]);
|
|
1780
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1781
|
+
"div",
|
|
1782
|
+
{
|
|
1783
|
+
style: {
|
|
1784
|
+
display: "flex",
|
|
1785
|
+
flexDirection: "column",
|
|
1786
|
+
alignItems: "center",
|
|
1787
|
+
justifyContent: "center",
|
|
1788
|
+
padding: "2rem",
|
|
1789
|
+
textAlign: "center",
|
|
1790
|
+
borderRadius: "12px",
|
|
1791
|
+
border: "1px solid #e5e7eb",
|
|
1792
|
+
backgroundColor: "#fafafa",
|
|
1793
|
+
maxWidth: "400px",
|
|
1794
|
+
margin: "0 auto"
|
|
1795
|
+
},
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1798
|
+
"div",
|
|
1799
|
+
{
|
|
1800
|
+
style: {
|
|
1801
|
+
width: "48px",
|
|
1802
|
+
height: "48px",
|
|
1803
|
+
borderRadius: "50%",
|
|
1804
|
+
backgroundColor: "#fee2e2",
|
|
1805
|
+
display: "flex",
|
|
1806
|
+
alignItems: "center",
|
|
1807
|
+
justifyContent: "center",
|
|
1808
|
+
marginBottom: "1rem"
|
|
1809
|
+
},
|
|
1810
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "#dc2626", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" }) })
|
|
1811
|
+
}
|
|
1812
|
+
),
|
|
1813
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1814
|
+
"h3",
|
|
1815
|
+
{
|
|
1816
|
+
style: {
|
|
1817
|
+
margin: "0 0 0.5rem 0",
|
|
1818
|
+
fontSize: "1.125rem",
|
|
1819
|
+
fontWeight: "600",
|
|
1820
|
+
color: "#111827"
|
|
1821
|
+
},
|
|
1822
|
+
children: title
|
|
1823
|
+
}
|
|
1824
|
+
),
|
|
1825
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1826
|
+
"p",
|
|
1827
|
+
{
|
|
1828
|
+
style: {
|
|
1829
|
+
margin: "0 0 1.5rem 0",
|
|
1830
|
+
fontSize: "0.875rem",
|
|
1831
|
+
color: "#6b7280",
|
|
1832
|
+
lineHeight: "1.5"
|
|
1833
|
+
},
|
|
1834
|
+
children: message
|
|
1835
|
+
}
|
|
1836
|
+
),
|
|
1837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.75rem", flexWrap: "wrap" }, children: [
|
|
1838
|
+
showRetry && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1839
|
+
"button",
|
|
1840
|
+
{
|
|
1841
|
+
onClick: handleRetry,
|
|
1842
|
+
disabled: isPending || isRetrying,
|
|
1843
|
+
style: {
|
|
1844
|
+
padding: "0.5rem 1rem",
|
|
1845
|
+
backgroundColor: "#3b82f6",
|
|
1846
|
+
color: "white",
|
|
1847
|
+
border: "none",
|
|
1848
|
+
borderRadius: "6px",
|
|
1849
|
+
fontSize: "0.875rem",
|
|
1850
|
+
fontWeight: "500",
|
|
1851
|
+
cursor: isPending || isRetrying ? "wait" : "pointer",
|
|
1852
|
+
opacity: isPending || isRetrying ? 0.7 : 1,
|
|
1853
|
+
transition: "all 0.2s"
|
|
1854
|
+
},
|
|
1855
|
+
children: isRetrying ? "Retrying..." : actionText
|
|
1856
|
+
}
|
|
1857
|
+
),
|
|
1858
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1859
|
+
"button",
|
|
1860
|
+
{
|
|
1861
|
+
onClick: () => window.location.reload(),
|
|
1862
|
+
style: {
|
|
1863
|
+
padding: "0.5rem 1rem",
|
|
1864
|
+
backgroundColor: "transparent",
|
|
1865
|
+
color: "#6b7280",
|
|
1866
|
+
border: "1px solid #d1d5db",
|
|
1867
|
+
borderRadius: "6px",
|
|
1868
|
+
fontSize: "0.875rem",
|
|
1869
|
+
fontWeight: "500",
|
|
1870
|
+
cursor: "pointer",
|
|
1871
|
+
transition: "all 0.2s"
|
|
1872
|
+
},
|
|
1873
|
+
children: "Refresh Page"
|
|
1874
|
+
}
|
|
1875
|
+
)
|
|
1876
|
+
] }),
|
|
1877
|
+
process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1878
|
+
"details",
|
|
1879
|
+
{
|
|
1880
|
+
style: {
|
|
1881
|
+
marginTop: "1rem",
|
|
1882
|
+
fontSize: "0.75rem",
|
|
1883
|
+
color: "#6b7280",
|
|
1884
|
+
width: "100%"
|
|
1885
|
+
},
|
|
1886
|
+
children: [
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsx("summary", { style: { cursor: "pointer", marginBottom: "0.5rem" }, children: "Error Details" }),
|
|
1888
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1889
|
+
"pre",
|
|
1890
|
+
{
|
|
1891
|
+
style: {
|
|
1892
|
+
whiteSpace: "pre-wrap",
|
|
1893
|
+
wordBreak: "break-all",
|
|
1894
|
+
backgroundColor: "#f3f4f6",
|
|
1895
|
+
padding: "0.5rem",
|
|
1896
|
+
borderRadius: "4px",
|
|
1897
|
+
overflow: "auto",
|
|
1898
|
+
maxHeight: "200px"
|
|
1899
|
+
},
|
|
1900
|
+
children: error.message
|
|
1901
|
+
}
|
|
1902
|
+
)
|
|
1903
|
+
]
|
|
1904
|
+
}
|
|
1905
|
+
)
|
|
1906
|
+
]
|
|
1907
|
+
}
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
function withErrorBoundary(Component2, errorBoundaryProps) {
|
|
1911
|
+
let WrappedComponent = (props) => /* @__PURE__ */ jsxRuntime.jsx(ConnectorErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props }) });
|
|
1912
|
+
return WrappedComponent.displayName = `withErrorBoundary(${Component2.displayName || Component2.name})`, WrappedComponent;
|
|
1913
|
+
}
|
|
1914
|
+
var logger7 = chunkSMUUAKC3_js.createLogger("Polyfills"), installed = false;
|
|
1915
|
+
function installPolyfills() {
|
|
1916
|
+
if (!(installed || typeof window > "u"))
|
|
1917
|
+
try {
|
|
1918
|
+
webcryptoEd25519Polyfill.install(), installed = true, process.env.NODE_ENV === "development" && logger7 && logger7.info("Browser compatibility polyfills installed");
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
logger7 && logger7.warn("Failed to install polyfills", { error }), installed = true;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
function isPolyfillInstalled() {
|
|
1924
|
+
return installed;
|
|
1925
|
+
}
|
|
1926
|
+
function isCryptoAvailable() {
|
|
1927
|
+
if (typeof window > "u") return false;
|
|
1928
|
+
try {
|
|
1929
|
+
return !!(window.crypto && window.crypto.subtle && typeof window.crypto.subtle.sign == "function");
|
|
1930
|
+
} catch {
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function getPolyfillStatus() {
|
|
1935
|
+
return {
|
|
1936
|
+
installed,
|
|
1937
|
+
cryptoAvailable: isCryptoAvailable(),
|
|
1938
|
+
environment: typeof window < "u" ? "browser" : "server"
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function formatAddress(address, options = {}) {
|
|
1942
|
+
let { length = 4, separator = "..." } = options;
|
|
1943
|
+
return !address || address.length <= length * 2 + separator.length ? address : `${address.slice(0, length)}${separator}${address.slice(-length)}`;
|
|
1944
|
+
}
|
|
1945
|
+
function formatSOL(lamports, options = {}) {
|
|
1946
|
+
let { decimals = 4, suffix = true, fast = false } = options;
|
|
1947
|
+
if (fast && typeof lamports == "number") {
|
|
1948
|
+
let formatted2 = (lamports / gill.LAMPORTS_PER_SOL).toFixed(decimals);
|
|
1949
|
+
return suffix ? `${formatted2} SOL` : formatted2;
|
|
1950
|
+
}
|
|
1951
|
+
let lamportsBigInt = typeof lamports == "bigint" ? lamports : BigInt(lamports), formatted = (Number(lamportsBigInt) / gill.LAMPORTS_PER_SOL).toFixed(decimals);
|
|
1952
|
+
return suffix ? `${formatted} SOL` : formatted;
|
|
1953
|
+
}
|
|
1954
|
+
function formatNumber(value, options = {}) {
|
|
1955
|
+
let { decimals, locale = "en-US" } = options;
|
|
1956
|
+
return new Intl.NumberFormat(locale, {
|
|
1957
|
+
minimumFractionDigits: decimals,
|
|
1958
|
+
maximumFractionDigits: decimals
|
|
1959
|
+
}).format(value);
|
|
1960
|
+
}
|
|
1961
|
+
function truncate(text, maxLength, position = "middle") {
|
|
1962
|
+
if (!text || text.length <= maxLength) return text;
|
|
1963
|
+
if (position === "end")
|
|
1964
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
1965
|
+
let half = Math.floor((maxLength - 3) / 2);
|
|
1966
|
+
return text.slice(0, half) + "..." + text.slice(-half);
|
|
1967
|
+
}
|
|
1968
|
+
function formatTokenAmount(amount, decimals, options = {}) {
|
|
1969
|
+
let value = Number(amount) / Math.pow(10, decimals), minDecimals = options.minimumDecimals ?? 0, maxDecimals = options.maximumDecimals ?? decimals;
|
|
1970
|
+
return value.toLocaleString("en-US", {
|
|
1971
|
+
minimumFractionDigits: minDecimals,
|
|
1972
|
+
maximumFractionDigits: maxDecimals
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
var ClipboardErrorType = /* @__PURE__ */ ((ClipboardErrorType2) => (ClipboardErrorType2.NOT_SUPPORTED = "not_supported", ClipboardErrorType2.PERMISSION_DENIED = "permission_denied", ClipboardErrorType2.SSR = "ssr", ClipboardErrorType2.EMPTY_VALUE = "empty_value", ClipboardErrorType2.INVALID_VALUE = "invalid_value", ClipboardErrorType2.UNKNOWN = "unknown", ClipboardErrorType2))(ClipboardErrorType || {});
|
|
1976
|
+
function isClipboardAvailable() {
|
|
1977
|
+
if (typeof window > "u" || typeof document > "u")
|
|
1978
|
+
return { modern: false, fallback: false, available: false };
|
|
1979
|
+
let modern = typeof navigator?.clipboard?.writeText == "function", fallback = typeof document.execCommand == "function";
|
|
1980
|
+
return {
|
|
1981
|
+
modern,
|
|
1982
|
+
fallback,
|
|
1983
|
+
available: modern || fallback
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
function validateAddress(address) {
|
|
1987
|
+
try {
|
|
1988
|
+
return gill.isAddress(address);
|
|
1989
|
+
} catch {
|
|
1990
|
+
return false;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
function validateSignature(signature) {
|
|
1994
|
+
return !signature || typeof signature != "string" || signature.length < 87 || signature.length > 88 ? false : /^[1-9A-HJ-NP-Za-km-z]+$/.test(signature);
|
|
1995
|
+
}
|
|
1996
|
+
function formatValue(value, options) {
|
|
1997
|
+
let { format = "full", customFormatter, shortFormatChars = 4 } = options;
|
|
1998
|
+
if (format === "custom" && customFormatter)
|
|
1999
|
+
try {
|
|
2000
|
+
return customFormatter(value);
|
|
2001
|
+
} catch {
|
|
2002
|
+
return value;
|
|
2003
|
+
}
|
|
2004
|
+
if (format === "short") {
|
|
2005
|
+
if (value.length > 32 && value.length < 50)
|
|
2006
|
+
return formatAddress(value, { length: shortFormatChars });
|
|
2007
|
+
if (value.length > shortFormatChars * 2)
|
|
2008
|
+
return `${value.slice(0, shortFormatChars)}...${value.slice(-shortFormatChars)}`;
|
|
2009
|
+
}
|
|
2010
|
+
return value;
|
|
2011
|
+
}
|
|
2012
|
+
function copyUsingFallback(text) {
|
|
2013
|
+
try {
|
|
2014
|
+
let textArea = document.createElement("textarea");
|
|
2015
|
+
textArea.value = text, textArea.style.position = "fixed", textArea.style.left = "-999999px", textArea.style.top = "-999999px", document.body.appendChild(textArea), textArea.focus(), textArea.select();
|
|
2016
|
+
let successful = document.execCommand("copy");
|
|
2017
|
+
return document.body.removeChild(textArea), successful;
|
|
2018
|
+
} catch {
|
|
2019
|
+
return false;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
async function copyToClipboard(text, options = {}) {
|
|
2023
|
+
let { onSuccess, onError, validate, validateType = "none", useFallback = true } = options;
|
|
2024
|
+
if (!text || typeof text != "string" || text.trim() === "") {
|
|
2025
|
+
let error2 = "empty_value" /* EMPTY_VALUE */, message2 = "No text provided to copy";
|
|
2026
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2027
|
+
}
|
|
2028
|
+
if (typeof window > "u" || typeof navigator > "u") {
|
|
2029
|
+
let error2 = "ssr" /* SSR */, message2 = "Clipboard not available in server-side rendering context";
|
|
2030
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2031
|
+
}
|
|
2032
|
+
if (validate && !validate(text)) {
|
|
2033
|
+
let error2 = "invalid_value" /* INVALID_VALUE */, message2 = "Value failed custom validation";
|
|
2034
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2035
|
+
}
|
|
2036
|
+
if (validateType === "address" && !validateAddress(text)) {
|
|
2037
|
+
let error2 = "invalid_value" /* INVALID_VALUE */, message2 = "Invalid Solana address format";
|
|
2038
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2039
|
+
}
|
|
2040
|
+
if (validateType === "signature" && !validateSignature(text)) {
|
|
2041
|
+
let error2 = "invalid_value" /* INVALID_VALUE */, message2 = "Invalid transaction signature format";
|
|
2042
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2043
|
+
}
|
|
2044
|
+
let formattedValue = formatValue(text, options), availability = isClipboardAvailable();
|
|
2045
|
+
if (!availability.available) {
|
|
2046
|
+
let error2 = "not_supported" /* NOT_SUPPORTED */, message2 = "Clipboard API not supported in this browser";
|
|
2047
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2048
|
+
}
|
|
2049
|
+
if (availability.modern)
|
|
2050
|
+
try {
|
|
2051
|
+
return await navigator.clipboard.writeText(formattedValue), onSuccess?.(), { success: true, copiedValue: formattedValue };
|
|
2052
|
+
} catch (err) {
|
|
2053
|
+
if (err instanceof Error && (err.name === "NotAllowedError" || err.message.toLowerCase().includes("permission"))) {
|
|
2054
|
+
let error3 = "permission_denied" /* PERMISSION_DENIED */, message3 = "Clipboard permission denied by user or browser";
|
|
2055
|
+
return onError?.(error3, message3), { success: false, error: error3, errorMessage: message3 };
|
|
2056
|
+
}
|
|
2057
|
+
if (useFallback && availability.fallback && copyUsingFallback(formattedValue))
|
|
2058
|
+
return onSuccess?.(), { success: true, usedFallback: true, copiedValue: formattedValue };
|
|
2059
|
+
let error2 = "unknown" /* UNKNOWN */, message2 = err instanceof Error ? err.message : "Failed to copy to clipboard";
|
|
2060
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2061
|
+
}
|
|
2062
|
+
if (useFallback && availability.fallback) {
|
|
2063
|
+
if (copyUsingFallback(formattedValue))
|
|
2064
|
+
return onSuccess?.(), { success: true, usedFallback: true, copiedValue: formattedValue };
|
|
2065
|
+
let error2 = "unknown" /* UNKNOWN */, message2 = "Failed to copy using fallback method";
|
|
2066
|
+
return onError?.(error2, message2), { success: false, error: error2, errorMessage: message2 };
|
|
2067
|
+
}
|
|
2068
|
+
let error = "not_supported" /* NOT_SUPPORTED */, message = "No clipboard method available";
|
|
2069
|
+
return onError?.(error, message), { success: false, error, errorMessage: message };
|
|
2070
|
+
}
|
|
2071
|
+
async function copyAddressToClipboard(address, options) {
|
|
2072
|
+
return copyToClipboard(address, {
|
|
2073
|
+
...options,
|
|
2074
|
+
validateType: "address"
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
async function copySignatureToClipboard(signature, options) {
|
|
2078
|
+
return copyToClipboard(signature, {
|
|
2079
|
+
...options,
|
|
2080
|
+
validateType: "signature"
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// src/lib/transaction/transaction-validator.ts
|
|
2085
|
+
var logger8 = chunkSMUUAKC3_js.createLogger("TransactionValidator"), MAX_TRANSACTION_SIZE = 1232, MIN_TRANSACTION_SIZE = 64, TransactionValidator = class {
|
|
2086
|
+
/**
|
|
2087
|
+
* Validate a transaction before signing
|
|
2088
|
+
*
|
|
2089
|
+
* @param transaction - The transaction to validate
|
|
2090
|
+
* @param options - Validation options
|
|
2091
|
+
* @returns Validation result with errors and warnings
|
|
2092
|
+
*/
|
|
2093
|
+
static validate(transaction, options = {}) {
|
|
2094
|
+
let {
|
|
2095
|
+
maxSize = MAX_TRANSACTION_SIZE,
|
|
2096
|
+
minSize = MIN_TRANSACTION_SIZE,
|
|
2097
|
+
checkDuplicateSignatures = true,
|
|
2098
|
+
strict = false
|
|
2099
|
+
} = options, errors = [], warnings = [], size;
|
|
2100
|
+
if (!transaction)
|
|
2101
|
+
return errors.push("Transaction is null or undefined"), { valid: false, errors, warnings };
|
|
2102
|
+
try {
|
|
2103
|
+
let serialized;
|
|
2104
|
+
if (typeof transaction.serialize == "function")
|
|
2105
|
+
try {
|
|
2106
|
+
serialized = transaction.serialize();
|
|
2107
|
+
} catch (serializeError) {
|
|
2108
|
+
logger8.debug("Transaction not yet serializable (may need signing)", {
|
|
2109
|
+
error: serializeError instanceof Error ? serializeError.message : String(serializeError)
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
else if (transaction instanceof Uint8Array)
|
|
2113
|
+
serialized = transaction;
|
|
2114
|
+
else
|
|
2115
|
+
return errors.push(
|
|
2116
|
+
"Transaction type not recognized - must be a Transaction object with serialize() or Uint8Array"
|
|
2117
|
+
), { valid: false, errors, warnings };
|
|
2118
|
+
serialized && (size = serialized.length, size > maxSize && (errors.push(`Transaction too large: ${size} bytes (max ${maxSize} bytes)`), logger8.warn("Transaction exceeds maximum size", { size, maxSize })), size < minSize && warnings.push(`Transaction is very small: ${size} bytes (min recommended ${minSize} bytes)`), size === 0 && errors.push("Transaction is empty (0 bytes)"), this.hasSuspiciousPattern(serialized) && warnings.push("Transaction contains unusual patterns - please review carefully"));
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
2121
|
+
errors.push(`Transaction validation failed: ${errorMessage}`), logger8.error("Validation error", { error: errorMessage });
|
|
2122
|
+
}
|
|
2123
|
+
if (checkDuplicateSignatures && typeof transaction == "object" && "signatures" in transaction) {
|
|
2124
|
+
let signatures = transaction.signatures;
|
|
2125
|
+
Array.isArray(signatures) && new Set(
|
|
2126
|
+
signatures.map((sig) => {
|
|
2127
|
+
let pubKey = sig?.publicKey;
|
|
2128
|
+
return pubKey ? String(pubKey) : null;
|
|
2129
|
+
}).filter(Boolean)
|
|
2130
|
+
).size < signatures.length && warnings.push("Transaction contains duplicate signers");
|
|
2131
|
+
}
|
|
2132
|
+
strict && warnings.length > 0 && (errors.push(...warnings.map((w) => `Strict mode: ${w}`)), warnings.length = 0);
|
|
2133
|
+
let valid = errors.length === 0;
|
|
2134
|
+
return valid ? warnings.length > 0 ? logger8.debug("Transaction validation passed with warnings", { warnings, size }) : logger8.debug("Transaction validation passed", { size }) : logger8.warn("Transaction validation failed", { errors, size }), {
|
|
2135
|
+
valid,
|
|
2136
|
+
errors,
|
|
2137
|
+
warnings,
|
|
2138
|
+
size
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Check for suspicious patterns in serialized transaction
|
|
2143
|
+
* This is a heuristic check to detect potentially malicious transactions
|
|
2144
|
+
*
|
|
2145
|
+
* @param serialized - Serialized transaction bytes
|
|
2146
|
+
* @returns True if suspicious patterns detected
|
|
2147
|
+
*/
|
|
2148
|
+
static hasSuspiciousPattern(serialized) {
|
|
2149
|
+
if (serialized.every((byte) => byte === 0) || serialized.every((byte) => byte === 255)) return true;
|
|
2150
|
+
let byteCounts = /* @__PURE__ */ new Map();
|
|
2151
|
+
for (let byte of serialized)
|
|
2152
|
+
byteCounts.set(byte, (byteCounts.get(byte) || 0) + 1);
|
|
2153
|
+
return Math.max(...Array.from(byteCounts.values())) / serialized.length > 0.5;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Quick validation check - returns true if valid, throws on error
|
|
2157
|
+
* Useful for inline validation
|
|
2158
|
+
*
|
|
2159
|
+
* @param transaction - Transaction to validate
|
|
2160
|
+
* @param options - Validation options
|
|
2161
|
+
* @throws Error if validation fails
|
|
2162
|
+
*
|
|
2163
|
+
* @example
|
|
2164
|
+
* ```ts
|
|
2165
|
+
* TransactionValidator.assertValid(transaction);
|
|
2166
|
+
* // Continues if valid, throws if invalid
|
|
2167
|
+
* await signer.signTransaction(transaction);
|
|
2168
|
+
* ```
|
|
2169
|
+
*/
|
|
2170
|
+
static assertValid(transaction, options) {
|
|
2171
|
+
let result = this.validate(transaction, options);
|
|
2172
|
+
if (!result.valid)
|
|
2173
|
+
throw new Error(`Transaction validation failed: ${result.errors.join(", ")}`);
|
|
2174
|
+
result.warnings.length > 0 && logger8.warn("Transaction validation warnings", { warnings: result.warnings });
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Batch validate multiple transactions
|
|
2178
|
+
* More efficient than validating one-by-one
|
|
2179
|
+
*
|
|
2180
|
+
* @param transactions - Array of transactions to validate
|
|
2181
|
+
* @param options - Validation options
|
|
2182
|
+
* @returns Array of validation results
|
|
2183
|
+
*/
|
|
2184
|
+
static validateBatch(transactions, options) {
|
|
2185
|
+
return transactions.map((tx, index) => (logger8.debug(`Validating transaction ${index + 1}/${transactions.length}`), this.validate(tx, options)));
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
|
|
2189
|
+
// src/lib/transaction/transaction-signer.ts
|
|
2190
|
+
var logger9 = chunkSMUUAKC3_js.createLogger("TransactionSigner");
|
|
2191
|
+
function createTransactionSigner(config) {
|
|
2192
|
+
let { wallet, account, cluster, eventEmitter } = config;
|
|
2193
|
+
if (!wallet || !account)
|
|
2194
|
+
return null;
|
|
2195
|
+
let features = wallet.features, address = account.address, capabilities = {
|
|
2196
|
+
canSign: !!features["solana:signTransaction"],
|
|
2197
|
+
canSend: !!features["solana:signAndSendTransaction"],
|
|
2198
|
+
canSignMessage: !!features["solana:signMessage"],
|
|
2199
|
+
supportsBatchSigning: !!features["solana:signAllTransactions"]
|
|
2200
|
+
}, signer = {
|
|
2201
|
+
address,
|
|
2202
|
+
async signTransaction(transaction) {
|
|
2203
|
+
if (!capabilities.canSign)
|
|
2204
|
+
throw Errors.featureNotSupported("transaction signing");
|
|
2205
|
+
let validation = TransactionValidator.validate(transaction);
|
|
2206
|
+
if (!validation.valid)
|
|
2207
|
+
throw logger9.error("Transaction validation failed", { errors: validation.errors }), Errors.invalidTransaction(validation.errors.join(", "));
|
|
2208
|
+
validation.warnings.length > 0 && logger9.warn("Transaction validation warnings", { warnings: validation.warnings });
|
|
2209
|
+
try {
|
|
2210
|
+
let signFeature = features["solana:signTransaction"], { serialized, wasWeb3js } = chunkSMUUAKC3_js.prepareTransactionForWallet(transaction);
|
|
2211
|
+
logger9.debug("Signing transaction", {
|
|
2212
|
+
wasWeb3js,
|
|
2213
|
+
serializedLength: serialized.length,
|
|
2214
|
+
serializedType: serialized.constructor.name,
|
|
2215
|
+
accountAddress: account?.address,
|
|
2216
|
+
hasAccount: !!account
|
|
2217
|
+
});
|
|
2218
|
+
let result, usedFormat = "";
|
|
2219
|
+
try {
|
|
2220
|
+
logger9.debug("Trying array format: transactions: [Uint8Array]"), result = await signFeature.signTransaction({
|
|
2221
|
+
account,
|
|
2222
|
+
transactions: [serialized],
|
|
2223
|
+
...cluster ? { chain: cluster.id } : {}
|
|
2224
|
+
}), usedFormat = "array";
|
|
2225
|
+
} catch (err1) {
|
|
2226
|
+
let error1 = err1 instanceof Error ? err1 : new Error(String(err1));
|
|
2227
|
+
logger9.debug("Array format failed, trying singular format", { error: error1.message });
|
|
2228
|
+
try {
|
|
2229
|
+
logger9.debug("Trying singular format: transaction: Uint8Array"), result = await signFeature.signTransaction({
|
|
2230
|
+
account,
|
|
2231
|
+
transaction: serialized,
|
|
2232
|
+
...cluster ? { chain: cluster.id } : {}
|
|
2233
|
+
}), usedFormat = "singular";
|
|
2234
|
+
} catch (err2) {
|
|
2235
|
+
let error2 = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
2236
|
+
throw logger9.error("Both array and singular formats failed", { error: error2.message }), error2;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
logger9.debug("Wallet signed successfully", { format: usedFormat });
|
|
2240
|
+
let signedTx;
|
|
2241
|
+
if (Array.isArray(result.signedTransactions) && result.signedTransactions[0])
|
|
2242
|
+
signedTx = result.signedTransactions[0];
|
|
2243
|
+
else if (result.signedTransaction)
|
|
2244
|
+
signedTx = result.signedTransaction;
|
|
2245
|
+
else if (Array.isArray(result) && result[0])
|
|
2246
|
+
signedTx = result[0];
|
|
2247
|
+
else if (result instanceof Uint8Array)
|
|
2248
|
+
signedTx = result;
|
|
2249
|
+
else
|
|
2250
|
+
throw new Error(`Unexpected wallet response format: ${JSON.stringify(Object.keys(result))}`);
|
|
2251
|
+
if (logger9.debug("Extracted signed transaction", {
|
|
2252
|
+
hasSignedTx: !!signedTx,
|
|
2253
|
+
signedTxType: signedTx?.constructor?.name,
|
|
2254
|
+
signedTxLength: signedTx?.length,
|
|
2255
|
+
isUint8Array: signedTx instanceof Uint8Array,
|
|
2256
|
+
hasSerialize: typeof signedTx?.serialize == "function"
|
|
2257
|
+
}), signedTx && typeof signedTx.serialize == "function")
|
|
2258
|
+
return logger9.debug("Wallet returned web3.js object directly, no conversion needed"), signedTx;
|
|
2259
|
+
if (signedTx && signedTx.signedTransaction) {
|
|
2260
|
+
logger9.debug("Found signedTransaction property");
|
|
2261
|
+
let bytes = signedTx.signedTransaction;
|
|
2262
|
+
if (bytes instanceof Uint8Array)
|
|
2263
|
+
return await chunkSMUUAKC3_js.convertSignedTransaction(bytes, wasWeb3js);
|
|
2264
|
+
}
|
|
2265
|
+
if (signedTx instanceof Uint8Array)
|
|
2266
|
+
return await chunkSMUUAKC3_js.convertSignedTransaction(signedTx, wasWeb3js);
|
|
2267
|
+
throw logger9.error("Unexpected wallet response format", {
|
|
2268
|
+
type: typeof signedTx,
|
|
2269
|
+
constructor: signedTx?.constructor?.name
|
|
2270
|
+
}), new ValidationError(
|
|
2271
|
+
"INVALID_FORMAT",
|
|
2272
|
+
"Wallet returned unexpected format - not a Transaction or Uint8Array"
|
|
2273
|
+
);
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
throw Errors.signingFailed(error);
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
async signAllTransactions(transactions) {
|
|
2279
|
+
if (transactions.length === 0)
|
|
2280
|
+
return [];
|
|
2281
|
+
if (capabilities.supportsBatchSigning)
|
|
2282
|
+
try {
|
|
2283
|
+
let signFeature = features["solana:signAllTransactions"], prepared = transactions.map((tx) => chunkSMUUAKC3_js.prepareTransactionForWallet(tx)), serializedTxs = prepared.map((p) => p.serialized), wasWeb3js = prepared[0].wasWeb3js, result = await signFeature.signAllTransactions({
|
|
2284
|
+
account,
|
|
2285
|
+
transactions: serializedTxs,
|
|
2286
|
+
...cluster ? { chain: cluster.id } : {}
|
|
2287
|
+
});
|
|
2288
|
+
return await Promise.all(
|
|
2289
|
+
result.signedTransactions.map(
|
|
2290
|
+
(signedBytes) => chunkSMUUAKC3_js.convertSignedTransaction(signedBytes, wasWeb3js)
|
|
2291
|
+
)
|
|
2292
|
+
);
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
throw new TransactionError(
|
|
2295
|
+
"SIGNING_FAILED",
|
|
2296
|
+
"Failed to sign transactions in batch",
|
|
2297
|
+
{ count: transactions.length },
|
|
2298
|
+
error
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (!capabilities.canSign)
|
|
2302
|
+
throw Errors.featureNotSupported("transaction signing");
|
|
2303
|
+
let signed = [];
|
|
2304
|
+
for (let i = 0; i < transactions.length; i++)
|
|
2305
|
+
try {
|
|
2306
|
+
let signedTx = await signer.signTransaction(transactions[i]);
|
|
2307
|
+
signed.push(signedTx);
|
|
2308
|
+
} catch (error) {
|
|
2309
|
+
throw new TransactionError(
|
|
2310
|
+
"SIGNING_FAILED",
|
|
2311
|
+
`Failed to sign transaction ${i + 1} of ${transactions.length}`,
|
|
2312
|
+
{ index: i, total: transactions.length },
|
|
2313
|
+
error
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
return signed;
|
|
2317
|
+
},
|
|
2318
|
+
async signAndSendTransaction(transaction, options) {
|
|
2319
|
+
if (!capabilities.canSend)
|
|
2320
|
+
throw Errors.featureNotSupported("sending transactions");
|
|
2321
|
+
try {
|
|
2322
|
+
let sendFeature = features["solana:signAndSendTransaction"], { serialized } = chunkSMUUAKC3_js.prepareTransactionForWallet(transaction);
|
|
2323
|
+
eventEmitter && eventEmitter.emit({
|
|
2324
|
+
type: "transaction:preparing",
|
|
2325
|
+
transaction: serialized,
|
|
2326
|
+
size: serialized.length,
|
|
2327
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2328
|
+
});
|
|
2329
|
+
let inputBase = {
|
|
2330
|
+
account,
|
|
2331
|
+
...cluster ? { chain: cluster.id } : {},
|
|
2332
|
+
...options ? { options } : {}
|
|
2333
|
+
};
|
|
2334
|
+
eventEmitter && eventEmitter.emit({
|
|
2335
|
+
type: "transaction:signing",
|
|
2336
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2337
|
+
});
|
|
2338
|
+
let result;
|
|
2339
|
+
try {
|
|
2340
|
+
result = await sendFeature.signAndSendTransaction({
|
|
2341
|
+
...inputBase,
|
|
2342
|
+
transactions: [serialized]
|
|
2343
|
+
});
|
|
2344
|
+
} catch {
|
|
2345
|
+
result = await sendFeature.signAndSendTransaction({
|
|
2346
|
+
...inputBase,
|
|
2347
|
+
transaction: serialized
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
let signature = typeof result == "object" && result.signature ? result.signature : String(result);
|
|
2351
|
+
return eventEmitter && eventEmitter.emit({
|
|
2352
|
+
type: "transaction:sent",
|
|
2353
|
+
signature,
|
|
2354
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2355
|
+
}), signature;
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
throw new TransactionError("SEND_FAILED", "Failed to send transaction", void 0, error);
|
|
2358
|
+
}
|
|
2359
|
+
},
|
|
2360
|
+
async signAndSendTransactions(transactions, options) {
|
|
2361
|
+
if (transactions.length === 0)
|
|
2362
|
+
return [];
|
|
2363
|
+
if (!capabilities.canSend)
|
|
2364
|
+
throw Errors.featureNotSupported("sending transactions");
|
|
2365
|
+
let signatures = [];
|
|
2366
|
+
for (let i = 0; i < transactions.length; i++)
|
|
2367
|
+
try {
|
|
2368
|
+
let sig = await signer.signAndSendTransaction(transactions[i], options);
|
|
2369
|
+
signatures.push(sig);
|
|
2370
|
+
} catch (error) {
|
|
2371
|
+
throw new TransactionError(
|
|
2372
|
+
"SEND_FAILED",
|
|
2373
|
+
`Failed to send transaction ${i + 1} of ${transactions.length}`,
|
|
2374
|
+
{ index: i, total: transactions.length },
|
|
2375
|
+
error
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
return signatures;
|
|
2379
|
+
},
|
|
2380
|
+
...capabilities.canSignMessage && {
|
|
2381
|
+
async signMessage(message) {
|
|
2382
|
+
try {
|
|
2383
|
+
return (await features["solana:signMessage"].signMessage({
|
|
2384
|
+
account,
|
|
2385
|
+
message,
|
|
2386
|
+
...cluster ? { chain: cluster.id } : {}
|
|
2387
|
+
})).signature;
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
throw new TransactionError("SIGNING_FAILED", "Failed to sign message", void 0, error);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
},
|
|
2393
|
+
getCapabilities() {
|
|
2394
|
+
return { ...capabilities };
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
return signer;
|
|
2398
|
+
}
|
|
2399
|
+
var TransactionSignerError = class extends TransactionError {
|
|
2400
|
+
constructor(message, code, originalError) {
|
|
2401
|
+
let newCode = code === "WALLET_NOT_CONNECTED" ? "FEATURE_NOT_SUPPORTED" : code;
|
|
2402
|
+
super(newCode, message, void 0, originalError), this.name = "TransactionSignerError";
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
function isTransactionSignerError(error) {
|
|
2406
|
+
return error instanceof TransactionSignerError || error instanceof TransactionError;
|
|
2407
|
+
}
|
|
2408
|
+
var logger10 = chunkSMUUAKC3_js.createLogger("GillTransactionSigner");
|
|
2409
|
+
function encodeShortVecLength(value) {
|
|
2410
|
+
let bytes = [], remaining = value;
|
|
2411
|
+
for (; remaining >= 128; )
|
|
2412
|
+
bytes.push(remaining & 127 | 128), remaining >>= 7;
|
|
2413
|
+
return bytes.push(remaining), new Uint8Array(bytes);
|
|
2414
|
+
}
|
|
2415
|
+
function decodeShortVecLength(data) {
|
|
2416
|
+
let length = 0, size = 0;
|
|
2417
|
+
for (; ; ) {
|
|
2418
|
+
if (size >= data.length)
|
|
2419
|
+
throw new Error("Invalid shortvec encoding: unexpected end of data");
|
|
2420
|
+
let byte = data[size];
|
|
2421
|
+
if (length |= (byte & 127) << size * 7, size += 1, (byte & 128) === 0)
|
|
2422
|
+
break;
|
|
2423
|
+
if (size > 10)
|
|
2424
|
+
throw new Error("Invalid shortvec encoding: length prefix too long");
|
|
2425
|
+
}
|
|
2426
|
+
return { length, bytesConsumed: size };
|
|
2427
|
+
}
|
|
2428
|
+
function createTransactionBytesForSigning(messageBytes, numSigners) {
|
|
2429
|
+
let numSignaturesBytes = encodeShortVecLength(numSigners), signatureSlots = new Uint8Array(numSigners * 64), totalLength = numSignaturesBytes.length + signatureSlots.length + messageBytes.length, transactionBytes = new Uint8Array(totalLength), offset = 0;
|
|
2430
|
+
return transactionBytes.set(numSignaturesBytes, offset), offset += numSignaturesBytes.length, transactionBytes.set(signatureSlots, offset), offset += signatureSlots.length, transactionBytes.set(messageBytes, offset), transactionBytes;
|
|
2431
|
+
}
|
|
2432
|
+
function extractSignature(signedTx) {
|
|
2433
|
+
if (signedTx instanceof Uint8Array) {
|
|
2434
|
+
let { length: numSignatures, bytesConsumed } = decodeShortVecLength(signedTx);
|
|
2435
|
+
if (numSignatures === 0)
|
|
2436
|
+
throw new Error("No signatures found in serialized transaction");
|
|
2437
|
+
let signatureStart = bytesConsumed;
|
|
2438
|
+
return signedTx.slice(signatureStart, signatureStart + 64);
|
|
2439
|
+
}
|
|
2440
|
+
if (chunkSMUUAKC3_js.isWeb3jsTransaction(signedTx)) {
|
|
2441
|
+
let signatures = signedTx.signatures;
|
|
2442
|
+
if (!signatures || signatures.length === 0)
|
|
2443
|
+
throw new Error("No signatures found in web3.js transaction");
|
|
2444
|
+
let firstSig = signatures[0];
|
|
2445
|
+
if (firstSig instanceof Uint8Array)
|
|
2446
|
+
return firstSig;
|
|
2447
|
+
if (firstSig && typeof firstSig == "object" && "signature" in firstSig && firstSig.signature)
|
|
2448
|
+
return firstSig.signature;
|
|
2449
|
+
throw new Error("Could not extract signature from web3.js transaction");
|
|
2450
|
+
}
|
|
2451
|
+
throw new Error("Cannot extract signature from transaction format");
|
|
2452
|
+
}
|
|
2453
|
+
function createGillTransactionSigner(connectorSigner) {
|
|
2454
|
+
let signerAddress = gill.address(connectorSigner.address);
|
|
2455
|
+
return {
|
|
2456
|
+
address: signerAddress,
|
|
2457
|
+
async modifyAndSignTransactions(transactions) {
|
|
2458
|
+
let transactionData = transactions.map((tx) => {
|
|
2459
|
+
let messageBytes = new Uint8Array(tx.messageBytes), numSigners = Object.keys(tx.signatures).length, wireFormat = createTransactionBytesForSigning(messageBytes, numSigners);
|
|
2460
|
+
return logger10.debug("Preparing wire format for wallet", {
|
|
2461
|
+
signerAddress,
|
|
2462
|
+
messageBytesLength: messageBytes.length,
|
|
2463
|
+
wireFormatLength: wireFormat.length,
|
|
2464
|
+
structure: {
|
|
2465
|
+
numSigsByte: wireFormat[0],
|
|
2466
|
+
firstSigSlotPreview: Array.from(wireFormat.slice(1, 17)),
|
|
2467
|
+
messageBytesStart: wireFormat.length - messageBytes.length
|
|
2468
|
+
}
|
|
2469
|
+
}), {
|
|
2470
|
+
originalTransaction: tx,
|
|
2471
|
+
messageBytes,
|
|
2472
|
+
wireFormat
|
|
2473
|
+
};
|
|
2474
|
+
}), transactionsForWallet = transactionData.map((data) => data.wireFormat);
|
|
2475
|
+
return (await connectorSigner.signAllTransactions(transactionsForWallet)).map((signedTx, index) => {
|
|
2476
|
+
let { originalTransaction, wireFormat } = transactionData[index];
|
|
2477
|
+
try {
|
|
2478
|
+
let signedTxBytes;
|
|
2479
|
+
if (signedTx instanceof Uint8Array)
|
|
2480
|
+
signedTxBytes = signedTx;
|
|
2481
|
+
else if (chunkSMUUAKC3_js.isWeb3jsTransaction(signedTx)) {
|
|
2482
|
+
let txObj = signedTx;
|
|
2483
|
+
if (typeof txObj.serialize == "function")
|
|
2484
|
+
signedTxBytes = txObj.serialize();
|
|
2485
|
+
else
|
|
2486
|
+
throw new Error("Web3.js transaction without serialize method");
|
|
2487
|
+
} else
|
|
2488
|
+
throw new Error("Unknown signed transaction format");
|
|
2489
|
+
if (logger10.debug("Wallet returned signed transaction", {
|
|
2490
|
+
returnedLength: signedTxBytes.length,
|
|
2491
|
+
sentLength: wireFormat.length,
|
|
2492
|
+
lengthsMatch: signedTxBytes.length === wireFormat.length,
|
|
2493
|
+
signedFirstBytes: Array.from(signedTxBytes.slice(0, 20)),
|
|
2494
|
+
sentFirstBytes: Array.from(wireFormat.slice(0, 20))
|
|
2495
|
+
}), signedTxBytes.length !== wireFormat.length) {
|
|
2496
|
+
logger10.warn("Wallet modified transaction! Using wallet version", {
|
|
2497
|
+
originalLength: wireFormat.length,
|
|
2498
|
+
modifiedLength: signedTxBytes.length,
|
|
2499
|
+
difference: signedTxBytes.length - wireFormat.length
|
|
2500
|
+
});
|
|
2501
|
+
let walletTransaction = gill.getTransactionDecoder().decode(signedTxBytes), originalWithLifetime = originalTransaction, result = {
|
|
2502
|
+
...walletTransaction,
|
|
2503
|
+
...originalWithLifetime.lifetimeConstraint ? {
|
|
2504
|
+
lifetimeConstraint: originalWithLifetime.lifetimeConstraint
|
|
2505
|
+
} : {}
|
|
2506
|
+
};
|
|
2507
|
+
return logger10.debug("Using modified transaction from wallet", {
|
|
2508
|
+
modifiedMessageBytesLength: walletTransaction.messageBytes.length,
|
|
2509
|
+
signatures: Object.keys(walletTransaction.signatures)
|
|
2510
|
+
}), result;
|
|
2511
|
+
}
|
|
2512
|
+
let signatureBytes = extractSignature(signedTxBytes), signatureBase58 = gill.getSignatureFromBytes(signatureBytes);
|
|
2513
|
+
return logger10.debug("Extracted signature from wallet (unmodified)", {
|
|
2514
|
+
signerAddress,
|
|
2515
|
+
signatureLength: signatureBytes.length,
|
|
2516
|
+
signatureBase58
|
|
2517
|
+
// Human-readable signature for debugging/logging
|
|
2518
|
+
}), {
|
|
2519
|
+
...originalTransaction,
|
|
2520
|
+
signatures: Object.freeze({
|
|
2521
|
+
...originalTransaction.signatures,
|
|
2522
|
+
[signerAddress]: signatureBytes
|
|
2523
|
+
})
|
|
2524
|
+
};
|
|
2525
|
+
} catch (error) {
|
|
2526
|
+
return logger10.error("Failed to decode signed transaction", { error }), originalTransaction;
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
exports.ClipboardErrorType = ClipboardErrorType;
|
|
2534
|
+
exports.ConfigurationError = ConfigurationError;
|
|
2535
|
+
exports.ConnectionError = ConnectionError;
|
|
2536
|
+
exports.ConnectorClient = ConnectorClient;
|
|
2537
|
+
exports.ConnectorError = ConnectorError;
|
|
2538
|
+
exports.ConnectorErrorBoundary = ConnectorErrorBoundary;
|
|
2539
|
+
exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES;
|
|
2540
|
+
exports.Errors = Errors;
|
|
2541
|
+
exports.NetworkError = NetworkError;
|
|
2542
|
+
exports.PUBLIC_RPC_ENDPOINTS = PUBLIC_RPC_ENDPOINTS;
|
|
2543
|
+
exports.TransactionError = TransactionError;
|
|
2544
|
+
exports.TransactionSignerError = TransactionSignerError;
|
|
2545
|
+
exports.ValidationError = ValidationError;
|
|
2546
|
+
exports.WalletErrorType = WalletErrorType;
|
|
2547
|
+
exports.copyAddressToClipboard = copyAddressToClipboard;
|
|
2548
|
+
exports.copySignatureToClipboard = copySignatureToClipboard;
|
|
2549
|
+
exports.copyToClipboard = copyToClipboard;
|
|
2550
|
+
exports.createGillTransactionSigner = createGillTransactionSigner;
|
|
2551
|
+
exports.createTransactionSigner = createTransactionSigner;
|
|
2552
|
+
exports.formatAddress = formatAddress;
|
|
2553
|
+
exports.formatNumber = formatNumber;
|
|
2554
|
+
exports.formatSOL = formatSOL;
|
|
2555
|
+
exports.formatTokenAmount = formatTokenAmount;
|
|
2556
|
+
exports.getAddressUrl = getAddressUrl;
|
|
2557
|
+
exports.getBlockUrl = getBlockUrl;
|
|
2558
|
+
exports.getClusterExplorerUrl = getClusterExplorerUrl;
|
|
2559
|
+
exports.getClusterName = getClusterName;
|
|
2560
|
+
exports.getClusterRpcUrl = getClusterRpcUrl;
|
|
2561
|
+
exports.getClusterType = getClusterType;
|
|
2562
|
+
exports.getDefaultRpcUrl = getDefaultRpcUrl;
|
|
2563
|
+
exports.getNetworkDisplayName = getNetworkDisplayName;
|
|
2564
|
+
exports.getPolyfillStatus = getPolyfillStatus;
|
|
2565
|
+
exports.getTokenUrl = getTokenUrl;
|
|
2566
|
+
exports.getTransactionUrl = getTransactionUrl;
|
|
2567
|
+
exports.getUserFriendlyMessage = getUserFriendlyMessage;
|
|
2568
|
+
exports.getWalletsRegistry = getWalletsRegistry;
|
|
2569
|
+
exports.installPolyfills = installPolyfills;
|
|
2570
|
+
exports.isClipboardAvailable = isClipboardAvailable;
|
|
2571
|
+
exports.isConfigurationError = isConfigurationError;
|
|
2572
|
+
exports.isConnectionError = isConnectionError;
|
|
2573
|
+
exports.isConnectorError = isConnectorError;
|
|
2574
|
+
exports.isCryptoAvailable = isCryptoAvailable;
|
|
2575
|
+
exports.isDevnet = isDevnet;
|
|
2576
|
+
exports.isDevnetCluster = isDevnetCluster;
|
|
2577
|
+
exports.isLocalCluster = isLocalCluster;
|
|
2578
|
+
exports.isLocalnet = isLocalnet;
|
|
2579
|
+
exports.isMainnet = isMainnet;
|
|
2580
|
+
exports.isMainnetCluster = isMainnetCluster;
|
|
2581
|
+
exports.isNetworkError = isNetworkError;
|
|
2582
|
+
exports.isPolyfillInstalled = isPolyfillInstalled;
|
|
2583
|
+
exports.isTestnet = isTestnet;
|
|
2584
|
+
exports.isTestnetCluster = isTestnetCluster;
|
|
2585
|
+
exports.isTransactionError = isTransactionError;
|
|
2586
|
+
exports.isTransactionSignerError = isTransactionSignerError;
|
|
2587
|
+
exports.isValidationError = isValidationError;
|
|
2588
|
+
exports.normalizeNetwork = normalizeNetwork;
|
|
2589
|
+
exports.toClusterId = toClusterId;
|
|
2590
|
+
exports.toConnectorError = toConnectorError;
|
|
2591
|
+
exports.truncate = truncate;
|
|
2592
|
+
exports.withErrorBoundary = withErrorBoundary;
|
|
2593
|
+
//# sourceMappingURL=chunk-ZLPQUOFK.js.map
|
|
2594
|
+
//# sourceMappingURL=chunk-ZLPQUOFK.js.map
|