accounts 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +6 -5
- package/dist/cli/adapter.d.ts.map +1 -1
- package/dist/cli/adapter.js +53 -67
- package/dist/cli/adapter.js.map +1 -1
- package/dist/core/AccessKey.d.ts +89 -106
- package/dist/core/AccessKey.d.ts.map +1 -1
- package/dist/core/AccessKey.js +226 -210
- package/dist/core/AccessKey.js.map +1 -1
- package/dist/core/Account.d.ts +0 -9
- package/dist/core/Account.d.ts.map +1 -1
- package/dist/core/Account.js +1 -13
- package/dist/core/Account.js.map +1 -1
- package/dist/core/Adapter.d.ts +10 -2
- package/dist/core/Adapter.d.ts.map +1 -1
- package/dist/core/Provider.d.ts +4 -5
- package/dist/core/Provider.d.ts.map +1 -1
- package/dist/core/Provider.js +66 -76
- package/dist/core/Provider.js.map +1 -1
- package/dist/core/Schema.d.ts +7 -7
- package/dist/core/adapters/dialog.d.ts.map +1 -1
- package/dist/core/adapters/dialog.js +114 -124
- package/dist/core/adapters/dialog.js.map +1 -1
- package/dist/core/adapters/local.d.ts.map +1 -1
- package/dist/core/adapters/local.js +92 -105
- package/dist/core/adapters/local.js.map +1 -1
- package/dist/core/adapters/privy.d.ts +164 -0
- package/dist/core/adapters/privy.d.ts.map +1 -0
- package/dist/core/adapters/privy.js +574 -0
- package/dist/core/adapters/privy.js.map +1 -0
- package/dist/core/adapters/turnkey.d.ts.map +1 -1
- package/dist/core/adapters/turnkey.js +136 -133
- package/dist/core/adapters/turnkey.js.map +1 -1
- package/dist/core/internal/AccessKeyTransaction.d.ts +59 -0
- package/dist/core/internal/AccessKeyTransaction.d.ts.map +1 -0
- package/dist/core/internal/AccessKeyTransaction.js +155 -0
- package/dist/core/internal/AccessKeyTransaction.js.map +1 -0
- package/dist/core/zod/rpc.d.ts +9 -4
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +9 -4
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-native/adapter.d.ts.map +1 -1
- package/dist/react-native/adapter.js +61 -72
- package/dist/react-native/adapter.js.map +1 -1
- package/dist/server/Kv.d.ts +19 -9
- package/dist/server/Kv.d.ts.map +1 -1
- package/dist/server/Kv.js +32 -9
- package/dist/server/Kv.js.map +1 -1
- package/dist/server/internal/handlers/auth.d.ts.map +1 -1
- package/dist/server/internal/handlers/auth.js +2 -0
- package/dist/server/internal/handlers/auth.js.map +1 -1
- package/dist/server/internal/handlers/relay.d.ts +18 -12
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +115 -2
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/handlers/webAuthn.d.ts +6 -1
- package/dist/server/internal/handlers/webAuthn.d.ts.map +1 -1
- package/dist/server/internal/handlers/webAuthn.js +16 -9
- package/dist/server/internal/handlers/webAuthn.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/adapter.ts +69 -74
- package/src/core/AccessKey.test.ts +525 -559
- package/src/core/AccessKey.ts +323 -327
- package/src/core/Account.test.ts +5 -50
- package/src/core/Account.ts +1 -19
- package/src/core/Adapter.ts +6 -2
- package/src/core/{Provider.test.ts → Provider.localnet.test.ts} +97 -31
- package/src/core/Provider.ts +110 -110
- package/src/core/Schema.test-d.ts +18 -0
- package/src/core/adapters/dialog.test.ts +137 -13
- package/src/core/adapters/dialog.ts +109 -121
- package/src/core/adapters/local.ts +95 -119
- package/src/core/adapters/privy.test-d.ts +128 -0
- package/src/core/adapters/privy.test.ts +634 -0
- package/src/core/adapters/privy.ts +805 -0
- package/src/core/adapters/turnkey.test.ts +117 -13
- package/src/core/adapters/turnkey.ts +132 -149
- package/src/core/internal/AccessKeyTransaction.ts +243 -0
- package/src/core/{mppx.test.ts → mppx.localnet.test.ts} +32 -2
- package/src/core/zod/request.test.ts +36 -0
- package/src/core/zod/rpc.ts +17 -10
- package/src/index.ts +1 -1
- package/src/react-native/adapter.ts +77 -84
- package/src/server/Kv.test.ts +51 -0
- package/src/server/Kv.ts +43 -9
- package/src/server/internal/handlers/auth.test.ts +10 -5
- package/src/server/internal/handlers/auth.ts +3 -0
- package/src/server/internal/handlers/{relay.test.ts → relay.localnet.test.ts} +3 -0
- package/src/server/internal/handlers/relay.ts +138 -13
- package/src/server/internal/handlers/session.test.ts +1 -1
- package/src/server/internal/handlers/webAuthn.test.ts +20 -0
- package/src/server/internal/handlers/webAuthn.ts +24 -12
- /package/src/cli/{Provider.test.ts → Provider.localnet.test.ts} +0 -0
- /package/src/react-native/{Provider.test.ts → Provider.localnet.test.ts} +0 -0
- /package/src/server/internal/handlers/{exchange.test.ts → exchange.localnet.test.ts} +0 -0
package/src/core/Account.test.ts
CHANGED
|
@@ -87,12 +87,9 @@ describe('hydrate', () => {
|
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
describe('find', () => {
|
|
90
|
-
function setup(
|
|
91
|
-
storeAccounts: readonly Account.Store[] = [],
|
|
92
|
-
accessKeys: readonly Store.AccessKey[] = [],
|
|
93
|
-
) {
|
|
90
|
+
function setup(storeAccounts: readonly Account.Store[] = []) {
|
|
94
91
|
const store = Store.create({ chainId: tempoLocalnet.id })
|
|
95
|
-
store.setState({ accounts: storeAccounts,
|
|
92
|
+
store.setState({ accounts: storeAccounts, activeAccount: 0 })
|
|
96
93
|
return store
|
|
97
94
|
}
|
|
98
95
|
|
|
@@ -145,52 +142,10 @@ describe('find', () => {
|
|
|
145
142
|
expect(typeof result.sign).toMatchInlineSnapshot(`"function"`)
|
|
146
143
|
})
|
|
147
144
|
|
|
148
|
-
test('behavior: prefers access key over root account', async () => {
|
|
149
|
-
const keyPair = await WebCryptoP256.createKeyPair()
|
|
150
|
-
const store = setup(
|
|
151
|
-
[{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
|
|
152
|
-
[
|
|
153
|
-
{
|
|
154
|
-
address: '0x0000000000000000000000000000000000000099',
|
|
155
|
-
access: accounts[0].address,
|
|
156
|
-
chainId: tempoLocalnet.id,
|
|
157
|
-
keyType: 'webCrypto',
|
|
158
|
-
keyPair,
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
const result = Account.find({ chainId: tempoLocalnet.id, store, signable: true })
|
|
164
|
-
|
|
165
|
-
expect(result.source).toMatchInlineSnapshot(`"accessKey"`)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test('behavior: accessKey false skips access key', async () => {
|
|
169
|
-
const keyPair = await WebCryptoP256.createKeyPair()
|
|
170
|
-
const store = setup(
|
|
171
|
-
[{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] }],
|
|
172
|
-
[
|
|
173
|
-
{
|
|
174
|
-
address: '0x0000000000000000000000000000000000000099',
|
|
175
|
-
access: accounts[0].address,
|
|
176
|
-
chainId: tempoLocalnet.id,
|
|
177
|
-
keyType: 'webCrypto',
|
|
178
|
-
keyPair,
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
const result = Account.find({ accessKey: false, signable: true, store })
|
|
184
|
-
|
|
185
|
-
expect(result.address).toMatchInlineSnapshot(`"${accounts[0].address}"`)
|
|
186
|
-
expect(result.source).not.toBe('accessKey')
|
|
187
|
-
})
|
|
188
|
-
|
|
189
145
|
test('behavior: falls back to root when no access key exists', () => {
|
|
190
|
-
const store = setup(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
146
|
+
const store = setup([
|
|
147
|
+
{ address: accounts[0].address, keyType: 'secp256k1', privateKey: privateKeys[0] },
|
|
148
|
+
])
|
|
194
149
|
|
|
195
150
|
const result = Account.find({ signable: true, store })
|
|
196
151
|
|
package/src/core/Account.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { Address, JsonRpcAccount } from 'viem/accounts'
|
|
|
4
4
|
import { Account as TempoAccount } from 'viem/tempo'
|
|
5
5
|
|
|
6
6
|
import type { OneOf } from '../internal/types.js'
|
|
7
|
-
import * as core_AccessKey from './AccessKey.js'
|
|
8
7
|
import type * as core_Store from './Store.js'
|
|
9
8
|
|
|
10
9
|
/** Account stored in the provider state. */
|
|
@@ -35,7 +34,7 @@ export type Store = {
|
|
|
35
34
|
export function find(options: find.Options & { signable: true }): TempoAccount.Account
|
|
36
35
|
export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount
|
|
37
36
|
export function find(options: find.Options): TempoAccount.Account | JsonRpcAccount {
|
|
38
|
-
const {
|
|
37
|
+
const { address, signable = false, store } = options
|
|
39
38
|
const { accounts, activeAccount } = store.getState()
|
|
40
39
|
|
|
41
40
|
const activeAddr = accounts[activeAccount]?.address
|
|
@@ -47,30 +46,13 @@ export function find(options: find.Options): TempoAccount.Account | JsonRpcAccou
|
|
|
47
46
|
? new Provider.UnauthorizedError({ message: `Account "${address}" not found.` })
|
|
48
47
|
: new Provider.DisconnectedError({ message: 'No active account.' })
|
|
49
48
|
|
|
50
|
-
// When accessKey is requested, prefer a locally-signable access key for this address.
|
|
51
|
-
if (accessKey) {
|
|
52
|
-
const account = core_AccessKey.selectAccount({
|
|
53
|
-
address: root.address,
|
|
54
|
-
calls: options.calls,
|
|
55
|
-
chainId: options.chainId ?? store.getState().chainId,
|
|
56
|
-
store,
|
|
57
|
-
})
|
|
58
|
-
if (account) return account
|
|
59
|
-
}
|
|
60
|
-
|
|
61
49
|
return hydrate(root, { signable }) as never
|
|
62
50
|
}
|
|
63
51
|
|
|
64
52
|
export declare namespace find {
|
|
65
53
|
type Options = {
|
|
66
|
-
/** Whether to prefer an access key for this account. @default true */
|
|
67
|
-
accessKey?: boolean | undefined
|
|
68
54
|
/** Address to find. Defaults to the active account. */
|
|
69
55
|
address?: Address | undefined
|
|
70
|
-
/** Calls to match against access key scopes. When provided, access keys whose scopes don't cover these calls are skipped. */
|
|
71
|
-
calls?: readonly { to?: Address | undefined; data?: Hex | undefined }[] | undefined
|
|
72
|
-
/** Chain ID the access key must be authorized on. Defaults to the active chain. */
|
|
73
|
-
chainId?: number | undefined
|
|
74
56
|
/** Whether to hydrate signing capability. @default false */
|
|
75
57
|
signable?: boolean | undefined
|
|
76
58
|
/** Reactive state store. */
|
package/src/core/Adapter.ts
CHANGED
|
@@ -143,7 +143,7 @@ export type Instance = {
|
|
|
143
143
|
export declare namespace SetupFn {
|
|
144
144
|
/** Parameters passed to an adapter's setup function. */
|
|
145
145
|
export type Parameters = {
|
|
146
|
-
/** Returns the rehydrated
|
|
146
|
+
/** Returns the rehydrated root account for the given address, or the active account if omitted. */
|
|
147
147
|
getAccount: Account.Find
|
|
148
148
|
/** Get the viem client for a given chain ID. Defaults to the active chain. */
|
|
149
149
|
getClient: (options?: getClient.Options | undefined) => Client<Transport, typeof tempo>
|
|
@@ -166,7 +166,7 @@ export function define(meta: Meta, fn: SetupFn): Adapter {
|
|
|
166
166
|
|
|
167
167
|
/** Spreads decoded params. */
|
|
168
168
|
export type ActionRequest<item extends Schema.Item> =
|
|
169
|
-
Schema.Decoded<item>['params'] extends readonly [infer first] ? first : never
|
|
169
|
+
NonNullable<Schema.Decoded<item>['params']> extends readonly [infer first] ? first : never
|
|
170
170
|
|
|
171
171
|
export declare namespace getClient {
|
|
172
172
|
type Options = {
|
|
@@ -201,6 +201,8 @@ export declare namespace createAccount {
|
|
|
201
201
|
email?: string | null | undefined
|
|
202
202
|
/** Signed key authorization, if an access key was granted. */
|
|
203
203
|
keyAuthorization?: KeyAuthorization.Rpc | undefined
|
|
204
|
+
/** Server Authentication result, if the auth capability was requested. */
|
|
205
|
+
auth?: { token?: string | undefined } | undefined
|
|
204
206
|
/**
|
|
205
207
|
* Echo of the `personalSign` request, present iff the caller supplied
|
|
206
208
|
* `personalSign`. The signature lives on the top-level `signature`
|
|
@@ -257,6 +259,8 @@ export declare namespace loadAccounts {
|
|
|
257
259
|
email?: string | null | undefined
|
|
258
260
|
/** Signed key authorization, if an access key was granted. */
|
|
259
261
|
keyAuthorization?: KeyAuthorization.Rpc | undefined
|
|
262
|
+
/** Server Authentication result, if the auth capability was requested. */
|
|
263
|
+
auth?: { token?: string | undefined } | undefined
|
|
260
264
|
/**
|
|
261
265
|
* Echo of the `personalSign` request, present iff the caller supplied
|
|
262
266
|
* `personalSign`. The signature lives on the top-level `signature`
|
|
@@ -349,37 +349,43 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
349
349
|
// Real Hono app: mount the auth handler under `/auth` and add a
|
|
350
350
|
// protected `/me` route — exactly as a dapp would compose them
|
|
351
351
|
// — so the e2e test below exercises the full flow.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
let listener: Parameters<typeof createServer>[0] | undefined
|
|
353
|
+
server = await createServer((req, res) => {
|
|
354
|
+
if (!listener) {
|
|
355
|
+
const auth = Handler.auth({ origin: server.url })
|
|
356
|
+
const app = Handler.compose([auth], { path: '/auth' })
|
|
357
|
+
app.get('/me', async (c) => {
|
|
358
|
+
const session = await auth.getSession(c.req.raw)
|
|
359
|
+
if (!session) return c.json({ error: 'unauthenticated' }, 401)
|
|
360
|
+
return c.json({ address: session.address, chainId: session.chainId })
|
|
361
|
+
})
|
|
362
|
+
// Bad-challenge / bad-verify endpoints mounted on the same origin
|
|
363
|
+
// as `/auth` so the same-origin enforcement (`absolutizeAuth`)
|
|
364
|
+
// doesn't reject the request before the bad-content paths under
|
|
365
|
+
// test can run. `app.all` so we don't depend on the SDK's request
|
|
366
|
+
// method (POST).
|
|
367
|
+
app.all('/bad/verify-401', (c) => c.json({ error: 'unauthorized' }, 401))
|
|
368
|
+
app.all('/bad/challenge-500', (c) => c.json({ error: 'boom' }, 500))
|
|
369
|
+
app.all('/bad/challenge-empty', (c) => c.json({}))
|
|
370
|
+
app.all('/bad/challenge-evil-domain', (c) =>
|
|
371
|
+
c.json({
|
|
372
|
+
message: [
|
|
373
|
+
'evil.example wants you to sign in with your Ethereum account:',
|
|
374
|
+
'0x0000000000000000000000000000000000000000',
|
|
375
|
+
'',
|
|
376
|
+
'',
|
|
377
|
+
'URI: https://evil.example',
|
|
378
|
+
'Version: 1',
|
|
379
|
+
'Chain ID: 0',
|
|
380
|
+
'Nonce: deadbeef00',
|
|
381
|
+
'Issued At: 2025-01-01T00:00:00Z',
|
|
382
|
+
].join('\n'),
|
|
383
|
+
}),
|
|
384
|
+
)
|
|
385
|
+
listener = app.listener
|
|
386
|
+
}
|
|
387
|
+
return listener(req, res)
|
|
358
388
|
})
|
|
359
|
-
// Bad-challenge / bad-verify endpoints mounted on the same origin
|
|
360
|
-
// as `/auth` so the same-origin enforcement (`absolutizeAuth`)
|
|
361
|
-
// doesn't reject the request before the bad-content paths under
|
|
362
|
-
// test can run. `app.all` so we don't depend on the SDK's request
|
|
363
|
-
// method (POST).
|
|
364
|
-
app.all('/bad/verify-401', (c) => c.json({ error: 'unauthorized' }, 401))
|
|
365
|
-
app.all('/bad/challenge-500', (c) => c.json({ error: 'boom' }, 500))
|
|
366
|
-
app.all('/bad/challenge-empty', (c) => c.json({}))
|
|
367
|
-
app.all('/bad/challenge-evil-domain', (c) =>
|
|
368
|
-
c.json({
|
|
369
|
-
message: [
|
|
370
|
-
'evil.example wants you to sign in with your Ethereum account:',
|
|
371
|
-
'0x0000000000000000000000000000000000000000',
|
|
372
|
-
'',
|
|
373
|
-
'',
|
|
374
|
-
'URI: https://evil.example',
|
|
375
|
-
'Version: 1',
|
|
376
|
-
'Chain ID: 0',
|
|
377
|
-
'Nonce: deadbeef00',
|
|
378
|
-
'Issued At: 2025-01-01T00:00:00Z',
|
|
379
|
-
].join('\n'),
|
|
380
|
-
}),
|
|
381
|
-
)
|
|
382
|
-
server = await createServer(app.listener)
|
|
383
389
|
authBase = `${server.url}/auth`
|
|
384
390
|
|
|
385
391
|
// Cross-origin bad server kept around for the same-origin enforcement
|
|
@@ -412,6 +418,22 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
412
418
|
expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
|
|
413
419
|
})
|
|
414
420
|
|
|
421
|
+
test('default: object-form auth with url derives challenge and verify', async () => {
|
|
422
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
423
|
+
|
|
424
|
+
const result = await provider.request({
|
|
425
|
+
method: 'wallet_connect',
|
|
426
|
+
params: [{ capabilities: { method: 'register', auth: { url: authBase } } }],
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
const capabilities = result.accounts[0]!.capabilities
|
|
430
|
+
expect(capabilities.auth).toEqual({ token: expect.any(String) })
|
|
431
|
+
expect(capabilities.personalSign).toEqual({
|
|
432
|
+
message: expect.stringContaining('wants you to sign in'),
|
|
433
|
+
})
|
|
434
|
+
expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
|
|
435
|
+
})
|
|
436
|
+
|
|
415
437
|
test('default: object-form auth with explicit endpoints uses the override URLs', async () => {
|
|
416
438
|
const provider = Provider.create({ adapter: adapter() })
|
|
417
439
|
|
|
@@ -433,6 +455,32 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
433
455
|
expect(result.accounts[0]!.capabilities.auth).toEqual({ token: expect.any(String) })
|
|
434
456
|
})
|
|
435
457
|
|
|
458
|
+
test('default: forwarded auth without verify returns signature for downstream verify', async () => {
|
|
459
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
460
|
+
|
|
461
|
+
const result = await provider.request({
|
|
462
|
+
method: 'wallet_connect',
|
|
463
|
+
params: [
|
|
464
|
+
{
|
|
465
|
+
capabilities: {
|
|
466
|
+
method: 'register',
|
|
467
|
+
auth: {
|
|
468
|
+
challenge: `${authBase}/challenge`,
|
|
469
|
+
logout: `${authBase}/logout`,
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const capabilities = result.accounts[0]!.capabilities
|
|
477
|
+
expect(capabilities.auth).toBeUndefined()
|
|
478
|
+
expect(capabilities.personalSign).toEqual({
|
|
479
|
+
message: expect.stringContaining('wants you to sign in'),
|
|
480
|
+
})
|
|
481
|
+
expect(capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
|
|
482
|
+
})
|
|
483
|
+
|
|
436
484
|
test('error: verify endpoint returns 401 → InternalError; user already signed', async () => {
|
|
437
485
|
const provider = Provider.create({ adapter: adapter() })
|
|
438
486
|
|
|
@@ -1133,7 +1181,7 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
1133
1181
|
from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
1134
1182
|
|
|
1135
1183
|
Details: plain send failure
|
|
1136
|
-
Version: viem@2.
|
|
1184
|
+
Version: viem@2.50.4]
|
|
1137
1185
|
`)
|
|
1138
1186
|
})
|
|
1139
1187
|
})
|
|
@@ -1957,6 +2005,24 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
1957
2005
|
expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeUndefined()
|
|
1958
2006
|
})
|
|
1959
2007
|
|
|
2008
|
+
test('behavior: fills pending keyAuthorization with the active account when from is omitted', async () => {
|
|
2009
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
2010
|
+
const address = await connect(provider)
|
|
2011
|
+
await fund(address)
|
|
2012
|
+
|
|
2013
|
+
await provider.request({
|
|
2014
|
+
method: 'wallet_authorizeAccessKey',
|
|
2015
|
+
params: [{ expiry: Expiry.days(1) }],
|
|
2016
|
+
})
|
|
2017
|
+
|
|
2018
|
+
const result = await provider.request({
|
|
2019
|
+
method: 'eth_fillTransaction',
|
|
2020
|
+
params: [fillTx],
|
|
2021
|
+
})
|
|
2022
|
+
expect(result.tx.gas).toBeDefined()
|
|
2023
|
+
expect(provider.store.getState().accessKeys[0]!.keyAuthorization).toBeDefined()
|
|
2024
|
+
})
|
|
2025
|
+
|
|
1960
2026
|
test('behavior: removes stale access key and retries on error', async () => {
|
|
1961
2027
|
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1962
2028
|
const address = await connect(provider)
|