accounts 0.18.4 → 0.19.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/core/AccessKey.d.ts +5 -1
  3. package/dist/core/AccessKey.d.ts.map +1 -1
  4. package/dist/core/AccessKey.js +2 -0
  5. package/dist/core/AccessKey.js.map +1 -1
  6. package/dist/core/Adapter.d.ts +4 -0
  7. package/dist/core/Adapter.d.ts.map +1 -1
  8. package/dist/core/Client.d.ts.map +1 -1
  9. package/dist/core/Client.js +62 -2
  10. package/dist/core/Client.js.map +1 -1
  11. package/dist/core/ExecutionError.d.ts.map +1 -1
  12. package/dist/core/ExecutionError.js +38 -3
  13. package/dist/core/ExecutionError.js.map +1 -1
  14. package/dist/core/Provider.d.ts.map +1 -1
  15. package/dist/core/Provider.js +16 -4
  16. package/dist/core/Provider.js.map +1 -1
  17. package/dist/core/Schema.d.ts +43 -0
  18. package/dist/core/Schema.d.ts.map +1 -1
  19. package/dist/core/adapters/privy.d.ts.map +1 -1
  20. package/dist/core/adapters/privy.js +7 -2
  21. package/dist/core/adapters/privy.js.map +1 -1
  22. package/dist/core/zod/rpc.d.ts +18 -0
  23. package/dist/core/zod/rpc.d.ts.map +1 -1
  24. package/dist/core/zod/rpc.js +13 -2
  25. package/dist/core/zod/rpc.js.map +1 -1
  26. package/dist/react/Remote.d.ts.map +1 -1
  27. package/dist/react/Remote.js +5 -1
  28. package/dist/react/Remote.js.map +1 -1
  29. package/dist/server/CliAuth.d.ts.map +1 -1
  30. package/dist/server/CliAuth.js +6 -4
  31. package/dist/server/CliAuth.js.map +1 -1
  32. package/dist/server/internal/handlers/relay.d.ts +0 -7
  33. package/dist/server/internal/handlers/relay.d.ts.map +1 -1
  34. package/dist/server/internal/handlers/relay.js +0 -46
  35. package/dist/server/internal/handlers/relay.js.map +1 -1
  36. package/dist/server/internal/handlers/sponsorship.d.ts +0 -2
  37. package/dist/server/internal/handlers/sponsorship.d.ts.map +1 -1
  38. package/dist/server/internal/handlers/sponsorship.js +1 -2
  39. package/dist/server/internal/handlers/sponsorship.js.map +1 -1
  40. package/dist/trusted-hosts.json +1 -0
  41. package/package.json +5 -5
  42. package/src/cli/Provider.localnet.test.ts +3 -3
  43. package/src/core/AccessKey.permissions.test.ts +73 -0
  44. package/src/core/AccessKey.test.ts +29 -27
  45. package/src/core/AccessKey.ts +7 -1
  46. package/src/core/Adapter.ts +4 -0
  47. package/src/core/Client.test.ts +153 -1
  48. package/src/core/Client.ts +73 -2
  49. package/src/core/ExecutionError.ts +39 -3
  50. package/src/core/Provider.localnet.test.ts +116 -1
  51. package/src/core/Provider.ts +25 -8
  52. package/src/core/Schema.test-d.ts +14 -0
  53. package/src/core/adapters/deviceCode/deviceCode.localnet.test.ts +4 -1
  54. package/src/core/adapters/privy.test.ts +150 -8
  55. package/src/core/adapters/privy.ts +13 -5
  56. package/src/core/zod/request.test.ts +26 -0
  57. package/src/core/zod/rpc.ts +15 -4
  58. package/src/react/Remote.test.ts +153 -0
  59. package/src/react/Remote.ts +7 -1
  60. package/src/server/CliAuth.test.ts +5 -3
  61. package/src/server/CliAuth.ts +6 -4
  62. package/src/server/internal/handlers/relay.ts +0 -56
  63. package/src/server/internal/handlers/sponsorship.ts +1 -4
  64. package/src/trusted-hosts.json +1 -0
  65. package/dist/server/internal/handlers/multisig.d.ts +0 -214
  66. package/dist/server/internal/handlers/multisig.d.ts.map +0 -1
  67. package/dist/server/internal/handlers/multisig.js +0 -505
  68. package/dist/server/internal/handlers/multisig.js.map +0 -1
  69. package/src/server/internal/handlers/multisig.localnet.test.ts +0 -94
  70. package/src/server/internal/handlers/multisig.test.ts +0 -743
  71. package/src/server/internal/handlers/multisig.ts +0 -774
