accounts 0.15.3 → 0.16.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 +31 -0
- package/dist/cli/adapter.d.ts +2 -2
- package/dist/cli/adapter.d.ts.map +1 -1
- package/dist/cli/adapter.js +84 -5
- package/dist/cli/adapter.js.map +1 -1
- package/dist/core/AccessKey.js +1 -1
- package/dist/core/AccessKey.js.map +1 -1
- package/dist/core/Client.d.ts +1 -1
- package/dist/core/Client.d.ts.map +1 -1
- package/dist/core/Client.js +30 -10
- package/dist/core/Client.js.map +1 -1
- package/dist/react/privy.d.ts.map +1 -1
- package/dist/react/privy.js +7 -5
- package/dist/react/privy.js.map +1 -1
- package/dist/server/CliAuth.d.ts +718 -33
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +340 -47
- package/dist/server/CliAuth.js.map +1 -1
- package/dist/server/internal/handlers/codeAuth.d.ts.map +1 -1
- package/dist/server/internal/handlers/codeAuth.js +2 -2
- package/dist/server/internal/handlers/codeAuth.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/Provider.localnet.test.ts +170 -5
- package/src/cli/adapter.ts +96 -6
- package/src/core/AccessKey.test.ts +36 -0
- package/src/core/AccessKey.ts +1 -1
- package/src/core/Client.test.ts +35 -19
- package/src/core/Client.ts +36 -11
- package/src/core/Provider.test.ts +16 -0
- package/src/react/privy.ts +9 -5
- package/src/server/CliAuth.test-d.ts +40 -5
- package/src/server/CliAuth.test.ts +535 -23
- package/src/server/CliAuth.ts +425 -66
- package/src/server/internal/handlers/codeAuth.ts +4 -2
package/src/core/Client.test.ts
CHANGED
|
@@ -87,12 +87,12 @@ describe('caching', () => {
|
|
|
87
87
|
chains: [tempoModerato, tempo],
|
|
88
88
|
storage: Storage.memory(),
|
|
89
89
|
})
|
|
90
|
-
const client_a = Client.fromChainId(
|
|
90
|
+
const client_a = Client.fromChainId(undefined, {
|
|
91
91
|
chains: [tempo, tempoModerato],
|
|
92
92
|
provider: provider_a,
|
|
93
93
|
store,
|
|
94
94
|
})
|
|
95
|
-
const client_b = Client.fromChainId(
|
|
95
|
+
const client_b = Client.fromChainId(undefined, {
|
|
96
96
|
chains: [tempo, tempoModerato],
|
|
97
97
|
provider: provider_b,
|
|
98
98
|
store,
|
|
@@ -203,35 +203,51 @@ describe('caching', () => {
|
|
|
203
203
|
})
|
|
204
204
|
|
|
205
205
|
describe('providerTransport', () => {
|
|
206
|
-
test('
|
|
206
|
+
test('behavior: returns empty accounts before connecting', async () => {
|
|
207
207
|
const store = setup()
|
|
208
208
|
const provider = Provider.create({
|
|
209
209
|
adapter: secp256k1(),
|
|
210
|
-
chains: [
|
|
210
|
+
chains: [tempo],
|
|
211
211
|
storage: Storage.memory(),
|
|
212
212
|
})
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
const client = Client.fromChainId(tempo.id, { chains: [tempo], provider, store })
|
|
214
|
+
const result = await client.request({ method: 'eth_accounts' })
|
|
215
|
+
expect(result).toMatchInlineSnapshot(`[]`)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('behavior: fills transactions on the requested chain', async () => {
|
|
219
|
+
const requests: unknown[] = []
|
|
220
|
+
const store = setup()
|
|
221
|
+
const provider = {
|
|
222
|
+
async request(request: unknown) {
|
|
223
|
+
requests.push(request)
|
|
224
|
+
return {}
|
|
225
|
+
},
|
|
226
|
+
} as never
|
|
216
227
|
const client = Client.fromChainId(tempo.id, {
|
|
217
228
|
chains: [tempo, tempoModerato],
|
|
218
229
|
provider,
|
|
219
230
|
store,
|
|
220
231
|
})
|
|
221
|
-
const result = await client.request({ method: 'eth_chainId' })
|
|
222
|
-
expect(result).toMatchInlineSnapshot(`"0xa5bf"`)
|
|
223
|
-
})
|
|
224
232
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
adapter: secp256k1(),
|
|
229
|
-
chains: [tempo],
|
|
230
|
-
storage: Storage.memory(),
|
|
233
|
+
await client.request({
|
|
234
|
+
method: 'eth_fillTransaction' as never,
|
|
235
|
+
params: [{ to: '0x0000000000000000000000000000000000000001' }] as never,
|
|
231
236
|
})
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
|
|
238
|
+
expect(requests).toMatchInlineSnapshot(`
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
"method": "eth_fillTransaction",
|
|
242
|
+
"params": [
|
|
243
|
+
{
|
|
244
|
+
"chainId": 4217,
|
|
245
|
+
"to": "0x0000000000000000000000000000000000000001",
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
`)
|
|
235
251
|
})
|
|
236
252
|
})
|
|
237
253
|
|
package/src/core/Client.ts
CHANGED
|
@@ -34,11 +34,6 @@ export function fromChainId(
|
|
|
34
34
|
})()
|
|
35
35
|
const id = chainId ?? store.getState().chainId
|
|
36
36
|
const key = `${id}:${provider ? 'p' : ''}:${feePayerOption === false ? 'no-fp' : (feePayerUrl ?? '')}:${precedence}`
|
|
37
|
-
// Scope the cache by `provider` (preferred) or `transports`, falling back
|
|
38
|
-
// to a shared module-level map. The cache key only encodes a boolean for
|
|
39
|
-
// `provider`, so without scoping, two providers sharing the same chainId
|
|
40
|
-
// would hit each other's cached clients and route requests to the wrong
|
|
41
|
-
// adapter via `providerTransport`.
|
|
42
37
|
const scope = provider ?? transports
|
|
43
38
|
const cache = (() => {
|
|
44
39
|
if (!scope) return defaultClients
|
|
@@ -53,7 +48,9 @@ export function fromChainId(
|
|
|
53
48
|
if (!client) {
|
|
54
49
|
const chain = chains.find((c) => c.id === id) ?? chains[0]!
|
|
55
50
|
const base = transports?.[id] ?? http()
|
|
56
|
-
const transport_base = provider
|
|
51
|
+
const transport_base = provider
|
|
52
|
+
? providerTransport(provider, base, chainId === undefined ? undefined : chain.id)
|
|
53
|
+
: base
|
|
57
54
|
const transport = feePayerUrl
|
|
58
55
|
? feePayerTransport(transport_base, feePayerUrl, precedence)
|
|
59
56
|
: transport_base
|
|
@@ -78,7 +75,7 @@ export declare namespace fromChainId {
|
|
|
78
75
|
precedence?: 'fee-payer-first' | 'user-first' | undefined
|
|
79
76
|
}
|
|
80
77
|
| undefined
|
|
81
|
-
/** Provider
|
|
78
|
+
/** Provider for account operations and active-chain requests. Explicit-chain RPCs use the matching transport. */
|
|
82
79
|
provider?: ox_Provider.Provider | undefined
|
|
83
80
|
/** Reactive state store. */
|
|
84
81
|
store: Store.Store
|
|
@@ -88,24 +85,52 @@ export declare namespace fromChainId {
|
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
88
|
+
* Routes account operations through the provider and explicit-chain RPCs
|
|
89
|
+
* through the requested chain's transport.
|
|
93
90
|
*/
|
|
94
|
-
function providerTransport(
|
|
91
|
+
function providerTransport(
|
|
92
|
+
provider: ox_Provider.Provider,
|
|
93
|
+
base: Transport,
|
|
94
|
+
chainId: number | undefined,
|
|
95
|
+
): Transport {
|
|
95
96
|
return (params) => {
|
|
96
97
|
const baseTransport = base(params)
|
|
97
98
|
return {
|
|
98
99
|
...baseTransport,
|
|
99
100
|
async request({ method, params: reqParams }) {
|
|
101
|
+
if (chainId !== undefined && !usesProvider(method))
|
|
102
|
+
return baseTransport.request({ method, params: reqParams } as never)
|
|
103
|
+
const params = (() => {
|
|
104
|
+
if (method !== 'eth_fillTransaction' || chainId === undefined) return reqParams
|
|
105
|
+
const request = (reqParams as readonly unknown[] | undefined)?.[0]
|
|
106
|
+
if (!request || typeof request !== 'object') return reqParams
|
|
107
|
+
return [{ ...request, chainId }]
|
|
108
|
+
})()
|
|
100
109
|
return (provider as { request: EIP1193RequestFn }).request({
|
|
101
110
|
method,
|
|
102
|
-
params
|
|
111
|
+
params,
|
|
103
112
|
} as any)
|
|
104
113
|
},
|
|
105
114
|
} as ReturnType<Transport>
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
|
|
118
|
+
function usesProvider(method: string) {
|
|
119
|
+
return (
|
|
120
|
+
method.startsWith('wallet_') ||
|
|
121
|
+
[
|
|
122
|
+
'eth_accounts',
|
|
123
|
+
'eth_fillTransaction',
|
|
124
|
+
'eth_requestAccounts',
|
|
125
|
+
'eth_sendTransaction',
|
|
126
|
+
'eth_sendTransactionSync',
|
|
127
|
+
'eth_signTransaction',
|
|
128
|
+
'eth_signTypedData_v4',
|
|
129
|
+
'personal_sign',
|
|
130
|
+
].includes(method)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
/**
|
|
110
135
|
* Resolves a fee payer URL to an absolute URL string. Relative paths (e.g.
|
|
111
136
|
* `/relay`) are resolved against `window.location.origin` when running in a
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Hex } from 'ox'
|
|
2
|
+
import { custom } from 'viem'
|
|
2
3
|
import { createSiweMessage } from 'viem/siwe'
|
|
4
|
+
import { tempo, tempoModerato } from 'viem/tempo/chains'
|
|
3
5
|
import { afterEach, describe, expect, test, vi } from 'vp/test'
|
|
4
6
|
|
|
5
7
|
import { accounts, privateKeys } from '../../test/config.js'
|
|
@@ -36,6 +38,20 @@ describe('getMppxParameters', () => {
|
|
|
36
38
|
expect((client as { account?: unknown }).account).toBeUndefined()
|
|
37
39
|
})
|
|
38
40
|
|
|
41
|
+
test('behavior: routes chain RPCs to the requested chain', async () => {
|
|
42
|
+
const provider = Provider.create({
|
|
43
|
+
chains: [tempo, tempoModerato],
|
|
44
|
+
storage: Storage.memory(),
|
|
45
|
+
transports: {
|
|
46
|
+
[tempoModerato.id]: custom({ request: async () => '0xa5bf' }),
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
provider.store.setState({ accounts: [{ address }], activeAccount: 0 })
|
|
50
|
+
|
|
51
|
+
const client = await provider.getMppxParameters().getClient({ chainId: tempoModerato.id })
|
|
52
|
+
expect(await client.request({ method: 'eth_chainId' })).toBe('0xa5bf')
|
|
53
|
+
})
|
|
54
|
+
|
|
39
55
|
test('behavior: resolves existing access key for connected account', async () => {
|
|
40
56
|
const payer = accounts[0]!
|
|
41
57
|
const provider = Provider.create({ storage: Storage.memory() })
|
package/src/react/privy.ts
CHANGED
|
@@ -82,12 +82,12 @@ export function PrivyAccountsBridge() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
login_started.current = false
|
|
85
|
-
|
|
85
|
+
const embedded = wallets.wallets.filter(isEmbeddedEthereumWallet)
|
|
86
|
+
if (!wallets.ready && embedded.length === 0) return
|
|
86
87
|
|
|
87
88
|
loading.current = true
|
|
88
89
|
void (async () => {
|
|
89
90
|
try {
|
|
90
|
-
const embedded = wallets.wallets.filter(isEmbeddedEthereumWallet)
|
|
91
91
|
if (embedded.length === 0)
|
|
92
92
|
throw new Error('Privy React returned no embedded Ethereum wallet.')
|
|
93
93
|
|
|
@@ -190,7 +190,7 @@ const client = {
|
|
|
190
190
|
function setPrivyReactState(next: PrivyReactState) {
|
|
191
191
|
state = next
|
|
192
192
|
if (next.ready) emitReady()
|
|
193
|
-
if (next.ready && next.walletsReady) emitWalletsReady()
|
|
193
|
+
if (next.ready && (next.walletsReady || next.wallets.length > 0)) emitWalletsReady()
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function usePrivyAccountsRequest() {
|
|
@@ -242,7 +242,11 @@ function getSnapshot() {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
function isEmbeddedEthereumWallet(wallet: ConnectedWallet) {
|
|
245
|
-
return
|
|
245
|
+
return (
|
|
246
|
+
wallet.type === 'ethereum' &&
|
|
247
|
+
wallet.walletClientType === 'privy' &&
|
|
248
|
+
wallet.connectorType === 'embedded'
|
|
249
|
+
)
|
|
246
250
|
}
|
|
247
251
|
|
|
248
252
|
function sameAddress(a: string, b: string) {
|
|
@@ -261,7 +265,7 @@ function waitForReady() {
|
|
|
261
265
|
|
|
262
266
|
async function waitForWallets() {
|
|
263
267
|
await waitForReady()
|
|
264
|
-
if (state.walletsReady) return
|
|
268
|
+
if (state.walletsReady || state.wallets.length > 0) return
|
|
265
269
|
return new Promise<void>((resolve) => wallets_ready_listeners.add(resolve))
|
|
266
270
|
}
|
|
267
271
|
|
|
@@ -6,7 +6,8 @@ import * as CliAuth from './CliAuth.js'
|
|
|
6
6
|
|
|
7
7
|
describe('createRequest', () => {
|
|
8
8
|
test('includes the v1 device-code request fields', () => {
|
|
9
|
-
|
|
9
|
+
type Request = Exclude<z.output<typeof CliAuth.createRequest>, { action: 'updateAccessKey' }>
|
|
10
|
+
expectTypeOf<Request>().toMatchTypeOf<{
|
|
10
11
|
account?: Hex | undefined
|
|
11
12
|
codeChallenge: string
|
|
12
13
|
expiry?: number | undefined
|
|
@@ -25,13 +26,26 @@ describe('createRequest', () => {
|
|
|
25
26
|
}>()
|
|
26
27
|
})
|
|
27
28
|
|
|
29
|
+
test('includes access-key update requests', () => {
|
|
30
|
+
type Request = Extract<z.output<typeof CliAuth.createRequest>, { action: 'updateAccessKey' }>
|
|
31
|
+
expectTypeOf<Request>().toMatchTypeOf<{
|
|
32
|
+
action: 'updateAccessKey'
|
|
33
|
+
accessKeyAddress: Hex
|
|
34
|
+
account: Hex
|
|
35
|
+
chainId?: bigint | undefined
|
|
36
|
+
codeChallenge: string
|
|
37
|
+
keyAuthorization?: z.output<typeof CliAuth.keyAuthorization> | undefined
|
|
38
|
+
limits: readonly { token: Hex; limit: bigint }[]
|
|
39
|
+
}>()
|
|
40
|
+
})
|
|
41
|
+
|
|
28
42
|
test('does not include scopes in v1', () => {
|
|
29
|
-
type Request = z.output<typeof CliAuth.createRequest>
|
|
43
|
+
type Request = Exclude<z.output<typeof CliAuth.createRequest>, { action: 'updateAccessKey' }>
|
|
30
44
|
expectTypeOf<Request>().not.toHaveProperty('scopes')
|
|
31
45
|
})
|
|
32
46
|
|
|
33
47
|
test('showDeposit does not include address or chainId', () => {
|
|
34
|
-
type Request = z.output<typeof CliAuth.createRequest>
|
|
48
|
+
type Request = Exclude<z.output<typeof CliAuth.createRequest>, { action: 'updateAccessKey' }>
|
|
35
49
|
type ShowDeposit = Exclude<Exclude<Request['showDeposit'], boolean | undefined>, undefined>
|
|
36
50
|
expectTypeOf<ShowDeposit>().not.toHaveProperty('address')
|
|
37
51
|
expectTypeOf<ShowDeposit>().not.toHaveProperty('chainId')
|
|
@@ -40,18 +54,31 @@ describe('createRequest', () => {
|
|
|
40
54
|
|
|
41
55
|
describe('pollResponse', () => {
|
|
42
56
|
test('authorized responses carry the normal keyAuthorization shape', () => {
|
|
43
|
-
type Response =
|
|
57
|
+
type Response = Exclude<
|
|
58
|
+
Extract<z.output<typeof CliAuth.pollResponse>, { status: 'authorized' }>,
|
|
59
|
+
{ action: 'updateAccessKey' }
|
|
60
|
+
>
|
|
44
61
|
expectTypeOf<Response>().toMatchTypeOf<{
|
|
45
62
|
accountAddress: Hex
|
|
46
63
|
keyAuthorization: z.output<typeof CliAuth.keyAuthorization>
|
|
47
64
|
status: 'authorized'
|
|
48
65
|
}>()
|
|
49
66
|
})
|
|
67
|
+
|
|
68
|
+
test('access-key updates can return a replacement authorization', () => {
|
|
69
|
+
type Response = Extract<z.output<typeof CliAuth.pollResponse>, { action: 'updateAccessKey' }>
|
|
70
|
+
expectTypeOf<Response>().toMatchTypeOf<{
|
|
71
|
+
action: 'updateAccessKey'
|
|
72
|
+
keyAuthorization?: z.output<typeof CliAuth.keyAuthorization> | undefined
|
|
73
|
+
status: 'authorized'
|
|
74
|
+
}>()
|
|
75
|
+
})
|
|
50
76
|
})
|
|
51
77
|
|
|
52
78
|
describe('pendingResponse', () => {
|
|
53
79
|
test('pending responses expose the browser approval payload', () => {
|
|
54
|
-
|
|
80
|
+
type Response = Exclude<z.output<typeof CliAuth.pendingResponse>, { action: 'updateAccessKey' }>
|
|
81
|
+
expectTypeOf<Response>().toMatchTypeOf<{
|
|
55
82
|
accessKeyAddress: Hex
|
|
56
83
|
account?: Hex | undefined
|
|
57
84
|
chainId: bigint
|
|
@@ -72,6 +99,14 @@ describe('pendingResponse', () => {
|
|
|
72
99
|
status: 'pending'
|
|
73
100
|
}>()
|
|
74
101
|
})
|
|
102
|
+
|
|
103
|
+
test('pending access-key updates expose an optional current authorization', () => {
|
|
104
|
+
type Response = Extract<z.output<typeof CliAuth.pendingResponse>, { action: 'updateAccessKey' }>
|
|
105
|
+
expectTypeOf<Response>().toMatchTypeOf<{
|
|
106
|
+
action: 'updateAccessKey'
|
|
107
|
+
keyAuthorization?: z.output<typeof CliAuth.keyAuthorization> | undefined
|
|
108
|
+
}>()
|
|
109
|
+
})
|
|
75
110
|
})
|
|
76
111
|
|
|
77
112
|
describe('Store', () => {
|