@marigoldlabs/web3-tester 0.1.2 → 0.4.2

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.
Files changed (96) hide show
  1. package/.env.example +26 -17
  2. package/LICENSE +21 -0
  3. package/README.md +480 -48
  4. package/dist/anvil.d.ts +90 -2
  5. package/dist/anvil.d.ts.map +1 -1
  6. package/dist/anvil.js +215 -13
  7. package/dist/anvil.js.map +1 -1
  8. package/dist/benchmark.d.ts +55 -0
  9. package/dist/benchmark.d.ts.map +1 -0
  10. package/dist/benchmark.js +168 -0
  11. package/dist/benchmark.js.map +1 -0
  12. package/dist/contracts/test-erc20.d.ts +227 -0
  13. package/dist/contracts/test-erc20.d.ts.map +1 -0
  14. package/dist/contracts/test-erc20.js +8 -0
  15. package/dist/contracts/test-erc20.js.map +1 -0
  16. package/dist/erc20.d.ts +38 -0
  17. package/dist/erc20.d.ts.map +1 -0
  18. package/dist/erc20.js +229 -0
  19. package/dist/erc20.js.map +1 -0
  20. package/dist/fixtures.d.ts +44 -2
  21. package/dist/fixtures.d.ts.map +1 -1
  22. package/dist/fixtures.js +162 -17
  23. package/dist/fixtures.js.map +1 -1
  24. package/dist/index.d.ts +26 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +11 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/injected-provider.d.ts.map +1 -1
  29. package/dist/injected-provider.js +863 -77
  30. package/dist/injected-provider.js.map +1 -1
  31. package/dist/live-fixtures.d.ts +32 -3
  32. package/dist/live-fixtures.d.ts.map +1 -1
  33. package/dist/live-fixtures.js +64 -27
  34. package/dist/live-fixtures.js.map +1 -1
  35. package/dist/matchers.d.ts +90 -0
  36. package/dist/matchers.d.ts.map +1 -0
  37. package/dist/matchers.js +268 -0
  38. package/dist/matchers.js.map +1 -0
  39. package/dist/metamask-extension.d.ts +34 -0
  40. package/dist/metamask-extension.d.ts.map +1 -0
  41. package/dist/metamask-extension.js +97 -0
  42. package/dist/metamask-extension.js.map +1 -0
  43. package/dist/mock-wallet-controller.d.ts +354 -4
  44. package/dist/mock-wallet-controller.d.ts.map +1 -1
  45. package/dist/mock-wallet-controller.js +1444 -58
  46. package/dist/mock-wallet-controller.js.map +1 -1
  47. package/dist/private-key-rpc-client.d.ts +1744 -2
  48. package/dist/private-key-rpc-client.d.ts.map +1 -1
  49. package/dist/private-key-rpc-client.js +245 -30
  50. package/dist/private-key-rpc-client.js.map +1 -1
  51. package/dist/real-wallet-cache.d.ts +103 -0
  52. package/dist/real-wallet-cache.d.ts.map +1 -0
  53. package/dist/real-wallet-cache.js +331 -0
  54. package/dist/real-wallet-cache.js.map +1 -0
  55. package/dist/real-wallet-extension-fixtures.d.ts +35 -0
  56. package/dist/real-wallet-extension-fixtures.d.ts.map +1 -0
  57. package/dist/real-wallet-extension-fixtures.js +96 -0
  58. package/dist/real-wallet-extension-fixtures.js.map +1 -0
  59. package/dist/real-wallet-extension.d.ts +86 -0
  60. package/dist/real-wallet-extension.d.ts.map +1 -0
  61. package/dist/real-wallet-extension.js +245 -0
  62. package/dist/real-wallet-extension.js.map +1 -0
  63. package/dist/real-wallet-fixtures.d.ts +52 -0
  64. package/dist/real-wallet-fixtures.d.ts.map +1 -0
  65. package/dist/real-wallet-fixtures.js +88 -0
  66. package/dist/real-wallet-fixtures.js.map +1 -0
  67. package/dist/real-wallet.d.ts +119 -19
  68. package/dist/real-wallet.d.ts.map +1 -1
  69. package/dist/real-wallet.js +1656 -144
  70. package/dist/real-wallet.js.map +1 -1
  71. package/dist/safe.d.ts +311 -0
  72. package/dist/safe.d.ts.map +1 -0
  73. package/dist/safe.js +743 -0
  74. package/dist/safe.js.map +1 -0
  75. package/dist/transactions.d.ts +118 -0
  76. package/dist/transactions.d.ts.map +1 -0
  77. package/dist/transactions.js +207 -0
  78. package/dist/transactions.js.map +1 -0
  79. package/dist/types.d.ts +36 -1
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/wallet-personas.d.ts +99 -0
  82. package/dist/wallet-personas.d.ts.map +1 -0
  83. package/dist/wallet-personas.js +666 -0
  84. package/dist/wallet-personas.js.map +1 -0
  85. package/dist/walletconnect.d.ts +373 -0
  86. package/dist/walletconnect.d.ts.map +1 -0
  87. package/dist/walletconnect.js +799 -0
  88. package/dist/walletconnect.js.map +1 -0
  89. package/examples/live-sepolia.spec.ts +20 -1
  90. package/examples/playwright.config.ts +8 -0
  91. package/package.json +90 -8
  92. package/docs/API.md +0 -223
  93. package/docs/ARCHITECTURE.md +0 -81
  94. package/docs/CONSUMING_FROM_FJORD.md +0 -123
  95. package/docs/FJORD_LIVE_QA.md +0 -87
  96. package/docs/RELEASE_CHECKLIST.md +0 -55
