accounts 0.14.0 → 0.14.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/CHANGELOG.md +12 -0
- package/dist/core/AccessKey.d.ts +1 -1
- package/dist/core/AccessKey.d.ts.map +1 -1
- package/dist/core/Adapter.d.ts +2 -2
- package/dist/core/Adapter.d.ts.map +1 -1
- package/dist/core/Provider.d.ts.map +1 -1
- package/dist/core/Provider.js +3 -0
- package/dist/core/Provider.js.map +1 -1
- package/dist/core/Schema.d.ts +14 -0
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/adapters/privy.d.ts.map +1 -1
- package/dist/core/adapters/privy.js +112 -155
- package/dist/core/adapters/privy.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +67 -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/react-native/adapter.d.ts.map +1 -1
- package/dist/react-native/adapter.js +3 -0
- package/dist/react-native/adapter.js.map +1 -1
- package/dist/server/CliAuth.d.ts +11 -0
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +1 -0
- package/dist/server/CliAuth.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/Provider.localnet.test.ts +46 -2
- package/src/core/AccessKey.ts +1 -1
- package/src/core/Adapter.ts +2 -2
- package/src/core/Provider.ts +3 -0
- package/src/core/Schema.test-d.ts +3 -1
- package/src/core/adapters/privy.test.ts +150 -10
- package/src/core/adapters/privy.ts +148 -192
- package/src/core/zod/rpc.test.ts +91 -4
- package/src/core/zod/rpc.ts +9 -4
- package/src/react-native/Provider.localnet.test.ts +25 -0
- package/src/react-native/adapter.ts +3 -0
- package/src/server/CliAuth.test-d.ts +2 -0
- package/src/server/CliAuth.test.ts +2 -0
- package/src/server/CliAuth.ts +1 -0
|
@@ -56,10 +56,11 @@ function connectRequest(
|
|
|
56
56
|
options: {
|
|
57
57
|
accessKey?: typeof accessKey | undefined
|
|
58
58
|
expiry?: number | undefined
|
|
59
|
+
method?: 'login' | 'register' | undefined
|
|
59
60
|
showDeposit?: z.output<typeof CliAuth.createRequest>['showDeposit'] | undefined
|
|
60
61
|
} = {},
|
|
61
62
|
) {
|
|
62
|
-
const { accessKey: key = accessKey, expiry: expiry_ = expiry, showDeposit } = options
|
|
63
|
+
const { accessKey: key = accessKey, expiry: expiry_ = expiry, method, showDeposit } = options
|
|
63
64
|
|
|
64
65
|
return {
|
|
65
66
|
method: 'wallet_connect',
|
|
@@ -71,7 +72,8 @@ function connectRequest(
|
|
|
71
72
|
keyType: key.keyType,
|
|
72
73
|
publicKey: key.publicKey,
|
|
73
74
|
},
|
|
74
|
-
...(
|
|
75
|
+
...(method ? { method } : {}),
|
|
76
|
+
...(showDeposit !== undefined ? { showDeposit } : {}),
|
|
75
77
|
},
|
|
76
78
|
},
|
|
77
79
|
],
|
|
@@ -237,9 +239,11 @@ describe('Provider.create', () => {
|
|
|
237
239
|
|
|
238
240
|
await provider.request(
|
|
239
241
|
connectRequest({
|
|
242
|
+
method: 'register',
|
|
240
243
|
showDeposit: {
|
|
241
244
|
amount: '50',
|
|
242
245
|
displayName: 'DoorDash',
|
|
246
|
+
on: 'register',
|
|
243
247
|
token: 'USDC',
|
|
244
248
|
},
|
|
245
249
|
}),
|
|
@@ -250,6 +254,7 @@ describe('Provider.create', () => {
|
|
|
250
254
|
{
|
|
251
255
|
"amount": "50",
|
|
252
256
|
"displayName": "DoorDash",
|
|
257
|
+
"on": "register",
|
|
253
258
|
"token": "USDC",
|
|
254
259
|
},
|
|
255
260
|
]
|
|
@@ -259,6 +264,45 @@ describe('Provider.create', () => {
|
|
|
259
264
|
}
|
|
260
265
|
})
|
|
261
266
|
|
|
267
|
+
test('behavior: forwards showDeposit through login device-code requests', async () => {
|
|
268
|
+
const handler = createHandler()
|
|
269
|
+
const server = await createServer(handler.listener)
|
|
270
|
+
const pendingShowDeposit: z.output<typeof CliAuth.pendingResponse>['showDeposit'][] = []
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const provider = Provider.create({
|
|
274
|
+
chains: [chain],
|
|
275
|
+
open: async (url) => {
|
|
276
|
+
const code = new URL(url).searchParams.get('code')!
|
|
277
|
+
const response = await fetch(`${server.url}/cli-auth/pending/${code}`)
|
|
278
|
+
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
279
|
+
pendingShowDeposit.push(pending.showDeposit)
|
|
280
|
+
await fetch(`${server.url}/cli-auth`, {
|
|
281
|
+
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
282
|
+
headers: { 'content-type': 'application/json' },
|
|
283
|
+
method: 'POST',
|
|
284
|
+
})
|
|
285
|
+
},
|
|
286
|
+
host: `${server.url}/cli-auth`,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
await provider.request(
|
|
290
|
+
connectRequest({
|
|
291
|
+
method: 'login',
|
|
292
|
+
showDeposit: true,
|
|
293
|
+
}),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
expect(pendingShowDeposit).toMatchInlineSnapshot(`
|
|
297
|
+
[
|
|
298
|
+
true,
|
|
299
|
+
]
|
|
300
|
+
`)
|
|
301
|
+
} finally {
|
|
302
|
+
await server.closeAsync()
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
262
306
|
test('behavior: browser-open failures surface the URL and code', async () => {
|
|
263
307
|
const handler = createHandler()
|
|
264
308
|
const server = await createServer(handler.listener)
|
package/src/core/AccessKey.ts
CHANGED
|
@@ -251,7 +251,7 @@ export declare namespace authorize {
|
|
|
251
251
|
/** Options for {@link authorize}. */
|
|
252
252
|
type Options = {
|
|
253
253
|
/** Root account that owns this access key and signs its authorization. */
|
|
254
|
-
account: TempoAccount.Account
|
|
254
|
+
account: Pick<TempoAccount.Account, 'address' | 'sign'>
|
|
255
255
|
/** Default chain ID for the authorization when `parameters.chainId` is not set. */
|
|
256
256
|
chainId: bigint | number
|
|
257
257
|
/** Access key authorization parameters. */
|
package/src/core/Adapter.ts
CHANGED
|
@@ -245,7 +245,7 @@ export declare namespace loadAccounts {
|
|
|
245
245
|
type Capabilities = NonNullable<
|
|
246
246
|
NonNullable<Rpc.wallet_connect.Decoded['params']>[number]['capabilities']
|
|
247
247
|
>
|
|
248
|
-
type ShowDeposit = Extract<Capabilities, { method
|
|
248
|
+
type ShowDeposit = Extract<Capabilities, { method?: 'login' | undefined }>['showDeposit']
|
|
249
249
|
|
|
250
250
|
type Parameters = {
|
|
251
251
|
/** Grant an access key during the ceremony. */
|
|
@@ -263,7 +263,7 @@ export declare namespace loadAccounts {
|
|
|
263
263
|
personalSign?: { message: string } | undefined
|
|
264
264
|
/** When `true`, prompts the user to pick from all available credentials instead of using the last-used one. */
|
|
265
265
|
selectAccount?: boolean | undefined
|
|
266
|
-
/** Show the deposit flow after
|
|
266
|
+
/** Show the deposit flow after the connect ceremony succeeds. */
|
|
267
267
|
showDeposit?: ShowDeposit | undefined
|
|
268
268
|
}
|
|
269
269
|
type ReturnType = {
|
package/src/core/Provider.ts
CHANGED
|
@@ -664,6 +664,9 @@ export function create(options: create.Options = {}): create.ReturnType {
|
|
|
664
664
|
authorizeAccessKey,
|
|
665
665
|
selectAccount: capabilities?.selectAccount,
|
|
666
666
|
...(personalSign_request ? { personalSign: personalSign_request } : {}),
|
|
667
|
+
...(capabilities?.showDeposit !== undefined
|
|
668
|
+
? { showDeposit: capabilities.showDeposit }
|
|
669
|
+
: {}),
|
|
667
670
|
},
|
|
668
671
|
request,
|
|
669
672
|
)
|
|
@@ -105,6 +105,7 @@ describe('Encoded', () => {
|
|
|
105
105
|
type Register = Extract<Capabilities, { method: 'register' }>
|
|
106
106
|
type Login = Extract<Capabilities, { method?: 'login' | undefined }>
|
|
107
107
|
type ShowDeposit = Register['showDeposit']
|
|
108
|
+
type LoginShowDeposit = Login['showDeposit']
|
|
108
109
|
type ShowDepositObject = Exclude<Exclude<ShowDeposit, boolean | undefined>, undefined>
|
|
109
110
|
|
|
110
111
|
expectTypeOf<ShowDeposit>().toMatchTypeOf<
|
|
@@ -112,11 +113,12 @@ describe('Encoded', () => {
|
|
|
112
113
|
| {
|
|
113
114
|
amount?: string | undefined
|
|
114
115
|
displayName?: string | undefined
|
|
116
|
+
on?: 'login' | 'register' | undefined
|
|
115
117
|
token?: string | undefined
|
|
116
118
|
}
|
|
117
119
|
| undefined
|
|
118
120
|
>()
|
|
119
|
-
expectTypeOf<
|
|
121
|
+
expectTypeOf<LoginShowDeposit>().toEqualTypeOf<ShowDeposit>()
|
|
120
122
|
expectTypeOf<ShowDepositObject>().not.toHaveProperty('address')
|
|
121
123
|
expectTypeOf<ShowDepositObject>().not.toHaveProperty('chainId')
|
|
122
124
|
})
|
|
@@ -80,10 +80,34 @@ describe('privy', () => {
|
|
|
80
80
|
`)
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
+
test('default: createAccount can select the active embedded wallet', async () => {
|
|
84
|
+
const { adapter, client } = setup({ createAddresses: [other] })
|
|
85
|
+
client.addWallet(other)
|
|
86
|
+
|
|
87
|
+
const result = await adapter.actions.createAccount(
|
|
88
|
+
{ digest: '0x1234', name: 'Ada' },
|
|
89
|
+
{ method: 'wallet_connect', params: undefined },
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(client.signWith).toMatchInlineSnapshot(`
|
|
93
|
+
[
|
|
94
|
+
"0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
95
|
+
]
|
|
96
|
+
`)
|
|
97
|
+
expect(result.accounts).toMatchInlineSnapshot(`
|
|
98
|
+
[
|
|
99
|
+
{
|
|
100
|
+
"address": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
101
|
+
"label": "Ada",
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
`)
|
|
105
|
+
})
|
|
106
|
+
|
|
83
107
|
test('default: loadAccounts delegates login and caches embedded wallets for signing', async () => {
|
|
84
|
-
const { adapter, client } = setup()
|
|
108
|
+
const { adapter, client, store } = setup()
|
|
85
109
|
|
|
86
|
-
await
|
|
110
|
+
await connect({ adapter, store })
|
|
87
111
|
const result = await adapter.actions.signPersonalMessage(
|
|
88
112
|
{ address, data: '0x68656c6c6f' },
|
|
89
113
|
{ method: 'personal_sign', params: ['0x68656c6c6f', address] },
|
|
@@ -100,6 +124,32 @@ describe('privy', () => {
|
|
|
100
124
|
)
|
|
101
125
|
})
|
|
102
126
|
|
|
127
|
+
test('default: loadAccounts can select and order embedded wallets', async () => {
|
|
128
|
+
const { adapter, client } = setup({ loadAddresses: [other, address] })
|
|
129
|
+
client.addWallet(other)
|
|
130
|
+
|
|
131
|
+
const result = await adapter.actions.loadAccounts(
|
|
132
|
+
{ digest: '0x1234' },
|
|
133
|
+
{ method: 'wallet_connect', params: undefined },
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
expect(client.signWith).toMatchInlineSnapshot(`
|
|
137
|
+
[
|
|
138
|
+
"0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
139
|
+
]
|
|
140
|
+
`)
|
|
141
|
+
expect(result.accounts).toMatchInlineSnapshot(`
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
"address": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"address": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
`)
|
|
151
|
+
})
|
|
152
|
+
|
|
103
153
|
test('default: loadAccounts can provision an external access key', async () => {
|
|
104
154
|
const { adapter, client } = setup()
|
|
105
155
|
|
|
@@ -144,6 +194,39 @@ describe('privy', () => {
|
|
|
144
194
|
`)
|
|
145
195
|
})
|
|
146
196
|
|
|
197
|
+
test('default: signs transactions with a materialized Privy account', async () => {
|
|
198
|
+
const { adapter, client, store } = setup()
|
|
199
|
+
await connect({ adapter, store })
|
|
200
|
+
|
|
201
|
+
const result = await adapter.actions.signTransaction(
|
|
202
|
+
{
|
|
203
|
+
chainId: 1,
|
|
204
|
+
from: address,
|
|
205
|
+
gas: 21_000n,
|
|
206
|
+
maxFeePerGas: 1n,
|
|
207
|
+
maxPriorityFeePerGas: 1n,
|
|
208
|
+
nonce: 0,
|
|
209
|
+
to: other,
|
|
210
|
+
value: 1n,
|
|
211
|
+
},
|
|
212
|
+
{ method: 'eth_signTransaction', params: [{ from: address }] },
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(client.signWith).toMatchInlineSnapshot(`
|
|
216
|
+
[
|
|
217
|
+
"0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
|
|
218
|
+
]
|
|
219
|
+
`)
|
|
220
|
+
expect(client.signPayloads).toMatchInlineSnapshot(`
|
|
221
|
+
[
|
|
222
|
+
"0x62f087d34b8a023e0461eb1b9a01267ba5b8400c13d2ffdac615ccec872cc288",
|
|
223
|
+
]
|
|
224
|
+
`)
|
|
225
|
+
expect(result).toMatchInlineSnapshot(
|
|
226
|
+
`"0x76f86a010101825208d8d7942b5ad5c4795c026514f8317c7a215e218dccd6cf0180c0808080808080c0b8418b0c18077cb78666296a4c0e8149935124f70d7820ec4e4ae81de428659d6c305c098dea43ccb536e813bb1c136eab5e96611de69489be6291169b2bb2318cde1b"`,
|
|
227
|
+
)
|
|
228
|
+
})
|
|
229
|
+
|
|
147
230
|
test('default: authorizeAccessKey signs with the connected Privy account', async () => {
|
|
148
231
|
const { adapter, client, store } = setup()
|
|
149
232
|
store.setState({ accounts: [{ address }], activeAccount: 0 })
|
|
@@ -386,6 +469,51 @@ describe('privy', () => {
|
|
|
386
469
|
expect(store.getState().accounts).toMatchInlineSnapshot(`[]`)
|
|
387
470
|
})
|
|
388
471
|
|
|
472
|
+
test('behavior: failed account selection does not poison silent restore cache', async () => {
|
|
473
|
+
const { adapter, store } = setup({ loadAddresses: [other] })
|
|
474
|
+
store.setState({ accounts: [{ address: other }], activeAccount: 0 })
|
|
475
|
+
|
|
476
|
+
await expect(
|
|
477
|
+
adapter.actions.loadAccounts(undefined, { method: 'wallet_connect', params: undefined }),
|
|
478
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
479
|
+
`[Provider.UnauthorizedError: Privy callback returned address "${other}" that was not found in the user's embedded wallets.]`,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
await expect(
|
|
483
|
+
adapter.actions.signPersonalMessage(
|
|
484
|
+
{ address: other, data: '0x68656c6c6f' },
|
|
485
|
+
{ method: 'personal_sign', params: ['0x68656c6c6f', other] },
|
|
486
|
+
),
|
|
487
|
+
).rejects.toMatchInlineSnapshot(
|
|
488
|
+
'[Provider.DisconnectedError: Privy session no longer matches persisted accounts.]',
|
|
489
|
+
)
|
|
490
|
+
expect(store.getState().accounts).toMatchInlineSnapshot(`[]`)
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
test('behavior: failed empty createAccount selection does not poison silent restore cache', async () => {
|
|
494
|
+
const { adapter, store } = setup({ createAddresses: [] })
|
|
495
|
+
store.setState({ accounts: [{ address: other }], activeAccount: 0 })
|
|
496
|
+
|
|
497
|
+
await expect(
|
|
498
|
+
adapter.actions.createAccount(
|
|
499
|
+
{ digest: '0x1234', name: 'Ada' },
|
|
500
|
+
{ method: 'wallet_connect', params: undefined },
|
|
501
|
+
),
|
|
502
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
503
|
+
`[Provider.DisconnectedError: Privy returned no wallet.]`,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
await expect(
|
|
507
|
+
adapter.actions.signPersonalMessage(
|
|
508
|
+
{ address: other, data: '0x68656c6c6f' },
|
|
509
|
+
{ method: 'personal_sign', params: ['0x68656c6c6f', other] },
|
|
510
|
+
),
|
|
511
|
+
).rejects.toMatchInlineSnapshot(
|
|
512
|
+
'[Provider.DisconnectedError: Privy session no longer matches persisted accounts.]',
|
|
513
|
+
)
|
|
514
|
+
expect(store.getState().accounts).toMatchInlineSnapshot(`[]`)
|
|
515
|
+
})
|
|
516
|
+
|
|
389
517
|
test('error: silent restore rejects non-hex secp256k1_sign results', async () => {
|
|
390
518
|
const { adapter, store } = setup({ signResult: 'not-hex' })
|
|
391
519
|
store.setState({ accounts: [{ address }], activeAccount: 0 })
|
|
@@ -410,8 +538,8 @@ describe('privy', () => {
|
|
|
410
538
|
})
|
|
411
539
|
|
|
412
540
|
test('error: malformed secp256k1_sign result is rejected by signer recovery', async () => {
|
|
413
|
-
const { adapter } = setup({ signResult: '0x1234' })
|
|
414
|
-
await
|
|
541
|
+
const { adapter, store } = setup({ signResult: '0x1234' })
|
|
542
|
+
await connect({ adapter, store })
|
|
415
543
|
|
|
416
544
|
await expect(
|
|
417
545
|
adapter.actions.signPersonalMessage(
|
|
@@ -424,8 +552,8 @@ describe('privy', () => {
|
|
|
424
552
|
})
|
|
425
553
|
|
|
426
554
|
test('error: signing for an unconnected address while others are connected throws Unauthorized', async () => {
|
|
427
|
-
const { adapter } = setup()
|
|
428
|
-
await
|
|
555
|
+
const { adapter, store } = setup()
|
|
556
|
+
await connect({ adapter, store })
|
|
429
557
|
|
|
430
558
|
await expect(
|
|
431
559
|
adapter.actions.signPersonalMessage(
|
|
@@ -478,8 +606,8 @@ describe('privy', () => {
|
|
|
478
606
|
})
|
|
479
607
|
|
|
480
608
|
test('error: signature recovered from a different key is rejected as Unauthorized', async () => {
|
|
481
|
-
const { adapter } = setup({ signWithPrivateKey: privateKeyB })
|
|
482
|
-
await
|
|
609
|
+
const { adapter, store } = setup({ signWithPrivateKey: privateKeyB })
|
|
610
|
+
await connect({ adapter, store })
|
|
483
611
|
|
|
484
612
|
await expect(
|
|
485
613
|
adapter.actions.signPersonalMessage(
|
|
@@ -503,12 +631,12 @@ function setup(options: setup.Options = {}) {
|
|
|
503
631
|
: {
|
|
504
632
|
createAccount: async () => {
|
|
505
633
|
client.createCalls++
|
|
506
|
-
return
|
|
634
|
+
return options.createAddresses
|
|
507
635
|
},
|
|
508
636
|
}),
|
|
509
637
|
loadAccounts: async () => {
|
|
510
638
|
client.loadCalls++
|
|
511
|
-
return
|
|
639
|
+
return options.loadAddresses
|
|
512
640
|
},
|
|
513
641
|
})({
|
|
514
642
|
getAccount: (() => {
|
|
@@ -527,10 +655,22 @@ function setup(options: setup.Options = {}) {
|
|
|
527
655
|
return { adapter, client, store }
|
|
528
656
|
}
|
|
529
657
|
|
|
658
|
+
async function connect(options: Pick<ReturnType<typeof setup>, 'adapter' | 'store'>) {
|
|
659
|
+
const { adapter, store } = options
|
|
660
|
+
const loaded = await adapter.actions.loadAccounts(undefined, {
|
|
661
|
+
method: 'wallet_connect',
|
|
662
|
+
params: undefined,
|
|
663
|
+
})
|
|
664
|
+
store.setState({ accounts: loaded.accounts, activeAccount: 0 })
|
|
665
|
+
return loaded
|
|
666
|
+
}
|
|
667
|
+
|
|
530
668
|
declare namespace setup {
|
|
531
669
|
type Options = {
|
|
532
670
|
/** Pass `false` to omit the adapter's `createAccount` callback (tests fallback to `loadAccounts`). */
|
|
533
671
|
createAccount?: false | undefined
|
|
672
|
+
createAddresses?: readonly Address[] | undefined
|
|
673
|
+
loadAddresses?: readonly Address[] | undefined
|
|
534
674
|
/** Make the mock client's `user.get` throw, to test restore-side session errors. */
|
|
535
675
|
restoreError?: unknown
|
|
536
676
|
token?: string | null | undefined
|