accounts 0.18.4 → 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 +10 -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 +28 -0
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/adapters/privy.d.ts.map +1 -1
- package/dist/core/adapters/privy.js +7 -2
- package/dist/core/adapters/privy.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +17 -0
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +12 -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 +14 -0
- package/src/core/adapters/privy.test.ts +150 -8
- package/src/core/adapters/privy.ts +13 -5
- package/src/core/zod/request.test.ts +26 -0
- package/src/core/zod/rpc.ts +13 -3
- package/src/react/Remote.test.ts +153 -0
- package/src/react/Remote.ts +7 -1
- package/src/trusted-hosts.json +1 -0
package/src/core/Client.test.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { Secp256k1 } from 'ox'
|
|
2
|
+
import { TxEnvelopeTempo } from 'ox/tempo'
|
|
1
3
|
import { custom, http } from 'viem'
|
|
4
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
5
|
+
import { Transaction } from 'viem/tempo'
|
|
2
6
|
import { tempo, tempoModerato } from 'viem/tempo/chains'
|
|
3
|
-
import { describe, expect, test } from 'vp/test'
|
|
7
|
+
import { afterEach, describe, expect, test, vi } from 'vp/test'
|
|
4
8
|
|
|
5
9
|
import { secp256k1 } from '../../test/adapters.js'
|
|
6
10
|
import * as Client from './Client.js'
|
|
@@ -252,6 +256,37 @@ describe('providerTransport', () => {
|
|
|
252
256
|
})
|
|
253
257
|
|
|
254
258
|
describe('feePayerTransport', () => {
|
|
259
|
+
afterEach(() => vi.unstubAllGlobals())
|
|
260
|
+
|
|
261
|
+
const sender = privateKeyToAccount(
|
|
262
|
+
'0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
263
|
+
)
|
|
264
|
+
const feePayerPrivateKey = '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
|
|
265
|
+
|
|
266
|
+
async function sponsoredTransaction(data: `0x${string}`) {
|
|
267
|
+
const requested = await sender.signTransaction(
|
|
268
|
+
{
|
|
269
|
+
calls: [{ data, to: '0x20c0000000000000000000000000000000000000' }],
|
|
270
|
+
chainId: tempo.id,
|
|
271
|
+
feePayer: true,
|
|
272
|
+
gas: 100_000n,
|
|
273
|
+
maxFeePerGas: 2n,
|
|
274
|
+
maxPriorityFeePerGas: 1n,
|
|
275
|
+
nonce: 1,
|
|
276
|
+
} as never,
|
|
277
|
+
{ serializer: Transaction.serialize },
|
|
278
|
+
)
|
|
279
|
+
const envelope = TxEnvelopeTempo.deserialize(requested as TxEnvelopeTempo.Serialized)
|
|
280
|
+
const feePayerSignature = Secp256k1.sign({
|
|
281
|
+
payload: TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender: envelope.from! }),
|
|
282
|
+
privateKey: feePayerPrivateKey,
|
|
283
|
+
})
|
|
284
|
+
return {
|
|
285
|
+
requested,
|
|
286
|
+
signed: TxEnvelopeTempo.serialize(envelope, { feePayerSignature }),
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
255
290
|
test('behavior: passes through unrelated methods to the base transport', async () => {
|
|
256
291
|
const store = setup()
|
|
257
292
|
const baseRequests: { method: string; params?: unknown }[] = []
|
|
@@ -337,4 +372,121 @@ describe('feePayerTransport', () => {
|
|
|
337
372
|
]
|
|
338
373
|
`)
|
|
339
374
|
})
|
|
375
|
+
|
|
376
|
+
test('behavior: accepts a fee payer signature for the requested transaction', async () => {
|
|
377
|
+
const store = setup()
|
|
378
|
+
const { requested, signed } = await sponsoredTransaction('0x1234')
|
|
379
|
+
vi.stubGlobal('fetch', async (_input: string | URL, init?: RequestInit) => {
|
|
380
|
+
const body = JSON.parse(String(init?.body)) as { id: number }
|
|
381
|
+
return Response.json({ id: body.id, jsonrpc: '2.0', result: signed })
|
|
382
|
+
})
|
|
383
|
+
const baseRequests: { method: string; params?: unknown }[] = []
|
|
384
|
+
const hash = `0x${'11'.repeat(32)}` as `0x${string}`
|
|
385
|
+
const client = Client.fromChainId(tempo.id, {
|
|
386
|
+
chains: [tempo],
|
|
387
|
+
feePayer: 'https://relay.example.com',
|
|
388
|
+
store,
|
|
389
|
+
transports: {
|
|
390
|
+
[tempo.id]: custom({
|
|
391
|
+
async request({ method, params }) {
|
|
392
|
+
baseRequests.push({ method, params })
|
|
393
|
+
return hash
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
const result = await client.request({
|
|
400
|
+
method: 'eth_sendRawTransaction',
|
|
401
|
+
params: [requested],
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
expect(result).toMatchInlineSnapshot(
|
|
405
|
+
`"0x1111111111111111111111111111111111111111111111111111111111111111"`,
|
|
406
|
+
)
|
|
407
|
+
expect(baseRequests.map((request) => request.method)).toMatchInlineSnapshot(`
|
|
408
|
+
[
|
|
409
|
+
"eth_sendRawTransaction",
|
|
410
|
+
]
|
|
411
|
+
`)
|
|
412
|
+
const sent = baseRequests[0]?.params as readonly unknown[] | undefined
|
|
413
|
+
expect(sent?.[0]).toBe(signed)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
test('error: rejects a fee payer signature for a different transaction', async () => {
|
|
417
|
+
const store = setup()
|
|
418
|
+
const { requested } = await sponsoredTransaction('0x1234')
|
|
419
|
+
const { signed } = await sponsoredTransaction('0x5678')
|
|
420
|
+
vi.stubGlobal('fetch', async (_input: string | URL, init?: RequestInit) => {
|
|
421
|
+
const body = JSON.parse(String(init?.body)) as { id: number }
|
|
422
|
+
return Response.json({ id: body.id, jsonrpc: '2.0', result: signed })
|
|
423
|
+
})
|
|
424
|
+
const baseRequests: unknown[] = []
|
|
425
|
+
const client = Client.fromChainId(tempo.id, {
|
|
426
|
+
chains: [tempo],
|
|
427
|
+
feePayer: 'https://relay.example.com',
|
|
428
|
+
store,
|
|
429
|
+
transports: {
|
|
430
|
+
[tempo.id]: custom({
|
|
431
|
+
async request(request) {
|
|
432
|
+
baseRequests.push(request)
|
|
433
|
+
return null
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
await expect(
|
|
440
|
+
client.request({ method: 'eth_sendRawTransaction', params: [requested] }),
|
|
441
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
442
|
+
`[Error: Fee payer changed the requested transaction intent.]`,
|
|
443
|
+
)
|
|
444
|
+
expect(baseRequests).toMatchInlineSnapshot(`[]`)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
test('error: rejects a filled transaction that changes the requested call', async () => {
|
|
448
|
+
const store = setup()
|
|
449
|
+
const request = {
|
|
450
|
+
calls: [{ data: '0x1234', to: '0x20c0000000000000000000000000000000000000', value: '0x' }],
|
|
451
|
+
chainId: '0x1079',
|
|
452
|
+
feePayer: true,
|
|
453
|
+
from: sender.address,
|
|
454
|
+
type: '0x76',
|
|
455
|
+
} as const
|
|
456
|
+
vi.stubGlobal('fetch', async (_input: string | URL, init?: RequestInit) => {
|
|
457
|
+
const body = JSON.parse(String(init?.body)) as { id: number }
|
|
458
|
+
return Response.json({
|
|
459
|
+
id: body.id,
|
|
460
|
+
jsonrpc: '2.0',
|
|
461
|
+
result: {
|
|
462
|
+
raw: '0x',
|
|
463
|
+
tx: {
|
|
464
|
+
...request,
|
|
465
|
+
calls: [{ ...request.calls[0], data: '0x5678' }],
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
})
|
|
469
|
+
})
|
|
470
|
+
const baseRequests: unknown[] = []
|
|
471
|
+
const client = Client.fromChainId(tempo.id, {
|
|
472
|
+
chains: [tempo],
|
|
473
|
+
feePayer: 'https://relay.example.com',
|
|
474
|
+
store,
|
|
475
|
+
transports: {
|
|
476
|
+
[tempo.id]: custom({
|
|
477
|
+
async request(request) {
|
|
478
|
+
baseRequests.push(request)
|
|
479
|
+
return null
|
|
480
|
+
},
|
|
481
|
+
}),
|
|
482
|
+
},
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
await expect(
|
|
486
|
+
client.request({ method: 'eth_fillTransaction' as never, params: [request] as never }),
|
|
487
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
488
|
+
`[Error: Fee payer changed the requested transaction intent.]`,
|
|
489
|
+
)
|
|
490
|
+
expect(baseRequests).toMatchInlineSnapshot(`[]`)
|
|
491
|
+
})
|
|
340
492
|
})
|
package/src/core/Client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Provider as ox_Provider } from 'ox'
|
|
2
|
+
import { TxEnvelopeTempo } from 'ox/tempo'
|
|
2
3
|
import {
|
|
3
4
|
type Chain,
|
|
4
5
|
createClient,
|
|
@@ -163,11 +164,14 @@ function feePayerTransport(
|
|
|
163
164
|
typeof request === 'object' &&
|
|
164
165
|
'feePayer' in request &&
|
|
165
166
|
(request.feePayer === true || typeof request.feePayer === 'string')
|
|
166
|
-
)
|
|
167
|
-
|
|
167
|
+
) {
|
|
168
|
+
const response = await sponsor.request({
|
|
168
169
|
method,
|
|
169
170
|
params: [{ ...request, feePayer: true }],
|
|
170
171
|
})
|
|
172
|
+
assertFilledTransactionIntent(request, response)
|
|
173
|
+
return response
|
|
174
|
+
}
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
if (method === 'eth_sendRawTransaction' || method === 'eth_sendRawTransactionSync') {
|
|
@@ -182,6 +186,7 @@ function feePayerTransport(
|
|
|
182
186
|
method: 'eth_signRawTransaction',
|
|
183
187
|
params: [serialized],
|
|
184
188
|
})
|
|
189
|
+
assertSignedTransactionIntent(serialized, signed)
|
|
185
190
|
return await baseTransport.request({ method, params: [signed] })
|
|
186
191
|
}
|
|
187
192
|
}
|
|
@@ -192,3 +197,69 @@ function feePayerTransport(
|
|
|
192
197
|
} as ReturnType<Transport>
|
|
193
198
|
}
|
|
194
199
|
}
|
|
200
|
+
|
|
201
|
+
function assertFilledTransactionIntent(request: Record<string, unknown>, response: unknown) {
|
|
202
|
+
if (!isObject(response) || !isObject(response.tx))
|
|
203
|
+
throw new Error('Fee payer returned an invalid filled transaction.')
|
|
204
|
+
|
|
205
|
+
const transaction = response.tx
|
|
206
|
+
if (
|
|
207
|
+
(request.from !== undefined && !sameAddress(request.from, transaction.from)) ||
|
|
208
|
+
(request.chainId !== undefined && !sameQuantity(request.chainId, transaction.chainId)) ||
|
|
209
|
+
(request.calls !== undefined && !sameCalls(request.calls, transaction.calls))
|
|
210
|
+
)
|
|
211
|
+
throw new Error('Fee payer changed the requested transaction intent.')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function assertSignedTransactionIntent(serialized: string, signed: unknown) {
|
|
215
|
+
if (typeof signed !== 'string' || (!signed.startsWith('0x76') && !signed.startsWith('0x78')))
|
|
216
|
+
throw new Error('Fee payer returned an invalid signed transaction.')
|
|
217
|
+
|
|
218
|
+
const requested = TxEnvelopeTempo.deserialize(serialized as TxEnvelopeTempo.Serialized)
|
|
219
|
+
const transaction = TxEnvelopeTempo.deserialize(signed as TxEnvelopeTempo.Serialized)
|
|
220
|
+
if (
|
|
221
|
+
!transaction.feePayerSignature ||
|
|
222
|
+
!sameAddress(requested.from, transaction.from) ||
|
|
223
|
+
TxEnvelopeTempo.getSignPayload(requested) !== TxEnvelopeTempo.getSignPayload(transaction)
|
|
224
|
+
)
|
|
225
|
+
throw new Error('Fee payer changed the requested transaction intent.')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function sameCalls(a: unknown, b: unknown) {
|
|
229
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false
|
|
230
|
+
return a.every((call, index) => {
|
|
231
|
+
const other = b[index]
|
|
232
|
+
return (
|
|
233
|
+
isObject(call) &&
|
|
234
|
+
isObject(other) &&
|
|
235
|
+
sameAddress(call.to, other.to) &&
|
|
236
|
+
sameQuantity(call.value, other.value) &&
|
|
237
|
+
sameHex(call.data, other.data)
|
|
238
|
+
)
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function sameAddress(a: unknown, b: unknown) {
|
|
243
|
+
if (a === undefined && b === undefined) return true
|
|
244
|
+
return typeof a === 'string' && typeof b === 'string' && a.toLowerCase() === b.toLowerCase()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function sameHex(a: unknown, b: unknown) {
|
|
248
|
+
const normalize = (value: unknown) =>
|
|
249
|
+
typeof value === 'string' ? value.toLowerCase() : value === undefined ? '0x' : undefined
|
|
250
|
+
return normalize(a) === normalize(b)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function sameQuantity(a: unknown, b: unknown) {
|
|
254
|
+
const normalize = (value: unknown) => {
|
|
255
|
+
if (value === undefined || value === null || value === '0x') return 0n
|
|
256
|
+
if (typeof value === 'bigint') return value
|
|
257
|
+
if (typeof value === 'number' || typeof value === 'string') return BigInt(value)
|
|
258
|
+
return undefined
|
|
259
|
+
}
|
|
260
|
+
return normalize(a) === normalize(b)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
264
|
+
return typeof value === 'object' && value !== null
|
|
265
|
+
}
|
|
@@ -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
|
|
|
@@ -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')
|