@@ -5,35 +5,112 @@ export const emitterName = EMITTER_NAME;
5
5
  export const buildInjectedProviderScript = (config) => `
6
6
  (() => {
7
7
  const config = ${JSON.stringify(config)};
8
- const listeners = new Map();
9
8
 
10
- const getListeners = (event) => {
11
- if (!listeners.has(event)) {
12
- listeners.set(event, new Set());
13
- }
14
- return listeners.get(event);
9
+ // Origin-scoped wallets never install in out-of-scope frames, so blocked
10
+ // pages cannot even read the account address or chain off the provider.
11
+ // window.origin (unlike location.origin or the frame URL) is the security
12
+ // origin, which about:blank/srcdoc children inherit from their parent —
13
+ // matching the bridge-side ancestor walk.
14
+ if (config.allowedOrigins && !config.allowedOrigins.includes(window.origin || location.origin)) {
15
+ return;
16
+ }
17
+
18
+ const toNetworkVersion = (chainId) => String(Number(BigInt(chainId)));
19
+
20
+ const walletError = (code, message) => {
21
+ const error = new Error(message);
22
+ error.code = code;
23
+ return error;
15
24
  };
16
25
 
17
- const emit = (event, payload) => {
18
- if (event === 'accountsChanged') {
19
- provider.selectedAddress = Array.isArray(payload) && payload.length > 0 ? payload[0] : null;
26
+ const createPublicKey = (value) => Object.freeze({
27
+ toString: () => value,
28
+ toBase58: () => value,
29
+ equals: (other) => {
30
+ const otherValue =
31
+ typeof other === 'string'
32
+ ? other
33
+ : typeof other?.toBase58 === 'function'
34
+ ? other.toBase58()
35
+ : typeof other?.toString === 'function'
36
+ ? other.toString()
37
+ : '';
38
+ return otherValue === value;
39
+ },
40
+ toBytes: () => base58Decode(value),
41
+ toJSON: () => value,
42
+ });
43
+
44
+ const base58Decode = (value) => {
45
+ const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
46
+ const bytes = [0];
47
+ for (const char of String(value)) {
48
+ const carryStart = alphabet.indexOf(char);
49
+ if (carryStart === -1) {
50
+ return new TextEncoder().encode(String(value)).slice(0, 32);
51
+ }
52
+ let carry = carryStart;
53
+ for (let index = 0; index < bytes.length; index += 1) {
54
+ const next = bytes[index] * 58 + carry;
55
+ bytes[index] = next & 0xff;
56
+ carry = next >> 8;
57
+ }
58
+ while (carry > 0) {
59
+ bytes.push(carry & 0xff);
60
+ carry >>= 8;
61
+ }
20
62
  }
63
+ for (const char of String(value)) {
64
+ if (char !== '1') break;
65
+ bytes.push(0);
66
+ }
67
+ return new Uint8Array(bytes.reverse());
68
+ };
21
69
 
22
- if (event === 'chainChanged') {
23
- provider.chainId = payload;
24
- provider.networkVersion = String(Number(BigInt(payload)));
70
+ const bytesFrom = (input) => {
71
+ if (input instanceof Uint8Array) {
72
+ return [...input];
73
+ }
74
+ if (input instanceof ArrayBuffer) {
75
+ return [...new Uint8Array(input)];
76
+ }
77
+ if (ArrayBuffer.isView(input)) {
78
+ return [...new Uint8Array(input.buffer, input.byteOffset, input.byteLength)];
79
+ }
80
+ if (Array.isArray(input)) {
81
+ return input.map((value) => Number(value) & 255);
25
82
  }
83
+ if (typeof input === 'string') {
84
+ return [...new TextEncoder().encode(input)];
85
+ }
86
+ return [...new TextEncoder().encode(JSON.stringify(input ?? null))];
87
+ };
26
88
 
27
- const callbacks = listeners.get(event);
28
- if (!callbacks) {
29
- return;
89
+ const deterministicSignature = (publicKey, payload) => {
90
+ const seed = [...new TextEncoder().encode(publicKey), ...bytesFrom(payload)];
91
+ const signature = new Uint8Array(64);
92
+ for (let index = 0; index < signature.length; index += 1) {
93
+ const a = seed[index % seed.length] ?? 0;
94
+ const b = seed[(index * 7 + 13) % seed.length] ?? 0;
95
+ signature[index] = (a + b + index * 17) & 255;
30
96
  }
97
+ return signature;
98
+ };
31
99
 
32
- for (const callback of [...callbacks]) {
33
- callback(payload);
100
+ const hexToBytes = (hex) => {
101
+ const value = String(hex).replace(/^0x/u, '');
102
+ const bytes = new Uint8Array(Math.floor(value.length / 2));
103
+ for (let index = 0; index < bytes.length; index += 1) {
104
+ bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
34
105
  }
106
+ return bytes;
35
107
  };
36
108
 
109
+ // EIP-1193 connectivity means "can the provider reach the chain", which is
110
+ // independent of account authorization. The controller's disconnect()
111
+ // simulates losing the chain via the 'disconnect' event.
112
+ let chainDisconnected = false;
113
+
37
114
  const request = async (args) => {
38
115
  if (!args || typeof args.method !== 'string') {
39
116
  const error = new Error('Invalid EIP-1193 request');
@@ -56,78 +133,760 @@ export const buildInjectedProviderScript = (config) => `
56
133
  return response.result;
