accounts 0.4.4 → 0.4.6

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cli/adapter.d.ts +0 -2
  3. package/dist/cli/adapter.d.ts.map +1 -1
  4. package/dist/cli/adapter.js +96 -72
  5. package/dist/cli/adapter.js.map +1 -1
  6. package/dist/core/Adapter.d.ts +6 -3
  7. package/dist/core/Adapter.d.ts.map +1 -1
  8. package/dist/core/Provider.d.ts.map +1 -1
  9. package/dist/core/Provider.js +14 -9
  10. package/dist/core/Provider.js.map +1 -1
  11. package/dist/core/Schema.d.ts +29 -23
  12. package/dist/core/Schema.d.ts.map +1 -1
  13. package/dist/core/adapters/dialog.d.ts.map +1 -1
  14. package/dist/core/adapters/dialog.js +15 -4
  15. package/dist/core/adapters/dialog.js.map +1 -1
  16. package/dist/core/adapters/local.d.ts.map +1 -1
  17. package/dist/core/adapters/local.js +4 -1
  18. package/dist/core/adapters/local.js.map +1 -1
  19. package/dist/core/zod/rpc.d.ts +17 -14
  20. package/dist/core/zod/rpc.d.ts.map +1 -1
  21. package/dist/core/zod/rpc.js +6 -2
  22. package/dist/core/zod/rpc.js.map +1 -1
  23. package/dist/server/CliAuth.d.ts +1 -0
  24. package/dist/server/CliAuth.d.ts.map +1 -1
  25. package/dist/server/CliAuth.js +3 -1
  26. package/dist/server/CliAuth.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/cli/Provider.test.ts +173 -12
  29. package/src/cli/adapter.ts +112 -80
  30. package/src/core/Adapter.ts +6 -3
  31. package/src/core/Provider.browser.test.ts +8 -7
  32. package/src/core/Provider.connect.browser.test.ts +3 -3
  33. package/src/core/Provider.test.ts +24 -10
  34. package/src/core/Provider.ts +12 -7
  35. package/src/core/Schema.test-d.ts +5 -12
  36. package/src/core/adapters/dialog.ts +20 -4
  37. package/src/core/adapters/local.ts +5 -2
  38. package/src/core/zod/rpc.ts +7 -2
  39. package/src/server/CliAuth.ts +2 -1
@@ -998,15 +998,29 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
998
998
  })
999
999
 
