accounts 0.18.3 → 0.18.5

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 (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/Adapter.d.ts +4 -0
  3. package/dist/core/Adapter.d.ts.map +1 -1
  4. package/dist/core/Client.d.ts.map +1 -1
  5. package/dist/core/Client.js +62 -2
  6. package/dist/core/Client.js.map +1 -1
  7. package/dist/core/Provider.d.ts.map +1 -1
  8. package/dist/core/Provider.js +16 -4
  9. package/dist/core/Provider.js.map +1 -1
  10. package/dist/core/Schema.d.ts +30 -2
  11. package/dist/core/Schema.d.ts.map +1 -1
  12. package/dist/core/adapters/postMessage/mount.d.ts.map +1 -1
  13. package/dist/core/adapters/postMessage/mount.js +4 -2
  14. package/dist/core/adapters/postMessage/mount.js.map +1 -1
  15. package/dist/core/adapters/privy.d.ts.map +1 -1
  16. package/dist/core/adapters/privy.js +7 -2
  17. package/dist/core/adapters/privy.js.map +1 -1
  18. package/dist/core/zod/rpc.d.ts +18 -1
  19. package/dist/core/zod/rpc.d.ts.map +1 -1
  20. package/dist/core/zod/rpc.js +13 -1
  21. package/dist/core/zod/rpc.js.map +1 -1
  22. package/dist/react/Remote.d.ts.map +1 -1
  23. package/dist/react/Remote.js +5 -1
  24. package/dist/react/Remote.js.map +1 -1
  25. package/dist/trusted-hosts.json +1 -0
  26. package/package.json +4 -4
  27. package/src/core/Adapter.ts +4 -0
  28. package/src/core/Client.test.ts +153 -1
  29. package/src/core/Client.ts +73 -2
  30. package/src/core/Provider.localnet.test.ts +114 -0
  31. package/src/core/Provider.ts +25 -8
  32. package/src/core/Schema.test-d.ts +23 -0
  33. package/src/core/adapters/postMessage/mount.test.ts +51 -0
  34. package/src/core/adapters/postMessage/mount.ts +4 -2
  35. package/src/core/adapters/privy.test.ts +150 -8
  36. package/src/core/adapters/privy.ts +13 -5
  37. package/src/core/zod/request.test.ts +29 -1
  38. package/src/core/zod/rpc.ts +14 -3
  39. package/src/react/Remote.test.ts +153 -0
  40. package/src/react/Remote.ts +7 -1
  41. package/src/trusted-hosts.json +1 -0
@@ -11,6 +11,9 @@ import {
11
11
  } from 'viem'
12
12
  import {
13
13
  getBalance,
14
+ getBlockNumber,
15
+ getLogs,
16
+ getTransactionReceipt,
14
17
  sendCalls,
15
18
  sendTransactionSync,
16
19
  signMessage,
@@ -2192,6 +2195,66 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
2192
2195
  })
2193
2196
 