57
134
  };
58
135
 
59
- const provider = {
60
- isMetaMask: true,
61
- isMock: true,
62
- selectedAddress: config.connected && config.accounts.length > 0 ? config.accounts[0] : null,
63
- chainId: config.chainId,
64
- networkVersion: String(Number(BigInt(config.chainId))),
65
- request,
66
- enable: () => request({ method: 'eth_requestAccounts' }),
67
- send: (methodOrPayload, paramsOrCallback) => {
68
- if (typeof methodOrPayload === 'string') {
69
- return request({ method: methodOrPayload, params: paramsOrCallback });
70
- }
71
-
72
- const payload = methodOrPayload;
73
- const callback = paramsOrCallback;
74
- request(payload)
75
- .then((result) => callback?.(null, { id: payload.id, jsonrpc: '2.0', result }))
76
- .catch((error) => callback?.(error, null));
77
- },
78
- sendAsync: (payload, callback) => {
79
- request(payload)
80
- .then((result) => callback(null, { id: payload.id, jsonrpc: '2.0', result }))
81
- .catch((error) => callback(error, null));
82
- },
83
- on: (event, handler) => {
84
- getListeners(event).add(handler);
85
- return provider;
86
- },
87
- once: (event, handler) => {
88
- const wrapped = (payload) => {
89
- provider.removeListener(event, wrapped);
90
- handler(payload);
136
+ // One distinct provider object per announced wallet so selector tests can
137
+ // detect a dapp talking to the wrong provider. They share request handling
138
+ // and state, but each has its own identity and listener registry.
139
+ const createProviderEntry = (providerConfig) => {
140
+ const { evm = true, flags = {}, aliases = [], solana, ...info } = providerConfig;
141
+ const providerInfo = Object.freeze({ ...info });
142
+ const listeners = new Map();
143
+
144
+ const getListeners = (event) => {
145
+ if (!listeners.has(event)) {
146
+ listeners.set(event, new Set());
147
+ }
148
+ return listeners.get(event);
149
+ };
150
+
151
+ const responseForPayload = (payload, result) => ({
152
+ id: payload.id,
153
+ jsonrpc: payload.jsonrpc ?? '2.0',
154
+ result,
155
+ });
156
+
157
+ const errorResponseForPayload = (payload, error) => {
158
+ const rpcError = {
159
+ code: typeof error?.code === 'number' ? error.code : -32603,
160
+ message:
161
+ typeof error?.message === 'string' ? error.message : 'Internal JSON-RPC error',
91
162
  };
92
- provider.on(event, wrapped);
93
- return provider;
94
- },
95
- removeListener: (event, handler) => {
96
- listeners.get(event)?.delete(handler);
97
- return provider;
98
- },
99
- removeAllListeners: (event) => {
100
- if (event) {
101
- listeners.delete(event);
102
- } else {
103
- listeners.clear();
163
+ if (error?.data !== undefined) {
164
+ rpcError.data = error.data;
104
165
  }
105
- return provider;
106
- },
107
- _metamask: {
108
- isUnlocked: async () => true,
109
- },
166
+ return {
167
+ id: payload.id,
168
+ jsonrpc: payload.jsonrpc ?? '2.0',
169
+ error: rpcError,
170
+ };
171
+ };
172
+
173
+ const requestBatch = (payloads) =>
174
+ Promise.all(payloads.map((payload) => request(payload)));
175
+
176
+ const requestBatchResponses = (payloads) =>
177
+ Promise.all(
178
+ payloads.map(async (payload) => {
179
+ try {
180
+ return responseForPayload(payload, await request(payload));
181
+ } catch (error) {
182
+ return errorResponseForPayload(payload, error);
183
+ }
184
+ }),
185
+ );
186
+
187
+ const provider = {
188
+ info: providerInfo,
189
+ selectedAddress:
190
+ config.connected && config.unlocked && config.accounts.length > 0 ? config.accounts[0] : null,
191
+ chainId: config.chainId,
192
+ networkVersion: toNetworkVersion(config.chainId),
193
+ request,
194
+ isConnected: () => !chainDisconnected,
195
+ enable: () => request({ method: 'eth_requestAccounts' }),
196
+ send: (methodOrPayload, paramsOrCallback) => {
197
+ if (typeof methodOrPayload === 'string') {
198
+ return request({ method: methodOrPayload, params: paramsOrCallback });
199
+ }
200
+
201
+ const payload = methodOrPayload;
202
+ const callback = paramsOrCallback;
203
+ if (typeof callback !== 'function') {
204
+ return Array.isArray(payload) ? requestBatch(payload) : request(payload);
205
+ }
206
+ const promise = Array.isArray(payload)
207
+ ? requestBatchResponses(payload)
208
+ : request(payload).then((result) => responseForPayload(payload, result));
209
+ promise
210
+ .then((response) => callback?.(null, response))
211
+ .catch((error) => callback?.(error, null));
212
+ return undefined;
213
+ },
214
+ sendAsync: (payload, callback) => {
215
+ const promise = Array.isArray(payload)
216
+ ? requestBatchResponses(payload)
217
+ : request(payload).then((result) => responseForPayload(payload, result));
218
+ promise
219
+ .then((response) => callback(null, response))
220
+ .catch((error) => callback(error, null));
221
+ },
222
+ on: (event, handler) => {
223
+ getListeners(event).add(handler);
224
+ if (event === 'connect' && config.connected && !chainDisconnected) {
225
+ setTimeout(() => {
226
+ if (listeners.get(event)?.has(handler)) {
227
+ handler({ chainId: provider.chainId });
228
+ }
229
+ }, 0);
230
+ }
231
+ return provider;
232
+ },
233
+ addListener: (event, handler) => provider.on(event, handler),
234
+ once: (event, handler) => {
235
+ const wrapped = (payload) => {
236
+ provider.removeListener(event, wrapped);
237
+ handler(payload);
238
+ };
239
+ provider.on(event, wrapped);
240
+ return provider;
241
+ },
242
+ removeListener: (event, handler) => {
243
+ listeners.get(event)?.delete(handler);
244
+ return provider;
245
+ },
246
+ off: (event, handler) => provider.removeListener(event, handler),
247
+ removeAllListeners: (event) => {
248
+ if (event) {
249
+ listeners.delete(event);
250
+ } else {
251
+ listeners.clear();
252
+ }
253
+ return provider;
254
+ },
255
+ listeners: (event) => [...(listeners.get(event) ?? [])],
256
+ listenerCount: (event) => listeners.get(event)?.size ?? 0,
257
+ };
258
+
259
+ for (const [flag, value] of Object.entries(flags)) {
260
+ Object.defineProperty(provider, flag, {
261
+ value: Boolean(value),
262
+ configurable: true,
263
+ enumerable: true,
264
+ });
265
+ }
266
+
267
+ if (flags.isMetaMask) {
268
+ Object.defineProperty(provider, '_metamask', {
269
+ value: {
270
+ isUnlocked: async () => {
271
+ try {
272
+ const state = await request({ method: 'metamask_getProviderState' });
273
+ return state.isUnlocked === true;
274
+ } catch {
275
+ return config.unlocked === true;
276
+ }
277
+ },
278
+ },
279
+ configurable: true,
280
+ enumerable: true,
281
+ });
282
+ }
283
+
284
+ return { info: providerInfo, provider, listeners, aliases, solana, evm: evm !== false };
285
+ };
286
+
287
+ const createSolanaProvider = (entry) => {
288
+ const solanaConfig = entry.solana;
289
+ if (!solanaConfig) {
290
+ return undefined;
291
+ }
292
+
293
+ const listeners = new Map();
294
+ const standardListeners = new Map();
295
+ const publicKeyValue =
296
+ solanaConfig.publicKey ?? '26qv4GCcx98RihuK3c4T6ozB3J7L6VwCuFVc7Ta2A3Uo';
297
+ const publicKey = createPublicKey(publicKeyValue);
298
+ const solanaChains = Object.freeze(
299
+ solanaConfig.chains ?? ['solana:mainnet', 'solana:devnet', 'solana:testnet'],
300
+ );
301
+ const solanaAccountFeatures = Object.freeze([
302
+ 'solana:signAndSendTransaction',
303
+ 'solana:signIn',
304
+ 'solana:signMessage',
305
+ 'solana:signTransaction',
306
+ ]);
307
+ const standardAccount = Object.freeze({
308
+ address: publicKeyValue,
309
+ publicKey: base58Decode(publicKeyValue),
310
+ chains: solanaChains,
311
+ features: solanaAccountFeatures,
312
+ label: entry.info.name,
313
+ icon: entry.info.icon,
314
+ });
315
+ let connected = false;
316
+ let trusted = false;
317
+ let hiddenByController = false;
318
+
319
+ const getListeners = (event) => {
320
+ if (!listeners.has(event)) {
321
+ listeners.set(event, new Set());
322
+ }
323
+ return listeners.get(event);
324
+ };
325
+
326
+ const emitSolana = (event, payload) => {
327
+ const callbacks = listeners.get(event);
328
+ if (!callbacks) {
329
+ return;
330
+ }
331
+ for (const callback of [...callbacks]) {
332
+ callback(payload);
333
+ }
334
+ };
335
+
336
+ const emitStandardChange = (properties) => {
337
+ const callbacks = standardListeners.get('change');
338
+ if (!callbacks) {
339
+ return;
340
+ }
341
+ for (const callback of [...callbacks]) {
342
+ callback(properties);
343
+ }
344
+ };
345
+
346
+ const standardOn = (event, handler) => {
347
+ if (!standardListeners.has(event)) {
348
+ standardListeners.set(event, new Set());
349
+ }
350
+ standardListeners.get(event).add(handler);
351
+ return () => standardListeners.get(event)?.delete(handler);
352
+ };
353
+
354
+ const ensureConnected = () => {
355
+ if (!connected) {
356
+ throw walletError(4100, 'The Solana wallet is not connected.');
357
+ }
358
+ };
359
+
360
+ const visibleAccounts = () => (connected ? [publicKeyValue] : []);
361
+ const visibleAccountObjects = () =>
362
+ connected
363
+ ? [
364
+ Object.freeze({
365
+ publicKey,
366
+ pubkey: publicKeyValue,
367
+ address: publicKeyValue,
368
+ }),
369
+ ]
370
+ : [];
371
+
372
+ const ensureStandardAccount = (account) => {
373
+ if (account?.address && account.address !== publicKeyValue) {
374
+ throw walletError(4100, 'The Solana wallet cannot use the requested account.');
375
+ }
376
+ };
377
+
378
+ const transactionsFromParams = (params) => {
379
+ if (Array.isArray(params)) {
380
+ return params;
381
+ }
382
+ if (Array.isArray(params?.transactions)) {
383
+ return params.transactions;
384
+ }
385
+ if (Array.isArray(params?.message)) {
386
+ return params.message;
387
+ }
388
+ if (Array.isArray(params?.[0])) {
389
+ return params[0];
390
+ }
391
+ return [];
392
+ };
393
+
394
+ const buildSignInMessage = (input = {}) => {
395
+ const address = input.address ?? publicKeyValue;
396
+ const domain = input.domain ?? window.location.host ?? 'localhost';
397
+ const statement = input.statement ?? 'Sign in with Solana.';
398
+ const lines = [
399
+ \`\${domain} wants you to sign in with your Solana account:\`,
400
+ address,
401
+ '',
402
+ statement,
403
+ ];
404
+ const fields = [
405
+ ['URI', input.uri],
406
+ ['Version', input.version],
407
+ ['Chain ID', input.chainId],
408
+ ['Nonce', input.nonce],
409
+ ['Issued At', input.issuedAt],
410
+ ['Expiration Time', input.expirationTime],
411
+ ['Not Before', input.notBefore],
412
+ ['Request ID', input.requestId],
413
+ ];
414
+ for (const [label, value] of fields) {
415
+ if (value !== undefined) {
416
+ lines.push(\`\${label}: \${value}\`);
417
+ }
418
+ }
419
+ if (Array.isArray(input.resources) && input.resources.length > 0) {
420
+ lines.push('Resources:');
421
+ for (const resource of input.resources) {
422
+ lines.push(\`- \${resource}\`);
423
+ }
424
+ }
425
+ return new TextEncoder().encode(lines.join('\\n'));
426
+ };
427
+
428
+ const provider = {
429
+ info: entry.info,
430
+ publicKey: null,
431
+ isConnected: false,
432
+ connect: async (options = {}) => {
433
+ if (options?.onlyIfTrusted && !trusted) {
434
+ throw walletError(4001, 'User rejected the request.');
435
+ }
436
+ if (!trusted) {
437
+ await request({
438
+ method: 'solana_requestAccounts',
439
+ params: { publicKey: publicKeyValue },
440
+ });
441
+ }
442
+ const wasConnected = connected;
443
+ connected = true;
444
+ trusted = true;
445
+ provider.isConnected = true;
446
+ provider.publicKey = publicKey;
447
+ if (!wasConnected) {
448
+ emitSolana('connect', publicKey);
449
+ emitStandardChange({ accounts: standardWallet.accounts });
450
+ }
451
+ return { publicKey };
452
+ },
453
+ disconnect: async () => {
454
+ const wasConnected = connected;
455
+ connected = false;
456
+ hiddenByController = false;
457
+ provider.isConnected = false;
458
+ provider.publicKey = null;
459
+ if (wasConnected) {
460
+ emitSolana('disconnect');
461
+ emitStandardChange({ accounts: standardWallet.accounts });
462
+ }
463
+ },
464
+ request: async ({ method, params } = {}) => {
465
+ switch (method) {
466
+ case 'connect':
467
+ return provider.connect(params ?? {});
468
+ case 'disconnect':
469
+ await provider.disconnect();
470
+ return null;
471
+ case 'getAccounts':
472
+ return visibleAccounts();
473
+ case 'requestAccounts':
474
+ await provider.connect(params ?? {});
475
+ return visibleAccounts();
476
+ case 'solana_getAccounts':
477
+ return visibleAccountObjects();
478
+ case 'solana_requestAccounts':
479
+ await provider.connect(params ?? {});
480
+ return visibleAccountObjects();
481
+ case 'signMessage':
482
+ case 'solana_signMessage':
483
+ return provider.signMessage(params?.message ?? params?.[0] ?? new Uint8Array());
484
+ case 'signTransaction':
485
+ case 'solana_signTransaction':
486
+ return provider.signTransaction(params?.transaction ?? params?.message ?? params?.[0]);
487
+ case 'signAllTransactions':
488
+ case 'solana_signAllTransactions':
489
+ return provider.signAllTransactions(transactionsFromParams(params));
490
+ case 'signAndSendTransaction':
491
+ case 'solana_signAndSendTransaction':
492
+ return provider.signAndSendTransaction(params?.transaction ?? params?.message ?? params?.[0]);
493
+ case 'signAndSendAllTransactions':
494
+ case 'solana_signAndSendAllTransactions':
495
+ return provider.signAndSendAllTransactions(transactionsFromParams(params));
496
+ case 'signIn':
497
+ case 'solana_signIn':
498
+ return provider.signIn(params ?? {});
499
+ default:
500
+ throw walletError(4200, \`The Solana wallet does not support the method "\${String(method)}".\`);
501
+ }
502
+ },
503
+ signIn: async (input = {}) => {
504
+ if (!connected) {
505
+ await provider.connect();
506
+ }
507
+ const signedMessage = buildSignInMessage(input);
508
+ await request({
509
+ method: 'solana_signIn',
510
+ params: { input, publicKey: publicKeyValue, message: [...signedMessage] },
511
+ });
512
+ return {
513
+ address: input.address ?? publicKeyValue,
514
+ publicKey,
515
+ signedMessage,
516
+ signature: deterministicSignature(publicKeyValue, signedMessage),
517
+ };
518
+ },
519
+ signMessage: async (message) => {
520
+ ensureConnected();
521
+ await request({
522
+ method: 'solana_signMessage',
523
+ params: { message, pubkey: publicKeyValue },
524
+ });
525
+ return {
526
+ publicKey,
527
+ signature: deterministicSignature(publicKeyValue, message),
528
+ };
529
+ },
530
+ signTransaction: async (transaction) => {
531
+ ensureConnected();
532
+ await request({
533
+ method: 'solana_signTransaction',
534
+ params: { transaction, pubkey: publicKeyValue },
535
+ });
536
+ return transaction;
537
+ },
538
+ signAllTransactions: async (transactions) => {
539
+ ensureConnected();
540
+ await request({
541
+ method: 'solana_signAllTransactions',
542
+ params: { transactions, pubkey: publicKeyValue },
543
+ });
544
+ return transactions;
545
+ },
546
+ signAndSendTransaction: async (transaction) => {
547
+ ensureConnected();
548
+ await request({
549
+ method: 'solana_signAndSendTransaction',
550
+ params: { transaction, pubkey: publicKeyValue },
551
+ });
552
+ const signature = deterministicSignature(publicKeyValue, transaction);
553
+ return {
554
+ signature: Array.from(signature)
555
+ .map((byte) => byte.toString(16).padStart(2, '0'))
556
+ .join(''),
557
+ };
558
+ },
559
+ signAndSendAllTransactions: async (transactions) => {
560
+ ensureConnected();
561
+ const list = Array.isArray(transactions) ? transactions : [];
562
+ await request({
563
+ method: 'solana_signAndSendAllTransactions',
564
+ params: { transactions: list, pubkey: publicKeyValue },
565
+ });
566
+ return {
567
+ publicKey,
568
+ signatures: list.map((transaction) =>
569
+ Array.from(deterministicSignature(publicKeyValue, transaction))
570
+ .map((byte) => byte.toString(16).padStart(2, '0'))
571
+ .join(''),
572
+ ),
573
+ };
574
+ },
575
+ on: (event, handler) => {
576
+ getListeners(event).add(handler);
577
+ return provider;
578
+ },
579
+ addListener: (event, handler) => provider.on(event, handler),
580
+ once: (event, handler) => {
581
+ const wrapped = (payload) => {
582
+ provider.removeListener(event, wrapped);
583
+ handler(payload);
584
+ };
585
+ provider.on(event, wrapped);
586
+ return provider;
587
+ },
588
+ removeListener: (event, handler) => {
589
+ listeners.get(event)?.delete(handler);
590
+ return provider;
591
+ },
592
+ off: (event, handler) => provider.removeListener(event, handler),
593
+ removeAllListeners: (event) => {
594
+ if (event) {
595
+ listeners.delete(event);
596
+ } else {
597
+ listeners.clear();
598
+ }
599
+ return provider;
600
+ },
601
+ listeners: (event) => [...(listeners.get(event) ?? [])],
602
+ listenerCount: (event) => listeners.get(event)?.size ?? 0,
603
+ };
604
+
605
+ const hideFromController = () => {
606
+ if (!connected) {
607
+ return;
608
+ }
609
+
610
+ connected = false;
611
+ hiddenByController = true;
612
+ provider.isConnected = false;
613
+ provider.publicKey = null;
614
+ emitSolana('accountChanged', null);
615
+ emitStandardChange({ accounts: standardWallet.accounts });
616
+ };
617
+
618
+ const restoreFromController = () => {
619
+ if (!hiddenByController) {
620
+ return;
621
+ }
622
+
623
+ hiddenByController = false;
624
+ connected = true;
625
+ trusted = true;
626
+ provider.isConnected = true;
627
+ provider.publicKey = publicKey;
628
+ emitSolana('accountChanged', publicKey);
629
+ emitStandardChange({ accounts: standardWallet.accounts });
630
+ };
631
+
632
+ const disconnectFromController = () => {
633
+ const wasVisible = connected;
634
+ const shouldEmitDisconnect = connected || hiddenByController;
635
+ connected = false;
636
+ hiddenByController = false;
637
+ provider.isConnected = false;
638
+ provider.publicKey = null;
639
+ if (wasVisible) {
640
+ emitSolana('accountChanged', null);
641
+ emitStandardChange({ accounts: standardWallet.accounts });
642
+ }
643
+ if (shouldEmitDisconnect) {
644
+ emitSolana('disconnect');
645
+ }
646
+ };
647
+
648
+ const handleControllerEvent = (event, payload) => {
649
+ if (event === 'accountsChanged') {
650
+ if (Array.isArray(payload) && payload.length > 0) {
651
+ restoreFromController();
652
+ } else {
653
+ hideFromController();
654
+ }
655
+ }
656
+ if (event === 'disconnect') {
657
+ disconnectFromController();
658
+ }
659
+ };
660
+
661
+ const standardWallet = Object.freeze({
662
+ version: '1.0.0',
663
+ name: entry.info.name,
664
+ icon: entry.info.icon,
665
+ chains: solanaChains,
666
+ get accounts() {
667
+ return connected ? Object.freeze([standardAccount]) : Object.freeze([]);
668
+ },
669
+ features: Object.freeze({
670
+ 'standard:connect': Object.freeze({
671
+ version: '1.0.0',
672
+ connect: async (input = {}) => {
673
+ await provider.connect({ onlyIfTrusted: input?.silent === true });
674
+ return { accounts: standardWallet.accounts };
675
+ },
676
+ }),
677
+ 'standard:disconnect': Object.freeze({
678
+ version: '1.0.0',
679
+ disconnect: async () => {
680
+ await provider.disconnect();
681
+ },
682
+ }),
683
+ 'standard:events': Object.freeze({
684
+ version: '1.0.0',
685
+ on: standardOn,
686
+ }),
687
+ 'solana:signIn': Object.freeze({
688
+ version: '1.0.0',
689
+ signIn: async (...inputs) => {
690
+ const signInInputs = inputs.length > 0 ? inputs : [{}];
691
+ return Promise.all(signInInputs.map(async (input) => {
692
+ const result = await provider.signIn(input);
693
+ return {
694
+ account: standardAccount,
695
+ signedMessage: result.signedMessage,
696
+ signature: result.signature,
697
+ };
698
+ }));
699
+ },
700
+ }),
701
+ 'solana:signMessage': Object.freeze({
702
+ version: '1.1.0',
703
+ signMessage: async (...inputs) => {
704
+ return Promise.all(inputs.map(async (input) => {
705
+ ensureStandardAccount(input.account);
706
+ const result = await provider.signMessage(input.message);
707
+ return {
708
+ signedMessage: input.message,
709
+ signature: result.signature,
710
+ };
711
+ }));
712
+ },
713
+ }),
714
+ 'solana:signTransaction': Object.freeze({
715
+ version: '1.0.0',
716
+ supportedTransactionVersions: Object.freeze(['legacy', 0]),
717
+ signTransaction: async (...inputs) => {
718
+ return Promise.all(inputs.map(async (input) => {
719
+ ensureStandardAccount(input.account);
720
+ const signedTransaction = await provider.signTransaction(input.transaction);
721
+ return {
722
+ signedTransaction,
723
+ };
724
+ }));
725
+ },
726
+ }),
727
+ 'solana:signAndSendTransaction': Object.freeze({
728
+ version: '1.0.0',
729
+ supportedTransactionVersions: Object.freeze(['legacy', 0]),
730
+ signAndSendTransaction: async (...inputs) => {
731
+ return Promise.all(inputs.map(async (input) => {
732
+ ensureStandardAccount(input.account);
733
+ const result = await provider.signAndSendTransaction(input.transaction);
734
+ return {
735
+ signature: hexToBytes(result.signature),
736
+ };
737
+ }));
738
+ },
739
+ }),
740
+ }),
741
+ });
742
+
743
+ for (const [flag, value] of Object.entries(solanaConfig.flags ?? {})) {
744
+ Object.defineProperty(provider, flag, {
745
+ value: Boolean(value),
746
+ configurable: true,
747
+ enumerable: true,
748
+ });
749
+ }
750
+
751
+ return { provider, aliases: solanaConfig.aliases ?? [], standardWallet, handleControllerEvent };
752
+ };
753
+
754
+ const registerStandardWallet = (wallet) => {
755
+ const register = (api) => {
756
+ if (api && typeof api.register === 'function') {
757
+ api.register(wallet);
758
+ }
759
+ };
760
+ window.addEventListener('wallet-standard:app-ready', (event) => register(event.detail));
761
+ window.dispatchEvent(
762
+ new CustomEvent('wallet-standard:register-wallet', {
763
+ detail: register,
764
+ }),
765
+ );
766
+ };
767
+
768
+ const entries = config.providers.map(createProviderEntry);
769
+ const evmEntries = entries.filter((entry) => entry.evm);
770
+ const solanaEntries = entries
771
+ .map((entry) => createSolanaProvider(entry))
772
+ .filter(Boolean);
773
+ if (evmEntries.length > 1) {
774
+ const providers = evmEntries.map((entry) => entry.provider);
775
+ for (const entry of evmEntries) {
776
+ Object.defineProperty(entry.provider, 'providers', {
777
+ value: providers,
778
+ configurable: true,
779
+ enumerable: true,
780
+ });
781
+ }
782
+ }
783
+
784
+ const emit = (event, payload) => {
785
+ if (event === 'connect') {
786
+ chainDisconnected = false;
787
+ }
788
+ if (event === 'disconnect') {
789
+ chainDisconnected = true;
790
+ }
791
+
792
+ for (const entry of evmEntries) {
793
+ if (event === 'accountsChanged') {
794
+ entry.provider.selectedAddress =
795
+ Array.isArray(payload) && payload.length > 0 ? payload[0] : null;
796
+ }
797
+
798
+ if (event === 'chainChanged') {
799
+ entry.provider.chainId = payload;
800
+ entry.provider.networkVersion = toNetworkVersion(payload);
801
+ }
802
+
803
+ const callbacks = entry.listeners.get(event);
804
+ if (callbacks) {
805
+ for (const callback of [...callbacks]) {
806
+ callback(payload);
807
+ }
808
+ }
809
+
810
+ if (event === 'chainChanged') {
811
+ const networkCallbacks = entry.listeners.get('networkChanged');
812
+ if (networkCallbacks) {
813
+ for (const callback of [...networkCallbacks]) {
814
+ callback(entry.provider.networkVersion);
815
+ }
816
+ }
817
+ }
818
+ }
819
+
820
+ for (const entry of solanaEntries) {
821
+ entry.handleControllerEvent(event, payload);
822
+ }
110
823
  };
111
824
 
112
825
  const announceProviders = () => {
113
- for (const info of config.providers) {
826
+ for (const entry of evmEntries) {
114
827
  window.dispatchEvent(
115
828
  new CustomEvent('eip6963:announceProvider', {
116
- detail: {
117
- info,
118
- provider,
119
- },
829
+ detail: Object.freeze({
830
+ info: Object.freeze({ ...entry.info }),
831
+ provider: entry.provider,
832
+ }),
120
833
  }),
121
834
  );
122
835
  }
123
836
  };
124
837
 
125
- Object.defineProperty(window, 'ethereum', {
126
- value: provider,
127
- configurable: true,
128
- enumerable: true,
129
- writable: true,
130
- });
838
+ if (evmEntries[0]) {
839
+ Object.defineProperty(window, 'ethereum', {
840
+ value: evmEntries[0].provider,
841
+ configurable: true,
842
+ enumerable: true,
843
+ writable: true,
844
+ });
845
+ }
846
+
847
+ const assignProviderAlias = (path, provider) => {
848
+ const parts = String(path).split('.').filter(Boolean);
849
+ if (parts.length === 0 || (parts.length === 1 && parts[0] === 'ethereum')) {
850
+ return;
851
+ }
852
+
853
+ let target = window;
854
+ for (const part of parts.slice(0, -1)) {
855
+ const current = target[part];
856
+ if (
857
+ current === null ||
858
+ (typeof current !== 'object' && typeof current !== 'function')
859
+ ) {
860
+ Object.defineProperty(target, part, {
861
+ value: {},
862
+ configurable: true,
863
+ enumerable: true,
864
+ writable: true,
865
+ });
866
+ }
867
+ target = target[part];
868
+ }
869
+
870
+ Object.defineProperty(target, parts[parts.length - 1], {
871
+ value: provider,
872
+ configurable: true,
873
+ enumerable: true,
874
+ writable: true,
875
+ });
876
+ };
877
+
878
+ for (const entry of evmEntries) {
879
+ for (const alias of entry.aliases) {
880
+ assignProviderAlias(alias, entry.provider);
881
+ }
882
+ }
883
+
884
+ for (const entry of solanaEntries) {
885
+ for (const alias of entry.aliases) {
886
+ assignProviderAlias(alias, entry.provider);
887
+ }
888
+ registerStandardWallet(entry.standardWallet);
889
+ }
131
890
 
132
891
  Object.defineProperty(window, '${EMITTER_NAME}', {
133
892
  value: emit,
@@ -136,6 +895,33 @@ export const buildInjectedProviderScript = (config) => `
136
895
 
137
896
  window.addEventListener('eip6963:requestProvider', announceProviders);
138
897
  queueMicrotask(announceProviders);
898
+
899
+ // The serialized config snapshot goes stale once the controller mutates
900
+ // accounts or chain and the page navigates; refresh the synchronous mirror
901
+ // properties from the wallet's current state as soon as the bridge is up.
902
+ const refresh = async () => {
903
+ try {
904
+ const response = await window.${BRIDGE_NAME}({
905
+ method: 'metamask_getProviderState',
906
+ params: [],
907
+ });
908
+ if (!response.ok) {
909
+ return;
910
+ }
911
+
912
+ const state = response.result;
913
+ for (const entry of evmEntries) {
914
+ entry.provider.selectedAddress =
915
+ state.isUnlocked && state.accounts.length > 0 ? state.accounts[0] : null;
916
+ entry.provider.chainId = state.chainId;
917
+ entry.provider.networkVersion = toNetworkVersion(state.chainId);
918
+ }
919
+ } catch {
920
+ // Bridge not available yet (e.g. detached frame) — sync values fall
921
+ // back to the injected config snapshot.
922
+ }
923
+ };
924
+ refresh();
139
925
  })();
140
926
  `;
141
927
  //# sourceMappingURL=injected-provider.js.map