1000
1000
  describe('wallet_authorizeAccessKey', () => {
1001
+ test('behavior: without publicKey or address requires a connected account', async () => {
1002
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
1003
+
1004
+ await expect(
1005
+ provider.request({
1006
+ method: 'wallet_authorizeAccessKey',
1007
+ params: [{ expiry: Expiry.days(1) }],
1008
+ }),
1009
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1010
+ `[Provider.DisconnectedError: No active account.]`,
1011
+ )
1012
+ })
1013
+
1001
1014
  test('default: grants an access key and returns its address', async () => {
1002
1015
  const provider = Provider.create({ adapter: adapter(), chains: [chain] })
1003
- await connect(provider)
1016
+ const rootAddress = await connect(provider)
1004
1017
 
1005
1018
  const result = await provider.request({
1006
1019
  method: 'wallet_authorizeAccessKey',
1007
1020
  params: [{ expiry: Expiry.days(1) }],
1008
1021
  })
1009
- expect(result.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
1022
+ expect(result.keyAuthorization.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
1023
+ expect(result.rootAddress).toBe(rootAddress)
1010
1024
  })
1011
1025
 
1012
1026
  test('behavior: granted access key is used for sendTransactionSync', async () => {
@@ -1035,8 +1049,8 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1035
1049
  method: 'wallet_authorizeAccessKey',
1036
1050
  params: [{ expiry }],
1037
1051
  })
1038
- expect(result.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
1039
- expect(result.expiry).toBe(Hex.fromNumber(expiry))
1052
+ expect(result.keyAuthorization.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
1053
+ expect(result.keyAuthorization.expiry).toBe(Hex.fromNumber(expiry))
1040
1054
  })
1041
1055
 
1042
1056
  test('behavior: expired access key falls back to root account', async () => {
@@ -1086,7 +1100,7 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1086
1100
  },
1087
1101
  ],
1088
1102
  })
1089
- expect(result.limits).toMatchInlineSnapshot(`
1103
+ expect(result.keyAuthorization.limits).toMatchInlineSnapshot(`
1090
1104
  [
1091
1105
  {
1092
1106
  "limit": "0x4c4b40",
@@ -1162,14 +1176,14 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1162
1176
  await connect(provider)
1163
1177
 
1164
1178
  const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
1165
- const { keyId } = await provider.request({
1179
+ const { keyAuthorization } = await provider.request({
1166
1180
  method: 'wallet_authorizeAccessKey',
1167
1181
  params: [{ expiry: Expiry.days(1) }],
1168
1182
  })
1169
1183
 
1170
1184
  await provider.request({
1171
1185
  method: 'wallet_revokeAccessKey',
1172
- params: [{ address: connected, accessKeyAddress: keyId }],
1186
+ params: [{ address: connected, accessKeyAddress: keyAuthorization.address }],
1173
1187
  })
1174
1188
 
1175
1189
  // After revoking, sendTransactionSync should use root key (still works)
@@ -1344,8 +1358,8 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1344
1358
  method: 'wallet_authorizeAccessKey',
1345
1359
  params: [{ ...accessKeyAccount, expiry: Expiry.days(1) }],
1346
1360
  })
1347
- expect(result.keyId).toBe(accessKeyAccount.address)
1348
- expect(result.keyType).toBe('p256')
1361
+ expect(result.keyAuthorization.keyId).toBe(accessKeyAccount.address)
1362
+ expect(result.keyAuthorization.keyType).toBe('p256')
1349
1363
  })
1350
1364
 
1351
1365
  test('behavior: external key authorization can be used to send a transaction', async () => {
@@ -1365,7 +1379,7 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
1365
1379
  const receipt = await sendTransactionSync(client, {
1366
1380
  account: TempoAccount.fromWebCryptoP256(keyPair, { access: rootAddress }),
1367
1381
  calls: [transferCall],
1368
- keyAuthorization: KeyAuthorization.fromRpc(result),
1382
+ keyAuthorization: KeyAuthorization.fromRpc(result.keyAuthorization),
1369
1383
  })
1370
1384
  expect(receipt.status).toBe('success')
1371
1385
  })
@@ -112,11 +112,14 @@ export function create(options: create.Options = {}): create.ReturnType {
112
112
  return merged
113
113
  }
114
114
 
115
- /** Resolves the `feePayer` field from a transaction request into a URL string or `undefined`. */
115
+ /** Resolves the `feePayer` field from a transaction request into an absolute URL string or `undefined`. */
116
116
  function resolveFeePayer(feePayer: string | boolean | undefined): string | undefined {
117
- if (typeof feePayer === 'string') return feePayer
118
- if (feePayer === true) return feePayerUrl
119
- return undefined
117
+ const url =
118
+ typeof feePayer === 'string' ? feePayer : feePayer === true ? feePayerUrl : undefined
119
+ if (!url) return undefined
120
+ if (url.startsWith('http://') || url.startsWith('https://')) return url
121
+ if (typeof window !== 'undefined') return new URL(url, window.location.origin).href
122
+ return url
120
123
  }
121
124
 
122
125
  const provider = Object.assign(
@@ -437,7 +440,6 @@ export function create(options: create.Options = {}): create.ReturnType {
437
440
  return
438
441
 
439
442
  case 'wallet_authorizeAccessKey': {
440
- assertConnected()
441
443
  if (!actions.authorizeAccessKey)
442
444
  throw new ox_Provider.UnsupportedMethodError({
443
445
  message: '`authorizeAccessKey` not supported by adapter.',
@@ -445,8 +447,11 @@ export function create(options: create.Options = {}): create.ReturnType {
445
447
  const decoded = request._decoded.params[0]
446
448
  const result = await actions.authorizeAccessKey(decoded, request)
447
449
  return {
448
- ...result,
449
- address: result.keyId,
450
+ keyAuthorization: {
451
+ ...result.keyAuthorization,
452
+ address: result.keyAuthorization.keyId,
453
+ },
454
+ rootAddress: result.rootAddress,
450
455
  } satisfies Rpc.wallet_authorizeAccessKey.Encoded['returns']
451
456
  }
452
457
 
@@ -106,18 +106,11 @@ describe('Encoded', () => {
106
106
  })
107
107
 
108
108
  test('wallet_authorizeAccessKey', () => {
109
- expectTypeOf<Rpc.wallet_authorizeAccessKey.Encoded>().toMatchTypeOf<{
110
- method: 'wallet_authorizeAccessKey'
111
- params: readonly [
112
- {
113
- address?: Hex | undefined
114
- expiry: number
115
- keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
116
- limits?: readonly { token: Hex; limit: Hex }[] | undefined
117
- publicKey?: Hex | undefined
118
- },
119
- ]
120
- }>()
109
+ expectTypeOf<Rpc.wallet_authorizeAccessKey.Encoded>().toHaveProperty('method')
110
+ expectTypeOf<Rpc.wallet_authorizeAccessKey.Encoded['returns']>().toHaveProperty('rootAddress')
111
+ expectTypeOf<Rpc.wallet_authorizeAccessKey.Encoded['returns']>().toHaveProperty(
112
+ 'keyAuthorization',
113
+ )
121
114
  })
122
115
 
123
116
  test('wallet_disconnect', () => {
@@ -33,6 +33,13 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
33
33
  rdns = 'xyz.tempo',
34
34
  } = options
35
35
 
36
+ if (typeof window !== 'undefined' && !window.isSecureContext)
37
+ console.warn(
38
+ '[accounts] Detected insecure context (HTTP).',
39
+ `\n\nThe Tempo Wallet iframe dialog is not supported on HTTP origins (${window.location.origin})`,
40
+ 'due to lack of WebAuthn passkey support in non-secure contexts.',
41
+ )
42
+
36
43
  return Adapter.define({ icon, name, rdns }, ({ getAccount, getClient, store }) => {
37
44
  const listeners = new Set<(requestQueue: readonly Store.QueuedRequest[]) => void>()
38
45
  const requestStore = ox_RpcRequest.createStore()
@@ -282,7 +289,10 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
282
289
  return await account.signTransaction(prepared as never)
283
290
  })
284
291
  if (result !== undefined) return result
285
- return await provider.request(request)
292
+ return await provider.request({
293
+ ...request,
294
+ params: [z.encode(Rpc.transactionRequest, parameters)] as const,
295
+ })
286
296
  },
287
297
 
288
298
  async signTypedData(_params, request) {
@@ -309,7 +319,10 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
309
319
  })
310
320
  })
311
321
  if (result !== undefined) return result
312
- return await provider.request(request)
322
+ return await provider.request({
323
+ ...request,
324
+ params: [z.encode(Rpc.transactionRequest, parameters)] as const,
325
+ })
313
326
  },
314
327
 
315
328
  async sendTransactionSync(parameters, request) {
@@ -332,7 +345,10 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
332
345
  })
333
346
  })
334
347
  if (result !== undefined) return result
335
- return await provider.request(request)
348
+ return await provider.request({
349
+ ...request,
350
+ params: [z.encode(Rpc.transactionRequest, parameters)] as const,
351
+ })
336
352
  },
