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.
- package/CHANGELOG.md +17 -0
- package/dist/core/Adapter.d.ts +4 -0
- package/dist/core/Adapter.d.ts.map +1 -1
- package/dist/core/Client.d.ts.map +1 -1
- package/dist/core/Client.js +62 -2
- package/dist/core/Client.js.map +1 -1
- package/dist/core/Provider.d.ts.map +1 -1
- package/dist/core/Provider.js +16 -4
- package/dist/core/Provider.js.map +1 -1
- package/dist/core/Schema.d.ts +30 -2
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/adapters/postMessage/mount.d.ts.map +1 -1
- package/dist/core/adapters/postMessage/mount.js +4 -2
- package/dist/core/adapters/postMessage/mount.js.map +1 -1
- package/dist/core/adapters/privy.d.ts.map +1 -1
- package/dist/core/adapters/privy.js +7 -2
- package/dist/core/adapters/privy.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +18 -1
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +13 -1
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/react/Remote.d.ts.map +1 -1
- package/dist/react/Remote.js +5 -1
- package/dist/react/Remote.js.map +1 -1
- package/dist/trusted-hosts.json +1 -0
- package/package.json +4 -4
- package/src/core/Adapter.ts +4 -0
- package/src/core/Client.test.ts +153 -1
- package/src/core/Client.ts +73 -2
- package/src/core/Provider.localnet.test.ts +114 -0
- package/src/core/Provider.ts +25 -8
- package/src/core/Schema.test-d.ts +23 -0
- package/src/core/adapters/postMessage/mount.test.ts +51 -0
- package/src/core/adapters/postMessage/mount.ts +4 -2
- package/src/core/adapters/privy.test.ts +150 -8
- package/src/core/adapters/privy.ts +13 -5
- package/src/core/zod/request.test.ts +29 -1
- package/src/core/zod/rpc.ts +14 -3
- package/src/react/Remote.test.ts +153 -0
- package/src/react/Remote.ts +7 -1
- 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 }),
|
package/src/core/Provider.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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(
|
|
575
|
-
|
|
576
|
-
|
|
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
|
|
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
|
],
|
package/src/core/zod/rpc.ts
CHANGED
|
@@ -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
|
]),
|