@marigoldlabs/web3-tester 0.4.1 → 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.
- package/README.md +317 -11
- package/dist/anvil.d.ts.map +1 -1
- package/dist/anvil.js.map +1 -1
- package/dist/benchmark.d.ts +55 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +168 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/fixtures.js +2 -2
- package/dist/fixtures.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/injected-provider.d.ts.map +1 -1
- package/dist/injected-provider.js +748 -25
- package/dist/injected-provider.js.map +1 -1
- package/dist/mock-wallet-controller.d.ts +150 -2
- package/dist/mock-wallet-controller.d.ts.map +1 -1
- package/dist/mock-wallet-controller.js +613 -24
- package/dist/mock-wallet-controller.js.map +1 -1
- package/dist/private-key-rpc-client.d.ts +144 -132
- package/dist/private-key-rpc-client.d.ts.map +1 -1
- package/dist/private-key-rpc-client.js +142 -20
- package/dist/private-key-rpc-client.js.map +1 -1
- package/dist/real-wallet-cache.d.ts +38 -0
- package/dist/real-wallet-cache.d.ts.map +1 -1
- package/dist/real-wallet-cache.js +143 -57
- package/dist/real-wallet-cache.js.map +1 -1
- package/dist/real-wallet-extension-fixtures.d.ts +35 -0
- package/dist/real-wallet-extension-fixtures.d.ts.map +1 -0
- package/dist/real-wallet-extension-fixtures.js +96 -0
- package/dist/real-wallet-extension-fixtures.js.map +1 -0
- package/dist/real-wallet-extension.d.ts +86 -0
- package/dist/real-wallet-extension.d.ts.map +1 -0
- package/dist/real-wallet-extension.js +245 -0
- package/dist/real-wallet-extension.js.map +1 -0
- package/dist/real-wallet-fixtures.d.ts.map +1 -1
- package/dist/real-wallet-fixtures.js +46 -31
- package/dist/real-wallet-fixtures.js.map +1 -1
- package/dist/real-wallet.d.ts +5 -14
- package/dist/real-wallet.d.ts.map +1 -1
- package/dist/real-wallet.js +668 -435
- package/dist/real-wallet.js.map +1 -1
- package/dist/safe.d.ts +311 -0
- package/dist/safe.d.ts.map +1 -0
- package/dist/safe.js +743 -0
- package/dist/safe.js.map +1 -0
- package/dist/types.d.ts +35 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/wallet-personas.d.ts +99 -0
- package/dist/wallet-personas.d.ts.map +1 -0
- package/dist/wallet-personas.js +666 -0
- package/dist/wallet-personas.js.map +1 -0
- package/dist/walletconnect.d.ts +176 -9
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +514 -74
- package/dist/walletconnect.js.map +1 -1
- package/examples/playwright.config.ts +8 -0
- package/package.json +29 -3
|
@@ -17,6 +17,95 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
17
17
|
|
|
18
18
|
const toNetworkVersion = (chainId) => String(Number(BigInt(chainId)));
|
|
19
19
|
|
|
20
|
+
const walletError = (code, message) => {
|
|
21
|
+
const error = new Error(message);
|
|
22
|
+
error.code = code;
|
|
23
|
+
return error;
|
|
24
|
+
};
|
|
25
|
+
|
|
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
|
+
}
|
|
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
|
+
};
|
|
69
|
+
|
|
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);
|
|
82
|
+
}
|
|
83
|
+
if (typeof input === 'string') {
|
|
84
|
+
return [...new TextEncoder().encode(input)];
|
|
85
|
+
}
|
|
86
|
+
return [...new TextEncoder().encode(JSON.stringify(input ?? null))];
|
|
87
|
+
};
|
|
88
|
+
|
|
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;
|
|
96
|
+
}
|
|
97
|
+
return signature;
|
|
98
|
+
};
|
|
99
|
+
|
|
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);
|
|
105
|
+
}
|
|
106
|
+
return bytes;
|
|
107
|
+
};
|
|
108
|
+
|
|
20
109
|
// EIP-1193 connectivity means "can the provider reach the chain", which is
|
|
21
110
|
// independent of account authorization. The controller's disconnect()
|
|
22
111
|
// simulates losing the chain via the 'disconnect' event.
|
|
@@ -47,7 +136,9 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
47
136
|
// One distinct provider object per announced wallet so selector tests can
|
|
48
137
|
// detect a dapp talking to the wrong provider. They share request handling
|
|
49
138
|
// and state, but each has its own identity and listener registry.
|
|
50
|
-
const createProviderEntry = (
|
|
139
|
+
const createProviderEntry = (providerConfig) => {
|
|
140
|
+
const { evm = true, flags = {}, aliases = [], solana, ...info } = providerConfig;
|
|
141
|
+
const providerInfo = Object.freeze({ ...info });
|
|
51
142
|
const listeners = new Map();
|
|
52
143
|
|
|
53
144
|
const getListeners = (event) => {
|
|
@@ -57,10 +148,46 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
57
148
|
return listeners.get(event);
|
|
58
149
|
};
|
|
59
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',
|
|
162
|
+
};
|
|
163
|
+
if (error?.data !== undefined) {
|
|
164
|
+
rpcError.data = error.data;
|
|
165
|
+
}
|
|
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
|
+
|
|
60
187
|
const provider = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
188
|
+
info: providerInfo,
|
|
189
|
+
selectedAddress:
|
|
190
|
+
config.connected && config.unlocked && config.accounts.length > 0 ? config.accounts[0] : null,
|
|
64
191
|
chainId: config.chainId,
|
|
65
192
|
networkVersion: toNetworkVersion(config.chainId),
|
|
66
193
|
request,
|
|
@@ -73,19 +200,37 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
73
200
|
|
|
74
201
|
const payload = methodOrPayload;
|
|
75
202
|
const callback = paramsOrCallback;
|
|
76
|
-
|
|
77
|
-
.
|
|
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))
|
|
78
211
|
.catch((error) => callback?.(error, null));
|
|
212
|
+
return undefined;
|
|
79
213
|
},
|
|
80
214
|
sendAsync: (payload, callback) => {
|
|
81
|
-
|
|
82
|
-
|
|
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))
|
|
83
220
|
.catch((error) => callback(error, null));
|
|
84
221
|
},
|
|
85
222
|
on: (event, handler) => {
|
|
86
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
|
+
}
|
|
87
231
|
return provider;
|
|
88
232
|
},
|
|
233
|
+
addListener: (event, handler) => provider.on(event, handler),
|
|
89
234
|
once: (event, handler) => {
|
|
90
235
|
const wrapped = (payload) => {
|
|
91
236
|
provider.removeListener(event, wrapped);
|
|
@@ -98,6 +243,7 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
98
243
|
listeners.get(event)?.delete(handler);
|
|
99
244
|
return provider;
|
|
100
245
|
},
|
|
246
|
+
off: (event, handler) => provider.removeListener(event, handler),
|
|
101
247
|
removeAllListeners: (event) => {
|
|
102
248
|
if (event) {
|
|
103
249
|
listeners.delete(event);
|
|
@@ -106,15 +252,534 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
106
252
|
}
|
|
107
253
|
return provider;
|
|
108
254
|
},
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
}
|
|
111
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
|
+
}
|
|
112
659
|
};
|
|
113
660
|
|
|
114
|
-
|
|
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
|
+
);
|
|
115
766
|
};
|
|
116
767
|
|
|
117
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
|
+
}
|
|
118
783
|
|
|
119
784
|
const emit = (event, payload) => {
|
|
120
785
|
if (event === 'connect') {
|
|
@@ -124,7 +789,7 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
124
789
|
chainDisconnected = true;
|
|
125
790
|
}
|
|
126
791
|
|
|
127
|
-
for (const entry of
|
|
792
|
+
for (const entry of evmEntries) {
|
|
128
793
|
if (event === 'accountsChanged') {
|
|
129
794
|
entry.provider.selectedAddress =
|
|
130
795
|
Array.isArray(payload) && payload.length > 0 ? payload[0] : null;
|
|
@@ -136,18 +801,29 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
136
801
|
}
|
|
137
802
|
|
|
138
803
|
const callbacks = entry.listeners.get(event);
|
|
139
|
-
if (
|
|
140
|
-
|
|
804
|
+
if (callbacks) {
|
|
805
|
+
for (const callback of [...callbacks]) {
|
|
806
|
+
callback(payload);
|
|
807
|
+
}
|
|
141
808
|
}
|
|
142
809
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
817
|
}
|
|
146
818
|
}
|
|
819
|
+
|
|
820
|
+
for (const entry of solanaEntries) {
|
|
821
|
+
entry.handleControllerEvent(event, payload);
|
|
822
|
+
}
|
|
147
823
|
};
|
|
148
824
|
|
|
149
825
|
const announceProviders = () => {
|
|
150
|
-
for (const entry of
|
|
826
|
+
for (const entry of evmEntries) {
|
|
151
827
|
window.dispatchEvent(
|
|
152
828
|
new CustomEvent('eip6963:announceProvider', {
|
|
153
829
|
detail: Object.freeze({
|
|
@@ -159,12 +835,58 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
159
835
|
}
|
|
160
836
|
};
|
|
161
837
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
+
}
|
|
168
890
|
|
|
169
891
|
Object.defineProperty(window, '${EMITTER_NAME}', {
|
|
170
892
|
value: emit,
|
|
@@ -188,8 +910,9 @@ export const buildInjectedProviderScript = (config) => `
|
|
|
188
910
|
}
|
|
189
911
|
|
|
190
912
|
const state = response.result;
|
|
191
|
-
for (const entry of
|
|
192
|
-
entry.provider.selectedAddress =
|
|
913
|
+
for (const entry of evmEntries) {
|
|
914
|
+
entry.provider.selectedAddress =
|
|
915
|
+
state.isUnlocked && state.accounts.length > 0 ? state.accounts[0] : null;
|
|
193
916
|
entry.provider.chainId = state.chainId;
|
|
194
917
|
entry.provider.networkVersion = toNetworkVersion(state.chainId);
|
|
195
918
|
}
|