2194
2197
  describe('wallet_revokeAccessKey', () => {
2198
+ test('error: rejects an authorization for a different access key', async () => {
2199
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2200
+ await connect(provider)
2201
+
2202
+ const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
2203
+ const first = await provider.request({
2204
+ method: 'wallet_authorizeAccessKey',
2205
+ params: [{ expiry: Expiry.days(1) }],
2206
+ })
2207
+ const second = await provider.request({
2208
+ method: 'wallet_authorizeAccessKey',
2209
+ params: [{ expiry: Expiry.days(1) }],
2210
+ })
2211
+
2212
+ await expect(
2213
+ provider.request({
2214
+ method: 'wallet_revokeAccessKey',
2215
+ params: [
2216
+ {
2217
+ address: connected,
2218
+ accessKeyAddress: first.keyAuthorization.address!,
2219
+ keyAuthorization: second.keyAuthorization,
2220
+ },
2221
+ ],
2222
+ }),
2223
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
2224
+ `[RpcResponse.InvalidParamsError: \`keyAuthorization\` must authorize \`accessKeyAddress\`.]`,
2225
+ )
2226
+ })
2227
+
2228
+ test('behavior: atomically authorizes and revokes an unpublished access key', async () => {
2229
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2230
+ await connect(provider)
2231
+
2232
+ const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
2233
+ await fund(connected)
2234
+
2235
+ const { keyAuthorization } = await provider.request({
2236
+ method: 'wallet_authorizeAccessKey',
2237
+ params: [{ expiry: Expiry.days(1) }],
2238
+ })
2239
+
2240
+ await provider.request({
2241
+ method: 'wallet_revokeAccessKey',
2242
+ params: [
2243
+ {
2244
+ address: connected,
2245
+ accessKeyAddress: keyAuthorization.address!,
2246
+ keyAuthorization,
2247
+ },
2248
+ ],
2249
+ })
2250
+
2251
+ const metadata = await Actions.accessKey.getMetadata(getClient(), {
2252
+ account: connected,
2253
+ accessKey: keyAuthorization.address!,
2254
+ })
2255
+ expect(metadata.isRevoked).toMatchInlineSnapshot(`true`)
2256
+ })
2257
+
2195
2258
  test('default: revokes a granted access key on-chain', async () => {
2196
2259
  const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2197
2260
  await connect(provider)
@@ -3232,6 +3295,57 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
3232
3295
  expect((result.tx as { feePayerSignature?: unknown }).feePayerSignature).toBeDefined()
3233
3296
  })
3234
3297
 
3298
+ test('behavior: feePayer URL sponsors wallet_revokeAccessKey', async () => {
3299
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
3300
+ const connected = await connect(provider)
3301
+ await fund(connected)
3302
+ const { keyAuthorization } = await provider.request({
3303
+ method: 'wallet_authorizeAccessKey',
3304
+ params: [{ expiry: Expiry.days(1) }],
3305
+ })
3306
+ await provider.request({
3307
+ method: 'eth_sendTransactionSync',
3308
+ params: [{ calls: [transferCall] }],
3309
+ })
3310
+
3311
+ const rpc = getClient()
3312
+ const blockNumber = await getBlockNumber(rpc)
3313
+
3314
+ await provider.request({
3315
+ method: 'wallet_revokeAccessKey',
3316
+ params: [
3317
+ {
3318
+ accessKeyAddress: keyAuthorization.address!,
3319
+ address: connected,
3320
+ feePayer: server.url,
3321
+ },
3322
+ ],
3323
+ })
3324
+
3325
+ const logs = await getLogs(rpc, {
3326
+ address: Addresses.accountKeychain,
3327
+ fromBlock: blockNumber,
3328
+ toBlock: 'latest',
3329
+ })
3330
+ const event = Actions.accessKey.revoke.extractEvent(logs)
3331
+ const receipt = await getTransactionReceipt(rpc, { hash: event.transactionHash })
3332
+ const metadata = await Actions.accessKey.getMetadata(rpc, {
3333
+ account: connected,
3334
+ accessKey: keyAuthorization.address!,
3335
+ })
3336
+ expect({
3337
+ feePayer: receipt.feePayer,
3338
+ isRevoked: metadata.isRevoked,
3339
+ status: receipt.status,
3340
+ }).toMatchInlineSnapshot(`
3341
+ {
3342
+ "feePayer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
3343
+ "isRevoked": true,
3344
+ "status": "success",
3345
+ }
3346
+ `)
3347
+ })
3348
+
3235
3349
  test('behavior: feePayer: true uses default from Provider.create', async () => {
3236
3350
  const provider = Provider.create({
3237
3351
  adapter: adapter({ feePayer: server.url }),
@@ -558,28 +558,29 @@ export function create(options: create.Options = {}): create.ReturnType {
558
558
  })
559
559
  }
560
560
 
561
- async function revokeAccessKey(parameters: {
562
- address: Address.Address
563
- accessKeyAddress: Address.Address
564
- }) {
561
+ async function revokeAccessKey(parameters: Adapter.revokeAccessKey.Parameters) {
565
562
  const selected = await getAdapterAccount({ address: parameters.address })
563
+ const feePayer = parameters.feePayer
566
564
  const client = getWalletClient({
567
565
  account: selected.account,
566
+ feePayer: feePayer === true ? undefined : feePayer,
568
567
  transport: selected.transport,
569
568
  })
570
569
  if (selected.account.type === 'json-rpc') {
571
570
  await client.request({
572
571
  method: 'wallet_revokeAccessKey' as never,
573
- params: [parameters] as never,
572
+ params: [z.encode(Rpc.wallet_revokeAccessKey.parameters, parameters)] as never,
574
573
  })
575
574
  } else {
576
575
  try {
577
- await Actions.accessKey.revokeSync(getClient(), {
576
+ await Actions.accessKey.revokeSync(client, {
578
577
  account: selected.account as TempoAccount.Account,
579
578
  accessKey: parameters.accessKeyAddress,
579
+ ...(feePayer ? { feePayer: true as never } : {}),
580
+ ...(parameters.keyAuthorization ? { keyAuthorization: parameters.keyAuthorization } : {}),
580
581
  })
581
582
  } catch (error) {
582
- if (!AccessKey.isUnavailableError(error)) throw error
583
+ if (typeof feePayer === 'string' || !AccessKey.isUnavailableError(error)) throw error
583
584
  }
584
585
  }
585
586
  store.accessKeys.remove({
@@ -677,6 +678,13 @@ export function create(options: create.Options = {}): create.ReturnType {
677
678
  parameters: Adapter.revokeAccessKey.Parameters,
678
679
  request: Pick<Rpc.wallet_revokeAccessKey.Encoded, 'method' | 'params'>,
679
680
  ) {
681
+ if (
682
+ parameters.keyAuthorization &&
683
+ !AddressUtil.isEqual(parameters.keyAuthorization.address, parameters.accessKeyAddress)
684
+ )
685
+ throw new RpcResponse.InvalidParamsError({
686
+ message: '`keyAuthorization` must authorize `accessKeyAddress`.',
687
+ })
680
688
  if (actions.revokeAccessKey) return await actions.revokeAccessKey(parameters, request)
681
689
  if (instance.getAccount) return await revokeAccessKey(parameters)
682
690
  unsupported('revokeAccessKey')
@@ -713,6 +721,12 @@ export function create(options: create.Options = {}): create.ReturnType {
713
721
  return url
714
722
  }
715
723
 
724
+ function resolveRevocationFeePayer(
725
+ feePayer: string | boolean | undefined,
726
+ ): Adapter.revokeAccessKey.Parameters['feePayer'] {
727
+ return resolveFeePayer(feePayer) ?? (feePayer === true ? true : undefined)
728
+ }
729
+
716
730
  function stripAuthorizeAccessKey(
717
731
  parameters: create.AuthorizeAccessKeyParameters,
718
732
  ): Adapter.authorizeAccessKey.Parameters {
@@ -1608,7 +1622,10 @@ export function create(options: create.Options = {}): create.ReturnType {
1608
1622
  case 'wallet_revokeAccessKey': {
1609
1623
  assertConnected()
1610
1624
  const [decoded] = request._decoded.params
1611
- await revokeAccessKeyAction({ ...decoded }, request)
1625
+ await revokeAccessKeyAction(
1626
+ { ...decoded, feePayer: resolveRevocationFeePayer(decoded.feePayer) },
1627
+ request,
1628
+ )
1612
1629
  return
1613
1630
  }
1614
1631
 
@@ -1,4 +1,5 @@
1
1
  import type { RpcSchema } from 'ox'
2
+ import type { KeyAuthorization } from 'ox/tempo'
2
3
  import type { Hex } from 'viem'
3
4
  import { describe, expectTypeOf, test } from 'vp/test'
4
5
 
@@ -214,6 +215,15 @@ describe('Encoded', () => {
214
215
  amount?: string | undefined
215
216
  chainId?: Hex | undefined
216
217
  displayName?: string | undefined
218
+ intent?:
219
+ | 'applePay'
220
+ | 'credits'
221
+ | 'crypto'
222
+ | 'faucet'
223
+ | 'mach'
224
+ | 'referralCode'
225
+ | 'x'
226
+ | undefined
217
227
  token?: Hex | string | undefined
218
228
  },
219
229
  ]
@@ -300,6 +310,19 @@ describe('Request', () => {
300
310
  expectTypeOf<SwitchChain['params']>().toEqualTypeOf<readonly [{ chainId: number }]>()
301
311
  })
302
312
 
313
+ test('wallet_revokeAccessKey supports app-provided fee sponsorship', () => {
314
+ expectTypeOf<Rpc.wallet_revokeAccessKey.Decoded['params']>().toEqualTypeOf<
315
+ readonly [
316
+ {
317
+ accessKeyAddress: Hex
318
+ address: Hex
319
+ feePayer?: boolean | string | undefined
320
+ keyAuthorization?: KeyAuthorization.Signed | undefined
321
+ },
322
+ ]
323
+ >()
324
+ })
325
+
303
326
  test('eth_accounts has no params', () => {
304
327
  type EthAccounts = Extract<Schema.Request, { method: 'eth_accounts' }>
305
328
  expectTypeOf<EthAccounts>().not.toHaveProperty('params')
@@ -69,6 +69,57 @@ describe('auto', () => {
69
69
  })
70
70
  })
71
71
 
72
+ describe('iframe', () => {
73
+ test('behavior: matches the iframe color scheme to the wallet theme', () => {
74
+ const elements: Record<string, unknown>[] = []
75
+ vi.stubGlobal('document', {
76
+ body: {
77
+ appendChild() {},
78
+ },
79
+ createElement(tag: string) {
80
+ const element = {
81
+ appendChild() {},
82
+ contentWindow: null,
83
+ dataset: {},
84
+ innerHTML: '',
85
+ remove() {},
86
+ setAttribute() {},
87
+ style: {},
88
+ tag,
89
+ }
90
+ elements.push(element)
91
+ return element
92
+ },
93
+ })
94
+ vi.stubGlobal(
95
+ 'MutationObserver',
96
+ class {
97
+ disconnect() {}
98
+ observe() {}
99
+ },
100
+ )
101
+
102
+ Mount.iframe()({
103
+ host: 'https://wallet.tempo.xyz/post-message?scheme=dark',
104
+ onDismiss() {},
105
+ onInvalidate() {},
106
+ })
107
+
108
+ expect(elements.find((element) => element.tag === 'iframe')?.style).toMatchInlineSnapshot(`
109
+ {
110
+ "backgroundColor": "transparent",
111
+ "border": "0",
112
+ "colorScheme": "dark",
113
+ "height": "100%",
114
+ "left": "0",
115
+ "position": "fixed",
116
+ "top": "0",
117
+ "width": "100%",
118
+ }
119
+ `)
120
+ })
121
+ })
122
+
72
123
  describe('postMessage', () => {
73
124
  test('behavior: derives trusted hosts from the wallet host', () => {
74
125
  const factory = Object.assign(
@@ -96,7 +96,9 @@ export function iframe(): Factory {
96
96
  return Object.assign(
97
97
  (parameters: Factory.Parameters): Mount => {
98
98
  const { host, onDismiss, onInvalidate } = parameters
99
- const origin = new URL(host).origin
99
+ const url = new URL(host)
100
+ const origin = url.origin
101
+ const scheme = url.searchParams.get('scheme')
100
102
 
101
103
  const root = document.createElement('dialog')
102
104
  // Distinct marker from the bespoke `Dialog.iframe` (`data-tempo-wallet`)
@@ -132,7 +134,7 @@ export function iframe(): Factory {
132
134
  Object.assign(frame.style, {
133
135
  backgroundColor: 'transparent',
134
136
  border: '0',
135
- colorScheme: 'light dark',
137
+ colorScheme: scheme === 'light' || scheme === 'dark' ? scheme : 'light dark',
136
138
  height: '100%',
137
139
  left: '0',
138
140
  position: 'fixed',
@@ -1,5 +1,5 @@
1
1
  import { Address as core_Address, Hex, Secp256k1, Signature } from 'ox'
2
- import { decodeFunctionData } from 'viem'
2
+ import { BaseError, decodeFunctionData } from 'viem'
3
3
  import type { Address } from 'viem/accounts'
4
4
  import { Abis } from 'viem/tempo'
5
5
  import { describe, expect, test } from 'vp/test'
@@ -323,6 +323,140 @@ describe('privy', () => {
323
323
  expect(store.getState().accessKeys).toMatchInlineSnapshot(`[]`)
324
324
  })
325
325
 
326
+ test('behavior: revokeAccessKey forwards a fee payer URL', async () => {
327
+ const { adapter, client, store } = setup()
328
+ store.setState({ accounts: [{ address }], activeAccount: 0 })
329
+
330
+ await adapter.actions.revokeAccessKey!(
331
+ { accessKeyAddress: other, address, feePayer: 'https://example.com/fee-payer' },
332
+ {
333
+ method: 'wallet_revokeAccessKey',
334
+ params: [{ accessKeyAddress: other, address, feePayer: 'https://example.com/fee-payer' }],
335
+ },
336
+ )
337
+
338
+ expect({
339
+ clientOptions: client.clientOptions,
340
+ transactions: client.transactions,
341
+ }).toMatchInlineSnapshot(`
342
+ {
343
+ "clientOptions": [
344
+ {
345
+ "feePayer": "https://example.com/fee-payer",
346
+ },
347
+ ],
348
+ "transactions": [
349
+ {
350
+ "account": {
351
+ "address": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
352
+ "sign": [Function],
353
+ "signTransaction": [Function],
354
+ "source": "privy",
355
+ "type": "local",
356
+ },
357
+ "data": "0x5ae7ab320000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf",
358
+ "feePayer": true,
359
+ "to": "0xaAAAaaAA00000000000000000000000000000000",
360
+ },
361
+ ],
362
+ }
363
+ `)
364
+ })
365
+
366
+ test('error: revokeAccessKey preserves local state on external fee payer errors', async () => {
367
+ const transactionError = new BaseError('revoke failed', {
368
+ cause: Object.assign(new Error('execution reverted'), {
369
+ data: { errorName: 'KeyAlreadyRevoked' },
370
+ }),
371
+ })
372
+ const { adapter, store } = setup({ transactionError })
373
+ store.setState({
374
+ accounts: [{ address }],
375
+ activeAccount: 0,
376
+ accessKeys: [
377
+ {
378
+ access: address,
379
+ address: other,
380
+ chainId: 1,
381
+ keyType: 'secp256k1',
382
+ } as never,
383
+ ],
384
+ })
385
+
386
+ await expect(
387
+ adapter.actions.revokeAccessKey!(
388
+ { accessKeyAddress: other, address, feePayer: 'https://example.com/fee-payer' },
389
+ {
390
+ method: 'wallet_revokeAccessKey',
391
+ params: [{ accessKeyAddress: other, address, feePayer: 'https://example.com/fee-payer' }],
392
+ },
393
+ ),
394
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
395
+ [ContractFunctionExecutionError: revoke failed
396
+
397
+ Contract Call:
398
+ address: 0xaAAAaaAA00000000000000000000000000000000
399
+ function: revokeKey(address keyId)
400
+ args: (0x2b5ad5c4795c026514f8317c7a215e218dccd6cf)
401
+ sender: 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
402
+
403
+ Docs: https://viem.sh/docs/contract/writeContract
404
+ Details: execution reverted
405
+ Version: viem@2.56.0]
406
+ `)
407
+
408
+ expect(store.getState().accessKeys).toMatchInlineSnapshot(`
409
+ [
410
+ {
411
+ "access": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
412
+ "address": "0x2b5ad5c4795c026514f8317c7a215e218dccd6cf",
413
+ "chainId": 1,
414
+ "keyType": "secp256k1",
415
+ },
416
+ ]
417
+ `)
418
+ })
419
+
420
+ test('behavior: revokeAccessKey forwards default fee sponsorship', async () => {
421
+ const { adapter, client, store } = setup()
422
+ store.setState({ accounts: [{ address }], activeAccount: 0 })
423
+
424
+ await adapter.actions.revokeAccessKey!(
425
+ { accessKeyAddress: other, address, feePayer: true },
426
+ {
427
+ method: 'wallet_revokeAccessKey',
428
+ params: [{ accessKeyAddress: other, address, feePayer: true }],
429
+ },
430
+ )
431
+
432
+ expect({
433
+ clientOptions: client.clientOptions,
434
+ transactions: client.transactions,
435
+ }).toMatchInlineSnapshot(`
436
+ {
437
+ "clientOptions": [
438
+ {
439
+ "feePayer": undefined,
440
+ },
441
+ ],
442
+ "transactions": [
443
+ {
444
+ "account": {
445
+ "address": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
446
+ "sign": [Function],
447
+ "signTransaction": [Function],
448
+ "source": "privy",
449
+ "type": "local",
450
+ },
451
+ "data": "0x5ae7ab320000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf",
452
+ "feePayer": true,
453
+ "to": "0xaAAAaaAA00000000000000000000000000000000",
454
+ },
455
+ ],
456
+ }
457
+ `)
458
+ })
459
+
326
460
  test('behavior: signing silently restores wallet accounts via the Privy SDK', async () => {
327
461
  const { adapter, client, store } = setup()
328
462
  store.setState({ accounts: [{ address }], activeAccount: 0 })
@@ -642,13 +776,17 @@ function setup(options: setup.Options = {}) {
642
776
  getAccount: (() => {
643
777
  throw new Error('not implemented')
644
778
  }) as never,
645
- getClient: (() => ({
646
- chain: { id: 1 },
647
- sendTransaction: async (parameters: unknown) => {
648
- client.transactions.push(parameters)
649
- return Hex.padLeft('0x1', 32)
650
- },
651
- })) as never,
779
+ getClient: ((clientOptions: unknown) => {
780
+ client.clientOptions.push(clientOptions)
781
+ return {
782
+ chain: { id: 1 },
783
+ sendTransaction: async (parameters: unknown) => {
784
+ client.transactions.push(parameters)
785
+ if (options.transactionError) throw options.transactionError
786
+ return Hex.padLeft('0x1', 32)
787
+ },
788
+ }
789
+ }) as never,
652
790
  storage,
653
791
  store,
654
792
  })
@@ -675,6 +813,8 @@ declare namespace setup {
675
813
  restoreError?: unknown
676
814
  token?: string | null | undefined
677
815
  signError?: unknown
816
+ /** Make the mock wallet client transaction fail. */
817
+ transactionError?: unknown
678
818
  /** Override the value returned by the embedded provider's `secp256k1_sign`. */
679
819
  signResult?: unknown
680
820
  /** Force the test wallet to sign with this private key (for wrong-signer tests). */
@@ -683,6 +823,7 @@ declare namespace setup {
683
823
  }
684
824
 
685
825
  type MockClient = privy.Client & {
826
+ clientOptions: unknown[]
686
827
  createCalls: number
687
828
  initCalls: number
688
829
  loadCalls: number
@@ -699,6 +840,7 @@ type MockClient = privy.Client & {
699
840
 
700
841
  function createClient(options: setup.Options = {}) {
701
842
  const client: MockClient = {
843
+ clientOptions: [] as unknown[],
702
844
  createCalls: 0,
703
845
  initCalls: 0,
704
846
  loadCalls: 0,
@@ -570,13 +570,21 @@ export function privy<const client extends privy.Client>(
570
570
  },
571
571
  async revokeAccessKey(parameters) {
572
572
  const account = await getSigningAccount(parameters.address)
573
+ const { feePayer } = parameters
573
574
  try {
574
- await Actions.accessKey.revoke(getClient(), {
575
- account: account as never,
576
- accessKey: parameters.accessKeyAddress,
577
- })
575
+ await Actions.accessKey.revoke(
576
+ getClient({ feePayer: feePayer === true ? undefined : feePayer }),
577
+ {
578
+ account: account as never,
579
+ accessKey: parameters.accessKeyAddress,
580
+ ...(feePayer ? { feePayer: true } : {}),
581
+ ...(parameters.keyAuthorization
582
+ ? { keyAuthorization: parameters.keyAuthorization }
583
+ : {}),
584
+ },
585
+ )
578
586
  } catch (error) {
579
- if (!AccessKey.isUnavailableError(error)) throw error
587
+ if (typeof feePayer === 'string' || !AccessKey.isUnavailableError(error)) throw error
580
588
  }
581
589
  store.accessKeys.remove({
582
590
  accessKey: parameters.accessKeyAddress,
@@ -69,6 +69,32 @@ describe('validate', () => {
69
69
  `)
70
70
  })
71
71
 
72
+ test('behavior: preserves access-key revocation sponsorship', () => {
73
+ const result = RpcRequest.validate(Schema.Request, {
74
+ method: 'wallet_revokeAccessKey',
75
+ params: [
76
+ {
77
+ accessKeyAddress: '0x2222222222222222222222222222222222222222',
78
+ address: '0x1111111111111111111111111111111111111111',
79
+ feePayer: 'https://app.example.com/fee-payer',
80
+ },
81
+ ],
82
+ })
83
+
84
+ expect(result._decoded).toMatchInlineSnapshot(`
85
+ {
86
+ "method": "wallet_revokeAccessKey",
87
+ "params": [
88
+ {
89
+ "accessKeyAddress": "0x2222222222222222222222222222222222222222",
90
+ "address": "0x1111111111111111111111111111111111111111",
91
+ "feePayer": "https://app.example.com/fee-payer",
92
+ },
93
+ ],
94
+ }
95
+ `)
96
+ })
97
+
72
98
  test('default: validates wallet_switchEthereumChain with hex chainId', () => {
73
99
  const result = RpcRequest.validate(Schema.Request, {
74
100
  method: 'wallet_switchEthereumChain',
@@ -115,13 +141,14 @@ describe('validate', () => {
115
141
  `)
116
142
  })
117
143
 
118
- test('default: validates wallet_deposit with amount and token symbol', () => {
144
+ test('default: validates wallet_deposit with MACH intent and pre-filled fields', () => {
119
145
  const result = RpcRequest.validate(Schema.Request, {
120
146
  method: 'wallet_deposit',
121
147
  params: [
122
148
  {
123
149
  amount: '50',
124
150
  displayName: 'DoorDash',
151
+ intent: 'mach',
125
152
  token: 'USDC',
126
153
  },
127
154
  ],
@@ -133,6 +160,7 @@ describe('validate', () => {
133
160
  {
134
161
  "amount": "50",
135
162
  "displayName": "DoorDash",
163
+ "intent": "mach",
136
164
  "token": "USDC",
137
165
  },
138
166
  ],
@@ -540,11 +540,21 @@ export namespace wallet_authorizeAccessKey_strict {
540
540
  }
541
541
 
542
542
  export namespace wallet_revokeAccessKey {
543
+ /** Parameters for revoking an access key. */
544
+ export const parameters = z.object({
545
+ /** Root account address. */
546
+ address: u.address(),
547
+ /** Address of the access key to revoke. */
548
+ accessKeyAddress: u.address(),
549
+ /** App-provided fee sponsorship for the revocation transaction. */
550
+ feePayer: z.optional(z.union([z.boolean(), z.string()])),
551
+ /** Signed authorization for an access key that has not been published on-chain. */
552
+ keyAuthorization: z.optional(keyAuthorization),
553
+ })
554
+
543
555
  export const schema = Schema.defineItem({
544
556
  method: z.literal('wallet_revokeAccessKey'),
545
- params: z.readonly(
546
- z.tuple([z.object({ address: u.address(), accessKeyAddress: u.address() })]),
547
- ),
557
+ params: z.readonly(z.tuple([parameters])),
548
558
  returns: undefined,
549
559
  })
550
560
  export type Encoded = Schema.Encoded<typeof schema>
@@ -1021,6 +1031,7 @@ export namespace wallet_deposit {
1021
1031
  z.literal('credits'),
1022
1032
  z.literal('crypto'),
1023
1033
  z.literal('faucet'),
1034
+ z.literal('mach'),
1024
1035
  z.literal('referralCode'),
1025
1036
  z.literal('x'),
1026
1037
  ]),