@@ -21,7 +21,6 @@ export type Rpc = UnionOmit<ExecutionError, 'args'>
21
21
  /** Human-readable messages keyed by ABI error name. */
22
22
  export const messages: Record<AbiErrorName, string> = {
23
23
  AbdicationAlreadyScheduled: 'Capability {0} abdication is already scheduled.',
24
- AccountAlreadyInitialized: 'Account already initialized.',
25
24
  AccountNotAllowed: 'Account {0} is not allowed.',
26
25
  ActiveLeaderRemoved: 'The active leader cannot be removed.',
27
26
  ActiveSupplyWithoutBacking: 'Active supply has no backing.',
@@ -32,6 +31,7 @@ export const messages: Record<AbiErrorName, string> = {
32
31
  AlreadyInitialized: 'Already initialized.',
33
32
  AmountExceedsDeposit: 'Amount exceeds deposit.',
34
33
  AmountExceedsLimit: 'Amount exceeds limit.',
34
+ AmountIsZero: 'Amount cannot be zero.',
35
35
  AmountNotIncreasing: 'Amount is not increasing.',
36
36
  AmountOverflow: 'Amount overflow.',
37
37
  AssetsNotArrived: 'Assets for request {0} have not arrived.',
@@ -56,6 +56,8 @@ export const messages: Record<AbiErrorName, string> = {
56
56
  ContractPaused: 'Contract is paused.',
57
57
  CreditExceedsBalance:
58
58
  'Credit for asset {0} exceeds the balance. Available: {1}, requested total: {2}.',
59
+ CustomerIdIsZero: 'Customer ID cannot be zero.',
60
+ DeadlineExpired: 'Deadline {0} has expired at timestamp {1}.',
59
61
  DepositBlockCapacityExceeded: 'Deposit block capacity of {0} exceeded.',
60
62
  DepositOverflow: 'Deposit overflow.',
61
63
  DepositTooSmall: 'Deposit too small.',
@@ -76,6 +78,7 @@ export const messages: Record<AbiErrorName, string> = {
76
78
  EarnVaultNotSet: 'Earn vault is not set.',
77
79
  EmptyDeploymentId: 'Deployment ID cannot be empty.',
78
80
  EmptyEarnShareMetadata: 'Empty earn share metadata.',
81
+ EmptyInstallation: 'Installation cannot be empty.',
79
82
  EmptyMetadata: 'Empty metadata.',
80
83
  EmptyV1ValidatorSet: 'Validator set is empty.',
81
84
  EncryptionKeyExpired: 'Encryption key {0}, activated at block {1}, expired at block {2}.',
@@ -86,6 +89,7 @@ export const messages: Record<AbiErrorName, string> = {
86
89
  EngineEarnVaultMismatch: 'Engine earn vault mismatch.',
87
90
  EngineExitUnsupported: 'This engine does not support exits.',
88
91
  EngineSharesTooLarge: 'Engine share amount {0} is too large.',
92
+ ExactBalanceDeltaFailed: 'Balance change for {0} was {2}, expected {1}.',
89
93
  ExceedsMaxEarnShares: 'Maximum earn shares exceeded.',
90
94
  ExcessiveConversionLoss:
91
95
  'Conversion loss is excessive. Input shares: {0}, represented shares: {1}.',
@@ -99,6 +103,7 @@ export const messages: Record<AbiErrorName, string> = {
99
103
  FailedDeployment: 'Deployment failed.',
100
104
  FeeValuationStale: 'Fee valuation is stale.',
101
105
  FinalizedAssetMismatch: 'Finalized asset {0} does not match.',
106
+ FutureOracleTimestamp: 'Oracle timestamp {0} is after the current time ({1}).',
102
107
  GasFeeRateTooHigh: 'Gas fee rate too high.',
103
108
  GasLimitTooHigh: 'Gas limit too high.',
104
109
  GlobalFeeDisableUnavailable: 'Global fee disabling is unavailable.',
@@ -117,11 +122,13 @@ export const messages: Record<AbiErrorName, string> = {
117
122
  'Insufficient engine share surplus. Available: {0}, requested: {1}.',
118
123
  InsufficientFeeTokenBalance: 'Insufficient fee token balance.',
119
124
  InsufficientFunding: 'Insufficient funding. Required: {0}, actual: {1}.',
125
+ InsufficientInventory: 'Insufficient inventory for {0}: available {1}, required {2}.',
120
126
  InsufficientLiquidity: 'Not enough liquidity in the order book to fill this trade.',
121
127
  InsufficientOutput:
122
128
  'The output amount is below the slippage minimum — try increasing slippage tolerance.',
123
129
  InsufficientReserves: 'Insufficient reserves.',
124
130
  InvalidAccount: 'Invalid account.',
131
+ InvalidAddress: 'Invalid address.',
125
132
  InvalidAdmin: 'Invalid admin.',
126
133
  InvalidAmount: 'Invalid amount.',
127
134
  InvalidArrayLengths: 'Invalid array lengths.',
@@ -152,6 +159,7 @@ export const messages: Record<AbiErrorName, string> = {
152
159
  InvalidEngine: 'Invalid engine.',
153
160
  InvalidEngineShareScale: 'Invalid engine share scale.',
154
161
  InvalidEphemeralPubkey: 'Invalid ephemeral pubkey.',
162
+ InvalidExistingDeployment: 'Existing deployment {0} is invalid.',
155
163
  InvalidExpiringNonceExpiry: 'Invalid expiring nonce expiry.',
156
164
  InvalidFallbackRecipient: 'Invalid fallback recipient.',
157
165
  InvalidFeeClaimReceiver: 'Invalid fee claim receiver.',
@@ -163,16 +171,21 @@ export const messages: Record<AbiErrorName, string> = {
163
171
  InvalidLeader: 'Invalid leader.',
164
172
  InvalidLogoURI: 'Invalid logo URI.',
165
173
  InvalidMasterAddress: 'Invalid master address.',
174
+ InvalidMaxOracleAge: 'Invalid maximum oracle age.',
175
+ InvalidMaxPriceDeviationBps: 'Maximum price deviation {0} exceeds {1} basis points.',
166
176
  InvalidMaxRateAge: 'Invalid maximum rate age.',
167
177
  InvalidMigrationIndex: 'Invalid migration index.',
168
178
  InvalidMode: 'Invalid mode.',
169
179
  InvalidMultiproof: 'Invalid multiproof.',
180
+ InvalidMultisigOwner: 'Invalid multisig owner.',
170
181
  InvalidNonceKey: 'Invalid nonce key.',
182
+ InvalidOraclePriceToleranceBps: 'Oracle price tolerance {0} exceeds {1} basis points.',
171
183
  InvalidOwner: 'Invalid owner.',
172
184
  InvalidOwnerOrder: 'Invalid owner order.',
173
185
  InvalidPayee: 'Invalid payee.',
174
186
  InvalidPayload: 'Invalid payload.',
175
187
  InvalidPolicyType: 'Invalid policy type.',
188
+ InvalidPrice: 'Invalid price.',
176
189
  InvalidPrivateAsset: 'Invalid private asset.',
177
190
  InvalidProof: 'Invalid proof.',
178
191
  InvalidProofOfPossession: 'Invalid proof of possession.',
@@ -186,6 +199,7 @@ export const messages: Record<AbiErrorName, string> = {
186
199
  InvalidRecoveryAuthority: 'Invalid recovery authority.',
187
200
  InvalidReturnToken: 'Invalid return token.',
188
201
  InvalidRevealTo: 'Invalid reveal to.',
202
+ InvalidRewardConfiguration: 'Invalid reward configuration.',
189
203
  InvalidRoutePair: 'Invalid route pair.',
190
204
  InvalidSequencerSet: 'Invalid sequencer set.',
191
205
  InvalidSignature: 'Invalid signature.',
@@ -245,18 +259,21 @@ export const messages: Record<AbiErrorName, string> = {
245
259
  NotEmergencyGuardianOrOperator: 'Caller is not the emergency guardian or operator.',
246
260
  NotEngine: 'Not engine.',
247
261
  NotFactory: 'Not factory.',
262
+ NotGuardianOrOwner: 'Not the guardian or owner.',
248
263
  NotHostPort: '"{1}" is not a valid host:port for {0}.',
249
264
  NotInitialized: 'Not initialized.',
250
265
  NotIp: '"{0}" is not a valid IP address.',
251
266
  NotIpPort: '"{1}" is not a valid IP:port for {0}.',
252
- NotMultisigAccount: 'Not multisig account.',
253
267
  NotOperator: 'Not operator.',
254
268
  NotOwner: 'Not owner.',
255
269
  NotPauseAuthority: 'Not pause authority.',
270
+ NotPaused: 'Not paused.',
256
271
  NotPayeeOrOperator: 'Not payee or operator.',
257
272
  NotPayer: 'Not payer.',
258
273
  NotPendingAdmin: 'Not pending admin.',
259
274
  NotPendingDistributor: 'Not pending distributor.',
275
+ NotPendingOperator: 'Not the pending operator.',
276
+ NotPendingOwner: 'Not the pending owner.',
260
277
  NotRequesterOrJanitor: 'Caller is not the requester or janitor.',
261
278
  NotSelf: 'Not self.',
262
279
  NotSequencer: 'Not sequencer.',
@@ -266,6 +283,10 @@ export const messages: Record<AbiErrorName, string> = {
266
283
  OnlySequencer: 'Only the sequencer may call this function.',
267
284
  OnlyZoneInbox: 'Only the zone inbox may call this function.',
268
285
  OperatorMigrationDisabled: 'Operator migration disabled.',
286
+ OperatorTransferNotPending: 'No operator transfer is pending.',
287
+ OracleDecimalsChanged: 'Oracle decimals changed from {0} to {1}.',
288
+ OraclePriceToleranceExceeded: 'Oracle price {1} differs from {0} by more than {2} basis points.',
289
+ OracleTimestampTooOld: 'Oracle timestamp {1} is before the minimum ({0}).',
269
290
  OrderDoesNotExist: 'No order exists with the given ID.',
270
291
  OrderNotStale: 'This order is not yet eligible for stale-cleanup.',
271
292
  OwnableInvalidOwner: 'Owner {0} is invalid.',
@@ -281,13 +302,18 @@ export const messages: Record<AbiErrorName, string> = {
281
302
  PolicyNotFound: 'Policy not found.',
282
303
  PolicyNotSimple: 'Policy is not a simple policy.',
283
304
  PortalIsPaused: 'Portal is paused.',
305
+ PriceAnchorNotInitialized: 'Price anchor is not initialized for direction {0}.',
306
+ PriceDeviationExceeded:
307
+ 'Price {1} deviates from anchor {2} by more than {3} basis points for direction {0}.',
284
308
  ProofOfWorkFailed: 'Proof of work failed.',
285
309
  ProtectedAddress: 'Address is protected.',
286
310
  ProtocolNonceNotSupported: 'Protocol nonce is not supported.',
287
311
  PublicKeyAlreadyExists: 'Public key already exists.',
288
312
  QueueTrackingDisabled: 'Tracking is disabled for queue {0}.',
289
313
  QueueUnavailable: 'Queue unavailable.',
314
+ QueuedAssetsBelowMinimum: 'Queued assets ({1}) are below the minimum ({0}).',
290
315
  RateChangedWithinTransaction: 'Rate changed within the transaction. Expected {0}, got {1}.',
316
+ RecipientNotAllowed: 'Recipient {0} is not allowed.',
291
317
  ReentrancyGuardReentrantCall: 'Reentrant call blocked.',
292
318
  ReentrantCall: 'Reentrant call blocked.',
293
319
  ReentrantWithdrawal: 'Reentrant withdrawal blocked.',
@@ -299,18 +325,22 @@ export const messages: Record<AbiErrorName, string> = {
299
325
  RootTotalDecreased: 'Root total decreased from {0} to {1}.',
300
326
  RootTotalExceeded: 'Requested amount {0} exceeds the remaining entitlement {1}.',
301
327
  SameEngine: 'Same engine.',
302
- SameTransactionUpdateNotAllowed: 'Same transaction update not allowed.',
328
+ SameToken: 'Tokens must be different.',
303
329
  SequencerConfigurationUnchanged: 'Sequencer configuration unchanged.',
330
+ SettlementAlreadyConsumed: 'Settlement {0} has already been consumed.',
304
331
  SignatureTypeMismatch: 'Signature type mismatch. Expected {0}, got {1}.',
332
+ SlippageExceeded: 'Amount {1} exceeds slippage limit {0}.',
305
333
  SolveUnderfunded: 'Solve for asset {0} is underfunded. Delivered: {1}, required: {2}.',
306
334
  SpendingLimitExceeded: 'Spending limit exceeded.',
307
335
  StaleAccountantRate: 'Accountant rate updated at {0} is stale at time {1}; maximum age is {2}.',
308
336
  StaleLeadershipEpoch: 'Stale leadership epoch. Expected {0}, got {1}.',
337
+ StaleOraclePrice: 'Oracle price timestamp {0} exceeds the maximum age ({1}).',
309
338
  StaleRootVersion: 'Stale root version. Expected {0}, got {1}.',
310
339
  StaticCallNotAllowed: 'Static call not allowed.',
311
340
  SupplyCapExceeded: 'Supply cap exceeded.',
312
341
  SweepExceedsAvailableBalance:
313
342
  'Sweep for token {0} exceeds the balance. Available: {1}, requested: {2}.',
343
+ TakerNotAllowed: 'Taker {0} is not allowed.',
314
344
  TickOutOfBounds: 'Price tick {0} is outside the allowed range.',
315
345
  TokenAlreadyEnabled: 'Token already enabled.',
316
346
  TokenAlreadyExists: 'Token {0} already exists.',
@@ -319,6 +349,7 @@ export const messages: Record<AbiErrorName, string> = {
319
349
  TokenEnablementBlockCapacityExceeded: 'Token enablement block capacity of {0} exceeded.',
320
350
  TokenMetadataTooLong: 'Token metadata too long.',
321
351
  TokenNotEnabled: 'Token not enabled.',
352
+ TokenTransferFailed: 'Transfer of token {0} failed.',
322
353
  TokenTransferPolicyNotSet: 'Token transfer policy not set.',
323
354
  TooManyOwners: 'Too many owners.',
324
355
  TooManyWithdrawalsThisBlock: 'Too many withdrawals this block.',
@@ -327,6 +358,7 @@ export const messages: Record<AbiErrorName, string> = {
327
358
  Unauthorized: 'Unauthorized.',
328
359
  UnauthorizedCaller: 'Unauthorized caller.',
329
360
  UnauthorizedClaimer: 'Unauthorized claimer.',
361
+ UnauthorizedMultisigCaller: 'Unauthorized multisig caller.',
330
362
  UnexpectedAssetsReceived: 'Unexpected assets received. Expected {0}, got {1}.',
331
363
  UnexpectedEarnShares: 'Unexpected earn shares. Reported: {0}, measured: {1}.',
332
364
  UnexpectedRetainedAssets: 'Unexpected assets retained. Balance before: {0}, after: {1}.',
@@ -334,6 +366,8 @@ export const messages: Record<AbiErrorName, string> = {
334
366
  UnexpectedVenueSharesQueued: 'Unexpected venue shares queued. Expected {0}, got {1}.',
335
367
  Uninitialized: 'Uninitialized.',
336
368
  UnsupportedAccountantFreshness: 'Accountant {0} does not support freshness checks.',
369
+ UnsupportedDecimals: 'Unsupported decimals: base {0}, quote {1}, oracle {2}.',
370
+ UnsupportedToken: 'Token {0} is not supported.',
337
371
  UnsupportedTransferPolicy: 'Transfer policy {0} has unsupported type {1}.',
338
372
  ValidatorAlreadyDeactivated: 'Validator is already deactivated.',
339
373
  ValidatorAlreadyExists: 'Validator already exists.',
@@ -352,8 +386,10 @@ export const messages: Record<AbiErrorName, string> = {
352
386
  ZeroMinimumAssets: 'Minimum assets cannot be zero.',
353
387
  ZeroMinimumEarnShares: 'Minimum earn shares cannot be zero.',
354
388
  ZeroMinimumEngineShares: 'Minimum engine shares cannot be zero.',
389
+ ZeroMinimumQueuedAssets: 'Minimum queued assets cannot be zero.',
355
390
  ZeroPublicKey: 'Public key cannot be zero.',
356
391
  ZeroRoot: 'Root cannot be zero.',
392
+ ZeroSettlementId: 'Settlement ID cannot be zero.',
357
393
  ZeroStatementHash: 'Statement hash cannot be zero.',
358
394
  }
359
395
 
@@ -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,
@@ -1109,6 +1112,7 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
1109
1112
  {
1110
1113
  "contractAddress": null,
1111
1114
  "feeToken": "0x20c0000000000000000000000000000000000000",
1115
+ "multisig": undefined,
1112
1116
  "status": "success",
1113
1117
  "to": "0x20c0000000000000000000000000000000000000",
1114
1118
  "type": "0x76",
@@ -1386,7 +1390,7 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
1386
1390
  from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
1387
1391
 
1388
1392
  Details: plain send failure
1389
- Version: viem@2.56.0]
1393
+ Version: viem@2.56.9]
1390
1394
  `)
1391
1395
  })
1392
1396
  })
@@ -2192,6 +2196,66 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
2192
2196
  })
2193
2197
 
2194
2198
  describe('wallet_revokeAccessKey', () => {
2199
+ test('error: rejects an authorization for a different access key', async () => {
2200
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2201
+ await connect(provider)
2202
+
2203
+ const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
2204
+ const first = await provider.request({
2205
+ method: 'wallet_authorizeAccessKey',
2206
+ params: [{ expiry: Expiry.days(1) }],
2207
+ })
2208
+ const second = await provider.request({
2209
+ method: 'wallet_authorizeAccessKey',
2210
+ params: [{ expiry: Expiry.days(1) }],
2211
+ })
2212
+
2213
+ await expect(
2214
+ provider.request({
2215
+ method: 'wallet_revokeAccessKey',
2216
+ params: [
2217
+ {
2218
+ address: connected,
2219
+ accessKeyAddress: first.keyAuthorization.address!,
2220
+ keyAuthorization: second.keyAuthorization,
2221
+ },
2222
+ ],
2223
+ }),
2224
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
2225
+ `[RpcResponse.InvalidParamsError: \`keyAuthorization\` must authorize \`accessKeyAddress\`.]`,
2226
+ )
2227
+ })
2228
+
2229
+ test('behavior: atomically authorizes and revokes an unpublished access key', async () => {
2230
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2231
+ await connect(provider)
2232
+
2233
+ const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
2234
+ await fund(connected)
2235
+
2236
+ const { keyAuthorization } = await provider.request({
2237
+ method: 'wallet_authorizeAccessKey',
2238
+ params: [{ expiry: Expiry.days(1) }],
2239
+ })
2240
+
2241
+ await provider.request({
2242
+ method: 'wallet_revokeAccessKey',
2243
+ params: [
2244
+ {
2245
+ address: connected,
2246
+ accessKeyAddress: keyAuthorization.address!,
2247
+ keyAuthorization,
2248
+ },
2249
+ ],
2250
+ })
2251
+
2252
+ const metadata = await Actions.accessKey.getMetadata(getClient(), {
2253
+ account: connected,
2254
+ accessKey: keyAuthorization.address!,
2255
+ })
2256
+ expect(metadata.isRevoked).toMatchInlineSnapshot(`true`)
2257
+ })
2258
+
2195
2259
  test('default: revokes a granted access key on-chain', async () => {
2196
2260
  const provider = Provider.create({ adapter: adapter(), chains: [chain] })
2197
2261
  await connect(provider)
@@ -3232,6 +3296,57 @@ describe.each(adapters)('$name', ({ adapter, name }: (typeof adapters)[number])
3232
3296
  expect((result.tx as { feePayerSignature?: unknown }).feePayerSignature).toBeDefined()
3233
3297
  })
3234
3298
 
3299
+ test('behavior: feePayer URL sponsors wallet_revokeAccessKey', async () => {
3300
+ const provider = Provider.create({ adapter: adapter(), chains: [chain] })
3301
+ const connected = await connect(provider)
3302
+ await fund(connected)
3303
+ const { keyAuthorization } = await provider.request({
3304
+ method: 'wallet_authorizeAccessKey',
3305
+ params: [{ expiry: Expiry.days(1) }],
3306
+ })
3307
+ await provider.request({
3308
+ method: 'eth_sendTransactionSync',
3309
+ params: [{ calls: [transferCall] }],
3310
+ })
3311
+
3312
+ const rpc = getClient()
3313
+ const blockNumber = await getBlockNumber(rpc)
3314
+
3315
+ await provider.request({
3316
+ method: 'wallet_revokeAccessKey',
3317
+ params: [
3318
+ {
3319
+ accessKeyAddress: keyAuthorization.address!,
3320
+ address: connected,
3321
+ feePayer: server.url,
3322
+ },
3323
+ ],
3324
+ })
3325
+
3326
+ const logs = await getLogs(rpc, {
3327
+ address: Addresses.accountKeychain,
3328
+ fromBlock: blockNumber,
3329
+ toBlock: 'latest',
3330
+ })
3331
+ const event = Actions.accessKey.revoke.extractEvent(logs)
3332
+ const receipt = await getTransactionReceipt(rpc, { hash: event.transactionHash })
3333
+ const metadata = await Actions.accessKey.getMetadata(rpc, {
3334
+ account: connected,
3335
+ accessKey: keyAuthorization.address!,
3336
+ })
3337
+ expect({
3338
+ feePayer: receipt.feePayer,
3339
+ isRevoked: metadata.isRevoked,
3340
+ status: receipt.status,
3341
+ }).toMatchInlineSnapshot(`
3342
+ {
3343
+ "feePayer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
3344
+ "isRevoked": true,
3345
+ "status": "success",
3346
+ }
3347
+ `)
3348
+ })
3349
+
3235
3350
  test('behavior: feePayer: true uses default from Provider.create', async () => {
3236
3351
  const provider = Provider.create({
3237
3352
  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
 
@@ -309,6 +310,19 @@ describe('Request', () => {
309
310
  expectTypeOf<SwitchChain['params']>().toEqualTypeOf<readonly [{ chainId: number }]>()
310
311
  })
311
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
+
312
326
  test('eth_accounts has no params', () => {
313
327
  type EthAccounts = Extract<Schema.Request, { method: 'eth_accounts' }>
314
328
  expectTypeOf<EthAccounts>().not.toHaveProperty('params')
@@ -1,4 +1,5 @@
1
1
  import { Hex } from 'ox'
2
+ import { SignatureEnvelope } from 'ox/tempo'
2
3
  import { describe, expect, test } from 'vp/test'
3
4
  import type { DeviceCode } from 'wata'
4
5
 
@@ -61,7 +62,9 @@ describe('deviceCode', () => {
61
62
  const keyAuthorization = account.capabilities.keyAuthorization
62
63
  ? {
63
64
  ...account.capabilities.keyAuthorization,
64
- signature: { type: account.capabilities.keyAuthorization.signature.type },
65
+ signature: {
66
+ type: SignatureEnvelope.fromRpc(account.capabilities.keyAuthorization.signature).type,
67
+ },
65
68
  }
66
69
  : undefined
67
70
 
@@ -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.9]
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,