@wagmi/core 3.4.2 → 3.4.3
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/dist/esm/exports/tempo.js +1 -2
- package/dist/esm/exports/tempo.js.map +1 -1
- package/dist/esm/tempo/Connectors.js +278 -570
- package/dist/esm/tempo/Connectors.js.map +1 -1
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/exports/tempo.d.ts +1 -2
- package/dist/types/exports/tempo.d.ts.map +1 -1
- package/dist/types/tempo/Connectors.d.ts +58 -86
- package/dist/types/tempo/Connectors.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +5 -1
- package/src/exports/tempo.ts +2 -1
- package/src/tempo/Connectors.ts +406 -798
- package/src/version.ts +1 -1
- package/dist/esm/tempo/KeyManager.js +0 -154
- package/dist/esm/tempo/KeyManager.js.map +0 -1
- package/dist/types/tempo/KeyManager.d.ts +0 -71
- package/dist/types/tempo/KeyManager.d.ts.map +0 -1
- package/src/tempo/KeyManager.ts +0 -241
|
@@ -1,478 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as PublicKey from 'ox/PublicKey';
|
|
3
|
-
import { KeyAuthorization, SignatureEnvelope } from 'ox/tempo';
|
|
4
|
-
import { createClient, defineChain, getAddress, SwitchChainError, } from 'viem';
|
|
5
|
-
import { generatePrivateKey, privateKeyToAccount, } from 'viem/accounts';
|
|
6
|
-
import { Account, WebAuthnP256, WebCryptoP256, walletNamespaceCompat, } from 'viem/tempo';
|
|
1
|
+
import { numberToHex, SwitchChainError, UserRejectedRequestError, withRetry, } from 'viem';
|
|
7
2
|
import { createConnector } from '../connectors/createConnector.js';
|
|
8
3
|
import { ChainNotConfiguredError } from '../errors/config.js';
|
|
9
|
-
|
|
4
|
+
const tempoWalletIcon = 'data:image/svg+xml,<svg width="269" height="269" viewBox="0 0 269 269" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="269" height="269" fill="black"/><path d="M123.273 190.794H93.445L121.09 105.318H85.7334L93.445 80.2642H191.95L184.238 105.318H150.773L123.273 190.794Z" fill="white"/></svg>';
|
|
10
5
|
/**
|
|
11
|
-
* Connector for
|
|
6
|
+
* Connector for the Tempo Wallet dialog.
|
|
12
7
|
*/
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (options.grantAccessKey === true)
|
|
24
|
-
return defaultAccessKeyOptions;
|
|
25
|
-
return undefined;
|
|
26
|
-
})();
|
|
27
|
-
return createConnector((config) => ({
|
|
28
|
-
id: 'webAuthn',
|
|
29
|
-
name: 'EOA (WebAuthn)',
|
|
30
|
-
type: 'webAuthn',
|
|
31
|
-
async setup() {
|
|
32
|
-
const credential = await config.storage?.getItem('webAuthn.activeCredential');
|
|
33
|
-
if (!credential)
|
|
34
|
-
return;
|
|
35
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
36
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
async connect(parameters = {}) {
|
|
40
|
-
const capabilities = 'capabilities' in parameters ? (parameters.capabilities ?? {}) : {};
|
|
41
|
-
const signHash = 'sign' in capabilities ? capabilities.sign?.hash : undefined;
|
|
42
|
-
// Fast path: if a credential is provided directly, use it.
|
|
43
|
-
if ('credential' in capabilities && capabilities.credential) {
|
|
44
|
-
const credential = capabilities.credential;
|
|
45
|
-
config.storage?.setItem('webAuthn.activeCredential', normalizeValue(credential));
|
|
46
|
-
config.storage?.setItem('webAuthn.lastActiveCredential', normalizeValue(credential));
|
|
47
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
48
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
49
|
-
});
|
|
50
|
-
const address = getAddress(account.address);
|
|
51
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id;
|
|
52
|
-
if (!chainId)
|
|
53
|
-
throw new ChainNotConfiguredError();
|
|
54
|
-
return {
|
|
55
|
-
accounts: (parameters.withCapabilities
|
|
56
|
-
? [{ address }]
|
|
57
|
-
: [address]),
|
|
58
|
-
chainId,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (accessKeyOptions?.strict &&
|
|
62
|
-
accessKeyOptions.expiry &&
|
|
63
|
-
accessKeyOptions.expiry < Date.now() / 1000)
|
|
64
|
-
throw new Error(`\`grantAccessKey.expiry = ${accessKeyOptions.expiry}\` is in the past (${new Date(accessKeyOptions.expiry * 1000).toLocaleString()}). Please provide a valid expiry.`);
|
|
65
|
-
// We are going to need to find:
|
|
66
|
-
// - a WebAuthn `credential` to instantiate an account
|
|
67
|
-
// - optionally, a `keyPair` to use as the access key for the account
|
|
68
|
-
// - optionally, a signed `keyAuthorization` to provision the access key
|
|
69
|
-
const { credential, keyAuthorization, keyPair, signature: signedHash, } = await (async () => {
|
|
70
|
-
// If the connection type is of "sign-up", we are going to create a new credential
|
|
71
|
-
// and provision an access key (if needed).
|
|
72
|
-
if (capabilities.type === 'sign-up') {
|
|
73
|
-
// Create credential (sign up)
|
|
74
|
-
const createOptions_remote = await options.keyManager.getChallenge?.();
|
|
75
|
-
const label = capabilities.label ??
|
|
76
|
-
options.createOptions?.label ??
|
|
77
|
-
new Date().toISOString();
|
|
78
|
-
const rpId = createOptions_remote?.rp?.id ??
|
|
79
|
-
options.createOptions?.rpId ??
|
|
80
|
-
options.rpId;
|
|
81
|
-
const credential = await WebAuthnP256.createCredential({
|
|
82
|
-
...(options.createOptions ?? {}),
|
|
83
|
-
label,
|
|
84
|
-
rpId,
|
|
85
|
-
...(createOptions_remote ?? {}),
|
|
86
|
-
});
|
|
87
|
-
await options.keyManager.setPublicKey({
|
|
88
|
-
credential: credential.raw,
|
|
89
|
-
publicKey: credential.publicKey,
|
|
90
|
-
});
|
|
91
|
-
// Get key pair (access key) to use for the account.
|
|
92
|
-
// Skip if signing a hash — access key provisioning is deferred.
|
|
93
|
-
const keyPair = await (async () => {
|
|
94
|
-
if (signHash)
|
|
95
|
-
return undefined;
|
|
96
|
-
if (!accessKeyOptions)
|
|
97
|
-
return undefined;
|
|
98
|
-
return await WebCryptoP256.createKeyPair();
|
|
99
|
-
})();
|
|
100
|
-
return { credential, keyPair, signature: undefined };
|
|
101
|
-
}
|
|
102
|
-
// If we are not selecting an account, we will check if an active credential is present in
|
|
103
|
-
// storage and if so, we will use it to instantiate an account.
|
|
104
|
-
if (!capabilities.selectAccount) {
|
|
105
|
-
const credential = (await config.storage?.getItem('webAuthn.activeCredential'));
|
|
106
|
-
if (credential) {
|
|
107
|
-
// If signing a hash, skip local keypair checks and return
|
|
108
|
-
// the stored credential — the hash will be signed via
|
|
109
|
-
// `account.sign` since `createCredential` cannot sign.
|
|
110
|
-
if (signHash)
|
|
111
|
-
return { credential, keyPair: undefined, signature: undefined };
|
|
112
|
-
// Get key pair (access key) to use for the account.
|
|
113
|
-
const keyPair = await (async () => {
|
|
114
|
-
if (!accessKeyOptions)
|
|
115
|
-
return undefined;
|
|
116
|
-
const address = Address.fromPublicKey(PublicKey.fromHex(credential.publicKey));
|
|
117
|
-
return await idb.get(`accessKey:${address}`);
|
|
118
|
-
})();
|
|
119
|
-
// If the access key provisioning is not in strict mode, return the credential and key pair (if exists).
|
|
120
|
-
if (!accessKeyOptions?.strict)
|
|
121
|
-
return { credential, keyPair, signature: undefined };
|
|
122
|
-
// If a key pair is found, return the credential and key pair.
|
|
123
|
-
if (keyPair)
|
|
124
|
-
return { credential, keyPair, signature: undefined };
|
|
125
|
-
// If we are reconnecting, throw an error if not found.
|
|
126
|
-
if (parameters.isReconnecting)
|
|
127
|
-
throw new Error('credential not found.');
|
|
128
|
-
// Otherwise, we want to continue to sign up or register against new key pair.
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
// Discover credential
|
|
132
|
-
{
|
|
133
|
-
// Get key pair (access key) to use for the account.
|
|
134
|
-
// Skip if signing a hash — access key provisioning is deferred.
|
|
135
|
-
const keyPair = await (async () => {
|
|
136
|
-
if (signHash)
|
|
137
|
-
return undefined;
|
|
138
|
-
if (!accessKeyOptions)
|
|
139
|
-
return undefined;
|
|
140
|
-
return await WebCryptoP256.createKeyPair();
|
|
141
|
-
})();
|
|
142
|
-
// If we are provisioning an access key, we will need to sign a key authorization.
|
|
143
|
-
// We will need the hash (digest) to sign, and the address of the access key to construct the key authorization.
|
|
144
|
-
const { hash, keyAuthorization_unsigned } = await (async () => {
|
|
145
|
-
const accessKeyAddress = keyPair
|
|
146
|
-
? Address.fromPublicKey(keyPair.publicKey)
|
|
147
|
-
: undefined;
|
|
148
|
-
if (!accessKeyAddress)
|
|
149
|
-
return { keyAuthorization_unsigned: undefined, hash: undefined };
|
|
150
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id;
|
|
151
|
-
const keyAuthorization_unsigned = KeyAuthorization.from({
|
|
152
|
-
address: accessKeyAddress,
|
|
153
|
-
chainId: chainId ? BigInt(chainId) : undefined,
|
|
154
|
-
expiry: accessKeyOptions?.expiry,
|
|
155
|
-
strict: accessKeyOptions?.strict ?? false,
|
|
156
|
-
type: 'p256',
|
|
157
|
-
});
|
|
158
|
-
const hash = KeyAuthorization.getSignPayload(keyAuthorization_unsigned);
|
|
159
|
-
return { keyAuthorization_unsigned, hash };
|
|
160
|
-
})();
|
|
161
|
-
// If no active credential, we will attempt to load the last active credential from storage.
|
|
162
|
-
const lastActiveCredential = !capabilities.selectAccount
|
|
163
|
-
? await config.storage?.getItem('webAuthn.lastActiveCredential')
|
|
164
|
-
: undefined;
|
|
165
|
-
const credential = await WebAuthnP256.getCredential({
|
|
166
|
-
...(options.getOptions ?? {}),
|
|
167
|
-
credentialId: lastActiveCredential?.id,
|
|
168
|
-
async getPublicKey(credential) {
|
|
169
|
-
const publicKey = await options.keyManager.getPublicKey({
|
|
170
|
-
credential,
|
|
171
|
-
});
|
|
172
|
-
if (!publicKey)
|
|
173
|
-
throw new Error('publicKey not found.');
|
|
174
|
-
return publicKey;
|
|
175
|
-
},
|
|
176
|
-
hash: signHash ?? hash,
|
|
177
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
178
|
-
});
|
|
179
|
-
const envelope = SignatureEnvelope.from({
|
|
180
|
-
metadata: credential.metadata,
|
|
181
|
-
signature: credential.signature,
|
|
182
|
-
publicKey: PublicKey.fromHex(credential.publicKey),
|
|
183
|
-
type: 'webAuthn',
|
|
184
|
-
});
|
|
185
|
-
const keyAuthorization = keyAuthorization_unsigned
|
|
186
|
-
? KeyAuthorization.from({
|
|
187
|
-
...keyAuthorization_unsigned,
|
|
188
|
-
signature: envelope,
|
|
189
|
-
})
|
|
190
|
-
: undefined;
|
|
191
|
-
const signature = signHash && !keyAuthorization_unsigned
|
|
192
|
-
? SignatureEnvelope.serialize(envelope)
|
|
193
|
-
: undefined;
|
|
194
|
-
return { credential, keyAuthorization, keyPair, signature };
|
|
195
|
-
}
|
|
196
|
-
})();
|
|
197
|
-
config.storage?.setItem('webAuthn.lastActiveCredential', normalizeValue(credential));
|
|
198
|
-
config.storage?.setItem('webAuthn.activeCredential', normalizeValue(credential));
|
|
199
|
-
account = Account.fromWebAuthnP256(credential, {
|
|
200
|
-
rpId: options.getOptions?.rpId ?? options.rpId,
|
|
201
|
-
});
|
|
202
|
-
let signature;
|
|
203
|
-
if (signHash && !signedHash) {
|
|
204
|
-
signature = await account.sign({ hash: signHash });
|
|
205
|
-
}
|
|
206
|
-
else if (signedHash) {
|
|
207
|
-
signature = signedHash;
|
|
208
|
-
}
|
|
209
|
-
else if (keyPair) {
|
|
210
|
-
accessKey = Account.fromWebCryptoP256(keyPair, {
|
|
211
|
-
access: account,
|
|
212
|
-
});
|
|
213
|
-
// If we are reconnecting, check if the access key is expired.
|
|
214
|
-
if (parameters.isReconnecting) {
|
|
215
|
-
if ('keyAuthorization' in keyPair &&
|
|
216
|
-
keyPair.keyAuthorization.expiry &&
|
|
217
|
-
keyPair.keyAuthorization.expiry < Date.now() / 1000) {
|
|
218
|
-
// remove any pending key authorizations from storage.
|
|
219
|
-
await config?.storage?.removeItem(`pendingKeyAuthorization:${account.address.toLowerCase()}`);
|
|
220
|
-
const message = `Access key expired (on ${new Date(keyPair.keyAuthorization.expiry * 1000).toLocaleString()}).`;
|
|
221
|
-
accessKey = undefined;
|
|
222
|
-
// if in strict mode, disconnect and throw an error.
|
|
223
|
-
if (accessKeyOptions?.strict) {
|
|
224
|
-
await this.disconnect();
|
|
225
|
-
throw new Error(message);
|
|
226
|
-
}
|
|
227
|
-
// otherwise, fall back to the root account.
|
|
228
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
229
|
-
console.warn(`${message} Falling back to passkey.`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// If we are not reconnecting, orchestrate the provisioning of the access key.
|
|
233
|
-
else {
|
|
234
|
-
const keyAuth = keyAuthorization ??
|
|
235
|
-
(await account.signKeyAuthorization(accessKey, {
|
|
236
|
-
...accessKeyOptions,
|
|
237
|
-
chainId: BigInt(parameters.chainId ?? config.chains[0]?.id ?? 0),
|
|
238
|
-
}));
|
|
239
|
-
await config?.storage?.setItem(`pendingKeyAuthorization:${account.address.toLowerCase()}`, keyAuth);
|
|
240
|
-
await idb.set(`accessKey:${account.address.toLowerCase()}`, {
|
|
241
|
-
...keyPair,
|
|
242
|
-
keyAuthorization: keyAuth,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
// If we are granting an access key and it is in strict mode, throw an error if the access key is not provisioned.
|
|
246
|
-
}
|
|
247
|
-
else if (accessKeyOptions?.strict) {
|
|
248
|
-
await config.storage?.removeItem('webAuthn.activeCredential');
|
|
249
|
-
throw new Error('access key not found');
|
|
250
|
-
}
|
|
251
|
-
const address = getAddress(account.address);
|
|
252
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id;
|
|
253
|
-
if (!chainId)
|
|
254
|
-
throw new ChainNotConfiguredError();
|
|
255
|
-
return {
|
|
256
|
-
accounts: (parameters.withCapabilities
|
|
257
|
-
? [{ address, capabilities: { signature } }]
|
|
258
|
-
: [address]),
|
|
259
|
-
chainId,
|
|
260
|
-
};
|
|
261
|
-
},
|
|
262
|
-
async disconnect() {
|
|
263
|
-
await config.storage?.removeItem('webAuthn.activeCredential');
|
|
264
|
-
config.emitter.emit('disconnect');
|
|
265
|
-
account = undefined;
|
|
266
|
-
},
|
|
267
|
-
async getAccounts() {
|
|
268
|
-
if (!account)
|
|
269
|
-
return [];
|
|
270
|
-
return [getAddress(account.address)];
|
|
271
|
-
},
|
|
272
|
-
async getChainId() {
|
|
273
|
-
return config.chains[0]?.id;
|
|
274
|
-
},
|
|
275
|
-
async isAuthorized() {
|
|
276
|
-
try {
|
|
277
|
-
const accounts = await this.getAccounts();
|
|
278
|
-
return !!accounts.length;
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
282
|
-
console.error('Connector.webAuthn: Failed to check authorization', error);
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
async switchChain({ chainId }) {
|
|
287
|
-
const chain = config.chains.find((chain) => chain.id === chainId);
|
|
288
|
-
if (!chain)
|
|
289
|
-
throw new SwitchChainError(new ChainNotConfiguredError());
|
|
290
|
-
return chain;
|
|
291
|
-
},
|
|
292
|
-
onAccountsChanged() { },
|
|
293
|
-
onChainChanged(chain) {
|
|
294
|
-
const chainId = Number(chain);
|
|
295
|
-
config.emitter.emit('change', { chainId });
|
|
296
|
-
},
|
|
297
|
-
async onDisconnect() {
|
|
298
|
-
config.emitter.emit('disconnect');
|
|
299
|
-
account = undefined;
|
|
300
|
-
},
|
|
301
|
-
async getClient({ chainId } = {}) {
|
|
302
|
-
const chain = config.chains.find((x) => x.id === chainId) ?? config.chains[0];
|
|
303
|
-
if (!chain)
|
|
304
|
-
throw new ChainNotConfiguredError();
|
|
305
|
-
const transports = config.transports;
|
|
306
|
-
if (!transports)
|
|
307
|
-
throw new ChainNotConfiguredError();
|
|
308
|
-
const transport = transports[chain.id];
|
|
309
|
-
if (!transport)
|
|
310
|
-
throw new ChainNotConfiguredError();
|
|
311
|
-
const targetAccount = await (async () => {
|
|
312
|
-
if (!accessKey)
|
|
313
|
-
return account;
|
|
314
|
-
if (!account)
|
|
315
|
-
throw new Error('account not found.');
|
|
316
|
-
const item = await idb.get(`accessKey:${account.address.toLowerCase()}`);
|
|
317
|
-
if (item?.keyAuthorization.expiry &&
|
|
318
|
-
item.keyAuthorization.expiry < Date.now() / 1000) {
|
|
319
|
-
// remove any pending key authorizations from storage.
|
|
320
|
-
await config?.storage?.removeItem(`pendingKeyAuthorization:${account.address.toLowerCase()}`);
|
|
321
|
-
const message = `Access key expired (on ${new Date(item.keyAuthorization.expiry * 1000).toLocaleString()}).`;
|
|
322
|
-
// if in strict mode, disconnect and throw an error.
|
|
323
|
-
if (accessKeyOptions?.strict) {
|
|
324
|
-
await this.disconnect();
|
|
325
|
-
throw new Error(message);
|
|
326
|
-
}
|
|
327
|
-
// otherwise, fall back to the root account.
|
|
328
|
-
// biome-ignore lint/suspicious/noConsole: notify
|
|
329
|
-
console.warn(`${message} Falling back to passkey.`);
|
|
330
|
-
return account;
|
|
331
|
-
}
|
|
332
|
-
return accessKey;
|
|
333
|
-
})();
|
|
334
|
-
if (!targetAccount)
|
|
335
|
-
throw new Error('account not found.');
|
|
336
|
-
const targetChain = defineChain({
|
|
337
|
-
...chain,
|
|
338
|
-
prepareTransactionRequest: [
|
|
339
|
-
async (args, { phase }) => {
|
|
340
|
-
const keyAuthorization = await (async () => {
|
|
341
|
-
{
|
|
342
|
-
const keyAuthorization = args.keyAuthorization;
|
|
343
|
-
if (keyAuthorization)
|
|
344
|
-
return keyAuthorization;
|
|
345
|
-
}
|
|
346
|
-
const keyAuthorization = await config.storage?.getItem(`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`);
|
|
347
|
-
await config.storage?.removeItem(`pendingKeyAuthorization:${targetAccount?.address.toLowerCase()}`);
|
|
348
|
-
return keyAuthorization;
|
|
349
|
-
})();
|
|
350
|
-
const [prepareTransactionRequestFn, options] = (() => {
|
|
351
|
-
if (!chain.prepareTransactionRequest)
|
|
352
|
-
return [undefined, undefined];
|
|
353
|
-
if (typeof chain.prepareTransactionRequest === 'function')
|
|
354
|
-
return [chain.prepareTransactionRequest, undefined];
|
|
355
|
-
return chain.prepareTransactionRequest;
|
|
356
|
-
})();
|
|
357
|
-
const request = await (async () => {
|
|
358
|
-
if (!prepareTransactionRequestFn)
|
|
359
|
-
return {};
|
|
360
|
-
if (!options || options?.runAt?.includes(phase))
|
|
361
|
-
return await prepareTransactionRequestFn(args, { phase });
|
|
362
|
-
return {};
|
|
363
|
-
})();
|
|
364
|
-
return {
|
|
365
|
-
...args,
|
|
366
|
-
...request,
|
|
367
|
-
keyAuthorization,
|
|
368
|
-
};
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
runAt: [
|
|
372
|
-
'afterFillParameters',
|
|
373
|
-
'beforeFillParameters',
|
|
374
|
-
'beforeFillTransaction',
|
|
375
|
-
],
|
|
376
|
-
},
|
|
377
|
-
],
|
|
8
|
+
export function tempoWallet(parameters = {}) {
|
|
9
|
+
const { dialog: dialogOption, host, icon = tempoWalletIcon, name, rdns, ...providerParameters } = parameters;
|
|
10
|
+
return _setup({
|
|
11
|
+
createAdapter(accounts) {
|
|
12
|
+
return accounts.dialog({
|
|
13
|
+
dialog: dialogOption,
|
|
14
|
+
host,
|
|
15
|
+
icon,
|
|
16
|
+
name,
|
|
17
|
+
rdns,
|
|
378
18
|
});
|
|
379
|
-
return createClient({
|
|
380
|
-
account: targetAccount,
|
|
381
|
-
chain: targetChain,
|
|
382
|
-
transport: walletNamespaceCompat(transport, {
|
|
383
|
-
account: targetAccount,
|
|
384
|
-
}),
|
|
385
|
-
});
|
|
386
|
-
},
|
|
387
|
-
async getProvider({ chainId } = {}) {
|
|
388
|
-
const { request } = await this.getClient({ chainId });
|
|
389
|
-
return { request };
|
|
390
19
|
},
|
|
391
|
-
|
|
20
|
+
icon,
|
|
21
|
+
id: rdns ?? 'xyz.tempo',
|
|
22
|
+
name: name ?? 'Tempo Wallet',
|
|
23
|
+
providerParameters,
|
|
24
|
+
rdns: rdns ?? 'xyz.tempo',
|
|
25
|
+
type: 'injected',
|
|
26
|
+
});
|
|
392
27
|
}
|
|
28
|
+
webAuthn.type = 'webAuthn';
|
|
393
29
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone
|
|
30
|
+
* Connector for a WebAuthn EOA.
|
|
397
31
|
*/
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
for (const [k, v] of Object.entries(value))
|
|
414
|
-
normalized[k] = normalizeValue(v);
|
|
415
|
-
return normalized;
|
|
32
|
+
export function webAuthn(parameters = {}) {
|
|
33
|
+
const { authUrl, ceremony, icon, name, rdns, ...providerParameters } = parameters;
|
|
34
|
+
return _setup({
|
|
35
|
+
createAdapter(accounts) {
|
|
36
|
+
return ceremony
|
|
37
|
+
? accounts.webAuthn({ ceremony, icon, name, rdns })
|
|
38
|
+
: accounts.webAuthn({ authUrl, icon, name, rdns });
|
|
39
|
+
},
|
|
40
|
+
icon,
|
|
41
|
+
id: 'webAuthn',
|
|
42
|
+
name: name ?? 'EOA (WebAuthn)',
|
|
43
|
+
providerParameters,
|
|
44
|
+
rdns,
|
|
45
|
+
type: 'webAuthn',
|
|
46
|
+
});
|
|
416
47
|
}
|
|
417
|
-
// Based on `idb-keyval`
|
|
418
|
-
// https://github.com/jakearchibald/idb-keyval
|
|
419
|
-
let defaultGetStoreFunc;
|
|
420
|
-
const idb = {
|
|
421
|
-
/**
|
|
422
|
-
* Get a value by its key.
|
|
423
|
-
*
|
|
424
|
-
* @param key
|
|
425
|
-
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
426
|
-
*/
|
|
427
|
-
get(key) {
|
|
428
|
-
return this.defaultGetStore()('readonly', (store) => this.promisifyRequest(store.get(key)));
|
|
429
|
-
},
|
|
430
|
-
/**
|
|
431
|
-
* Set a value with a key.
|
|
432
|
-
*
|
|
433
|
-
* @param key
|
|
434
|
-
* @param value
|
|
435
|
-
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
|
436
|
-
*/
|
|
437
|
-
set(key, value) {
|
|
438
|
-
return this.defaultGetStore()('readwrite', (store) => {
|
|
439
|
-
store.put(value, key);
|
|
440
|
-
return this.promisifyRequest(store.transaction);
|
|
441
|
-
});
|
|
442
|
-
},
|
|
443
|
-
defaultGetStore() {
|
|
444
|
-
if (!defaultGetStoreFunc)
|
|
445
|
-
defaultGetStoreFunc = this.createStore('keyval-store', 'keyval');
|
|
446
|
-
return defaultGetStoreFunc;
|
|
447
|
-
},
|
|
448
|
-
createStore(dbName, storeName) {
|
|
449
|
-
let dbp;
|
|
450
|
-
const getDB = () => {
|
|
451
|
-
if (dbp)
|
|
452
|
-
return dbp;
|
|
453
|
-
const request = indexedDB.open(dbName);
|
|
454
|
-
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
|
455
|
-
dbp = this.promisifyRequest(request);
|
|
456
|
-
dbp.then((db) => {
|
|
457
|
-
// It seems like Safari sometimes likes to just close the connection.
|
|
458
|
-
// It's supposed to fire this event when that happens. Let's hope it does!
|
|
459
|
-
db.onclose = () => {
|
|
460
|
-
dbp = undefined;
|
|
461
|
-
};
|
|
462
|
-
}, () => { });
|
|
463
|
-
return dbp;
|
|
464
|
-
};
|
|
465
|
-
return (txMode, callback) => getDB().then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
|
|
466
|
-
},
|
|
467
|
-
promisifyRequest(request) {
|
|
468
|
-
return new Promise((resolve, reject) => {
|
|
469
|
-
// @ts-ignore - file size hacks
|
|
470
|
-
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
|
471
|
-
// @ts-ignore - file size hacks
|
|
472
|
-
request.onabort = request.onerror = () => reject(request.error);
|
|
473
|
-
});
|
|
474
|
-
},
|
|
475
|
-
};
|
|
476
48
|
dangerous_secp256k1.type = 'dangerous_secp256k1';
|
|
477
49
|
/**
|
|
478
50
|
* Connector for a Secp256k1 EOA.
|
|
@@ -481,119 +53,255 @@ dangerous_secp256k1.type = 'dangerous_secp256k1';
|
|
|
481
53
|
* This connector stores private keys in clear text, and are bound to the session
|
|
482
54
|
* length of the storage used.
|
|
483
55
|
*/
|
|
484
|
-
export function dangerous_secp256k1(
|
|
485
|
-
|
|
486
|
-
return
|
|
56
|
+
export function dangerous_secp256k1(parameters = {}) {
|
|
57
|
+
const { icon, name, privateKey, rdns, ...providerParameters } = parameters;
|
|
58
|
+
return _setup({
|
|
59
|
+
createAdapter(accounts) {
|
|
60
|
+
return accounts.dangerous_secp256k1({ icon, name, privateKey, rdns });
|
|
61
|
+
},
|
|
62
|
+
icon,
|
|
487
63
|
id: 'secp256k1',
|
|
488
|
-
name: 'EOA (Secp256k1)',
|
|
64
|
+
name: name ?? 'EOA (Secp256k1)',
|
|
65
|
+
providerParameters,
|
|
66
|
+
rdns,
|
|
489
67
|
type: 'secp256k1',
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
account = options.account;
|
|
499
|
-
},
|
|
500
|
-
async connect(parameters = {}) {
|
|
501
|
-
const address = await (async () => {
|
|
502
|
-
if ('capabilities' in parameters &&
|
|
503
|
-
parameters.capabilities?.type === 'sign-up') {
|
|
504
|
-
const privateKey = generatePrivateKey();
|
|
505
|
-
const account = privateKeyToAccount(privateKey);
|
|
506
|
-
const address = account.address;
|
|
507
|
-
await config.storage?.setItem(`secp256k1.${address}.privateKey`, privateKey);
|
|
508
|
-
await config.storage?.setItem('secp256k1.activeAddress', address);
|
|
509
|
-
await config.storage?.setItem('secp256k1.lastActiveAddress', address);
|
|
510
|
-
return address;
|
|
511
|
-
}
|
|
512
|
-
const address = await config.storage?.getItem('secp256k1.lastActiveAddress');
|
|
513
|
-
const privateKey = await config.storage?.getItem(`secp256k1.${address}.privateKey`);
|
|
514
|
-
if (privateKey)
|
|
515
|
-
account = privateKeyToAccount(privateKey);
|
|
516
|
-
else if (options.account) {
|
|
517
|
-
account = options.account;
|
|
518
|
-
await config.storage?.setItem('secp256k1.lastActiveAddress', account.address);
|
|
519
|
-
}
|
|
520
|
-
if (!account)
|
|
521
|
-
throw new Error('account not found.');
|
|
522
|
-
await config.storage?.setItem('secp256k1.activeAddress', account.address);
|
|
523
|
-
return account.address;
|
|
524
|
-
})();
|
|
525
|
-
const chainId = parameters.chainId ?? config.chains[0]?.id;
|
|
526
|
-
if (!chainId)
|
|
527
|
-
throw new ChainNotConfiguredError();
|
|
528
|
-
return {
|
|
529
|
-
accounts: (parameters.withCapabilities
|
|
530
|
-
? [{ address }]
|
|
531
|
-
: [address]),
|
|
532
|
-
chainId,
|
|
533
|
-
};
|
|
534
|
-
},
|
|
535
|
-
async disconnect() {
|
|
536
|
-
await config.storage?.removeItem('secp256k1.activeAddress');
|
|
537
|
-
account = undefined;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function createAccountsStorage(storage, namespace) {
|
|
71
|
+
const prefix = `accounts.${namespace}`;
|
|
72
|
+
return {
|
|
73
|
+
async getItem(key) {
|
|
74
|
+
return ((await storage.getItem(`${prefix}.${key}`, null)) ??
|
|
75
|
+
null);
|
|
538
76
|
},
|
|
539
|
-
async
|
|
540
|
-
|
|
541
|
-
return [];
|
|
542
|
-
return [getAddress(account.address)];
|
|
77
|
+
async removeItem(key) {
|
|
78
|
+
await storage.removeItem(`${prefix}.${key}`);
|
|
543
79
|
},
|
|
544
|
-
async
|
|
545
|
-
|
|
80
|
+
async setItem(key, value) {
|
|
81
|
+
await storage.setItem(`${prefix}.${key}`, value);
|
|
546
82
|
},
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
console.error('Connector.secp256k1: Failed to check authorization', error);
|
|
555
|
-
return false;
|
|
556
|
-
}
|
|
557
|
-
},
|
|
558
|
-
async switchChain({ chainId }) {
|
|
559
|
-
const chain = config.chains.find((chain) => chain.id === chainId);
|
|
560
|
-
if (!chain)
|
|
561
|
-
throw new SwitchChainError(new ChainNotConfiguredError());
|
|
562
|
-
return chain;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createMemoryAccountsStorage() {
|
|
86
|
+
const map = new Map();
|
|
87
|
+
return {
|
|
88
|
+
async getItem(key) {
|
|
89
|
+
return (map.get(key) ?? null);
|
|
563
90
|
},
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const chainId = Number(chain);
|
|
567
|
-
config.emitter.emit('change', { chainId });
|
|
91
|
+
async removeItem(key) {
|
|
92
|
+
map.delete(key);
|
|
568
93
|
},
|
|
569
|
-
async
|
|
570
|
-
|
|
571
|
-
account = undefined;
|
|
94
|
+
async setItem(key, value) {
|
|
95
|
+
map.set(key, value);
|
|
572
96
|
},
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
account,
|
|
587
|
-
chain,
|
|
588
|
-
transport: walletNamespaceCompat(transport, {
|
|
589
|
-
account,
|
|
590
|
-
}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function _setup(parameters) {
|
|
100
|
+
return createConnector((config) => {
|
|
101
|
+
const chains = config.chains;
|
|
102
|
+
let providerPromise;
|
|
103
|
+
let accountsChanged;
|
|
104
|
+
let chainChanged;
|
|
105
|
+
let connect;
|
|
106
|
+
let disconnect;
|
|
107
|
+
async function getAccountsModule() {
|
|
108
|
+
return await import('accounts').catch(() => {
|
|
109
|
+
throw new Error('dependency "accounts" not found');
|
|
591
110
|
});
|
|
592
|
-
}
|
|
593
|
-
async getProvider(
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
111
|
+
}
|
|
112
|
+
async function getProvider() {
|
|
113
|
+
providerPromise ??= (async () => {
|
|
114
|
+
const accounts = await getAccountsModule();
|
|
115
|
+
return accounts.Provider.create({
|
|
116
|
+
...parameters.providerParameters,
|
|
117
|
+
adapter: parameters.createAdapter(accounts),
|
|
118
|
+
chains: config.chains,
|
|
119
|
+
storage: parameters.providerParameters.storage ??
|
|
120
|
+
(config.storage
|
|
121
|
+
? createAccountsStorage(config.storage, parameters.id)
|
|
122
|
+
: createMemoryAccountsStorage()),
|
|
123
|
+
});
|
|
124
|
+
})();
|
|
125
|
+
return await providerPromise;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
icon: parameters.icon,
|
|
129
|
+
id: parameters.id,
|
|
130
|
+
name: parameters.name,
|
|
131
|
+
rdns: parameters.rdns,
|
|
132
|
+
type: parameters.type,
|
|
133
|
+
async connect(connectParameters = {}) {
|
|
134
|
+
const { chainId, isReconnecting, withCapabilities } = connectParameters;
|
|
135
|
+
const capabilities = 'capabilities' in connectParameters
|
|
136
|
+
? connectParameters.capabilities
|
|
137
|
+
: undefined;
|
|
138
|
+
let accounts = [];
|
|
139
|
+
let currentChainId;
|
|
140
|
+
if (isReconnecting) {
|
|
141
|
+
accounts = await this.getAccounts()
|
|
142
|
+
.then((accounts) => accounts.map((address) => ({ address, capabilities: {} })))
|
|
143
|
+
.catch(() => []);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
if (!accounts.length && !isReconnecting) {
|
|
147
|
+
const provider = await getProvider();
|
|
148
|
+
const response = (await provider.request({
|
|
149
|
+
method: 'wallet_connect',
|
|
150
|
+
params: [
|
|
151
|
+
{
|
|
152
|
+
...(chainId ? { chainId } : {}),
|
|
153
|
+
...(capabilities ? { capabilities } : {}),
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
}));
|
|
157
|
+
accounts = response.accounts;
|
|
158
|
+
}
|
|
159
|
+
currentChainId ??= await this.getChainId();
|
|
160
|
+
if (!currentChainId)
|
|
161
|
+
throw new ChainNotConfiguredError();
|
|
162
|
+
const provider = await getProvider();
|
|
163
|
+
if (connect) {
|
|
164
|
+
provider.removeListener('connect', connect);
|
|
165
|
+
connect = undefined;
|
|
166
|
+
}
|
|
167
|
+
if (!accountsChanged) {
|
|
168
|
+
accountsChanged = this.onAccountsChanged.bind(this);
|
|
169
|
+
provider.on('accountsChanged', accountsChanged);
|
|
170
|
+
}
|
|
171
|
+
if (!chainChanged) {
|
|
172
|
+
chainChanged = this.onChainChanged.bind(this);
|
|
173
|
+
provider.on('chainChanged', chainChanged);
|
|
174
|
+
}
|
|
175
|
+
if (!disconnect) {
|
|
176
|
+
disconnect = this.onDisconnect.bind(this);
|
|
177
|
+
provider.on('disconnect', disconnect);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
accounts: (withCapabilities
|
|
181
|
+
? accounts
|
|
182
|
+
: accounts.map((account) => account.address)),
|
|
183
|
+
chainId: currentChainId,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const rpcError = error;
|
|
188
|
+
if (rpcError.code === UserRejectedRequestError.code)
|
|
189
|
+
throw new UserRejectedRequestError(rpcError);
|
|
190
|
+
throw rpcError;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async disconnect() {
|
|
194
|
+
const provider = await getProvider();
|
|
195
|
+
if (chainChanged) {
|
|
196
|
+
provider.removeListener('chainChanged', chainChanged);
|
|
197
|
+
chainChanged = undefined;
|
|
198
|
+
}
|
|
199
|
+
if (disconnect) {
|
|
200
|
+
provider.removeListener('disconnect', disconnect);
|
|
201
|
+
disconnect = undefined;
|
|
202
|
+
}
|
|
203
|
+
if (!connect) {
|
|
204
|
+
connect = this.onConnect?.bind(this);
|
|
205
|
+
if (connect)
|
|
206
|
+
provider.on('connect', connect);
|
|
207
|
+
}
|
|
208
|
+
await provider.request({ method: 'wallet_disconnect' });
|
|
209
|
+
},
|
|
210
|
+
async getAccounts() {
|
|
211
|
+
const provider = await getProvider();
|
|
212
|
+
return await provider.request({ method: 'eth_accounts' });
|
|
213
|
+
},
|
|
214
|
+
async getChainId() {
|
|
215
|
+
const provider = await getProvider();
|
|
216
|
+
return Number(await provider.request({ method: 'eth_chainId' }));
|
|
217
|
+
},
|
|
218
|
+
async getClient({ chainId } = {}) {
|
|
219
|
+
const provider = await getProvider();
|
|
220
|
+
return Object.assign(provider.getClient({ chainId }), {
|
|
221
|
+
account: provider.getAccount(),
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
async getProvider() {
|
|
225
|
+
return await getProvider();
|
|
226
|
+
},
|
|
227
|
+
async isAuthorized() {
|
|
228
|
+
try {
|
|
229
|
+
const accounts = await withRetry(() => this.getAccounts());
|
|
230
|
+
return !!accounts.length;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
onAccountsChanged(accounts) {
|
|
237
|
+
config.emitter.emit('change', {
|
|
238
|
+
accounts: accounts,
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
onChainChanged(chain) {
|
|
242
|
+
config.emitter.emit('change', { chainId: Number(chain) });
|
|
243
|
+
},
|
|
244
|
+
async onConnect(connectInfo) {
|
|
245
|
+
const accounts = await this.getAccounts();
|
|
246
|
+
if (accounts.length === 0)
|
|
247
|
+
return;
|
|
248
|
+
const chainId = Number(connectInfo.chainId);
|
|
249
|
+
config.emitter.emit('connect', { accounts, chainId });
|
|
250
|
+
const provider = await getProvider();
|
|
251
|
+
if (connect) {
|
|
252
|
+
provider.removeListener('connect', connect);
|
|
253
|
+
connect = undefined;
|
|
254
|
+
}
|
|
255
|
+
if (!accountsChanged) {
|
|
256
|
+
accountsChanged = this.onAccountsChanged.bind(this);
|
|
257
|
+
provider.on('accountsChanged', accountsChanged);
|
|
258
|
+
}
|
|
259
|
+
if (!chainChanged) {
|
|
260
|
+
chainChanged = this.onChainChanged.bind(this);
|
|
261
|
+
provider.on('chainChanged', chainChanged);
|
|
262
|
+
}
|
|
263
|
+
if (!disconnect) {
|
|
264
|
+
disconnect = this.onDisconnect.bind(this);
|
|
265
|
+
provider.on('disconnect', disconnect);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
async onDisconnect(_error) {
|
|
269
|
+
const provider = await getProvider();
|
|
270
|
+
config.emitter.emit('disconnect');
|
|
271
|
+
if (chainChanged) {
|
|
272
|
+
provider.removeListener('chainChanged', chainChanged);
|
|
273
|
+
chainChanged = undefined;
|
|
274
|
+
}
|
|
275
|
+
if (disconnect) {
|
|
276
|
+
provider.removeListener('disconnect', disconnect);
|
|
277
|
+
disconnect = undefined;
|
|
278
|
+
}
|
|
279
|
+
if (!connect) {
|
|
280
|
+
connect = this.onConnect?.bind(this);
|
|
281
|
+
if (connect)
|
|
282
|
+
provider.on('connect', connect);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
async setup() {
|
|
286
|
+
if (!connect) {
|
|
287
|
+
const provider = await getProvider();
|
|
288
|
+
connect = this.onConnect?.bind(this);
|
|
289
|
+
if (connect)
|
|
290
|
+
provider.on('connect', connect);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
async switchChain({ chainId }) {
|
|
294
|
+
const chain = chains.find((chain) => chain.id === chainId);
|
|
295
|
+
if (!chain)
|
|
296
|
+
throw new SwitchChainError(new ChainNotConfiguredError());
|
|
297
|
+
const provider = await getProvider();
|
|
298
|
+
await provider.request({
|
|
299
|
+
method: 'wallet_switchEthereumChain',
|
|
300
|
+
params: [{ chainId: numberToHex(chainId) }],
|
|
301
|
+
});
|
|
302
|
+
return chain;
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
});
|
|
598
306
|
}
|
|
599
307
|
//# sourceMappingURL=Connectors.js.map
|