337
353
 
338
354
  async authorizeAccessKey(parameters, request) {
@@ -345,7 +361,7 @@ export function dialog(options: dialog.Options = {}): Adapter.Adapter {
345
361
 
346
362
  if (accessKey) {
347
363
  const account = getAccount({ accessKey: false, signable: false })
348
- saveAccessKey(account.address, result, accessKey.keyPair)
364
+ saveAccessKey(account.address, result.keyAuthorization, accessKey.keyPair)
349
365
  }
350
366
 
351
367
  return result
@@ -73,7 +73,7 @@ export function local(options: local.Options): Adapter.Adapter {
73
73
  options: {
74
74
  signature?: Hex.Hex | undefined
75
75
  } = {},
76
- ): Promise<Adapter.authorizeAccessKey.ReturnType> {
76
+ ) {
77
77
  const { keyPair } = prepared
78
78
 
79
79
  const keyAuthorization = await (async () => {
@@ -140,7 +140,10 @@ export function local(options: local.Options): Adapter.Adapter {
140
140
  async authorizeAccessKey(parameters) {
141
141
  const prepared = await prepareKeyAuthorization(parameters)
142
142
  const account = getAccount({ accessKey: false, signable: true })
143
- return await signKeyAuthorization(account, prepared, { signature: parameters.signature })
143
+ const keyAuthorization = await signKeyAuthorization(account, prepared, {
144
+ signature: parameters.signature,
145
+ })
146
+ return { keyAuthorization, rootAddress: account.address }
144
147
  },
145
148
  async loadAccounts(parameters) {
146
149
  const { authorizeAccessKey, ...rest } =
@@ -67,7 +67,7 @@ export const transactionRequest = z.object({
67
67
  ),
68
68
  calls: z.optional(z.readonly(z.array(call))),
69
69
  chainId: z.optional(u.number()),
70
- feePayer: z.optional(z.union([z.boolean(), z.url()])),
70
+ feePayer: z.optional(z.union([z.boolean(), z.string()])),
71
71
  feeToken: z.optional(u.address()),
72
72
  from: z.optional(u.address()),
73
73
  gas: z.optional(u.bigint()),
@@ -259,10 +259,15 @@ export namespace wallet_authorizeAccessKey {
259
259
  publicKey: z.optional(u.hex()),
260
260
  })
261
261
 
262
+ const returns = z.object({
263
+ keyAuthorization,
264
+ rootAddress: u.address(),
265
+ })
266
+
262
267
  export const schema = Schema.defineItem({
263
268
  method: z.literal('wallet_authorizeAccessKey'),
264
269
  params: z.readonly(z.tuple([parameters])),
265
- returns: keyAuthorization,
270
+ returns,
266
271
  })
267
272
  export type Encoded = Schema.Encoded<typeof schema>
268
273
  export type Decoded = Schema.Decoded<typeof schema>
@@ -27,6 +27,7 @@ export const keyAuthorization = z.object({
27
27
  /** Request body for `POST /auth/pkce/code`. */
28
28
  export const createRequest = z.object({
29
29
  account: z.optional(u.address()),
30
+ chainId: z.optional(u.bigint()),
30
31
  codeChallenge: z.string(),
31
32
  expiry: z.optional(z.number()),
32
33
  keyType: z.optional(keyType),
@@ -345,7 +346,6 @@ export async function createDeviceCode(
345
346
  options: createDeviceCode.Options,
346
347
  ): Promise<createDeviceCode.ReturnType> {
347
348
  const {
348
- chainId = BigInt(tempo.id),
349
349
  now = Date.now,
350
350
  policy = Policy.allow(),
351
351
  random = randomBytes,
@@ -353,6 +353,7 @@ export async function createDeviceCode(
353
353
  store = Store.memory(),
354
354
  ttlMs = 10 * 60 * 1_000,
355
355
  } = options
356
+ const chainId = request.chainId ?? options.chainId ?? BigInt(tempo.id)
356
357
  const { account, codeChallenge, pubKey } = request
357
358
  const keyType = request.keyType ?? 'secp256k1'
358
359
  const approved = await policy.validate({