accounts 0.16.2 → 0.17.1
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 +39 -0
- package/README.md +3 -4
- package/dist/cli/Provider.d.ts +1 -1
- package/dist/cli/Provider.d.ts.map +1 -1
- package/dist/cli/Provider.js +3 -3
- package/dist/cli/Provider.js.map +1 -1
- package/dist/cli/adapter.d.ts +13 -8
- package/dist/cli/adapter.d.ts.map +1 -1
- package/dist/cli/adapter.js +92 -303
- package/dist/cli/adapter.js.map +1 -1
- package/dist/core/Schema.d.ts +8 -0
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/adapters/deviceCode/deviceCode.d.ts +59 -0
- package/dist/core/adapters/deviceCode/deviceCode.d.ts.map +1 -0
- package/dist/core/adapters/deviceCode/deviceCode.js +93 -0
- package/dist/core/adapters/deviceCode/deviceCode.js.map +1 -0
- package/dist/core/adapters/deviceCode/index.d.ts +3 -0
- package/dist/core/adapters/deviceCode/index.d.ts.map +1 -0
- package/dist/core/adapters/deviceCode/index.js +3 -0
- package/dist/core/adapters/deviceCode/index.js.map +1 -0
- package/dist/core/adapters/deviceCode/tempoWallet.d.ts +9 -0
- package/dist/core/adapters/deviceCode/tempoWallet.d.ts.map +1 -0
- package/dist/core/adapters/deviceCode/tempoWallet.js +11 -0
- package/dist/core/adapters/deviceCode/tempoWallet.js.map +1 -0
- package/dist/core/adapters/internal/fromRequest.d.ts +10 -0
- package/dist/core/adapters/internal/fromRequest.d.ts.map +1 -1
- package/dist/core/adapters/internal/fromRequest.js +4 -2
- package/dist/core/adapters/internal/fromRequest.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +5 -0
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +1 -0
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/server/CliAuth.d.ts +69 -1
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +6 -1
- package/dist/server/CliAuth.js.map +1 -1
- package/dist/server/Handler.d.ts +2 -1
- package/dist/server/Handler.d.ts.map +1 -1
- package/dist/server/Handler.js +2 -1
- package/dist/server/Handler.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal/handlers/codeAuth.d.ts +1 -0
- package/dist/server/internal/handlers/codeAuth.d.ts.map +1 -1
- package/dist/server/internal/handlers/codeAuth.js +1 -0
- package/dist/server/internal/handlers/codeAuth.js.map +1 -1
- package/dist/server/internal/handlers/deviceCode.d.ts +60 -0
- package/dist/server/internal/handlers/deviceCode.d.ts.map +1 -0
- package/dist/server/internal/handlers/deviceCode.js +218 -0
- package/dist/server/internal/handlers/deviceCode.js.map +1 -0
- package/package.json +7 -1
- package/src/cli/Provider.localnet.test.ts +414 -470
- package/src/cli/Provider.test-d.ts +4 -3
- package/src/cli/Provider.ts +6 -6
- package/src/cli/adapter.ts +111 -380
- package/src/core/Provider.test.ts +48 -0
- package/src/core/adapters/deviceCode/deviceCode.localnet.test.ts +226 -0
- package/src/core/adapters/deviceCode/deviceCode.test-d.ts +19 -0
- package/src/core/adapters/deviceCode/deviceCode.ts +157 -0
- package/src/core/adapters/deviceCode/index.ts +2 -0
- package/src/core/adapters/deviceCode/tempoWallet.ts +17 -0
- package/src/core/adapters/internal/fromRequest.ts +115 -88
- package/src/core/zod/rpc.ts +1 -0
- package/src/server/CliAuth.test.ts +29 -18
- package/src/server/CliAuth.ts +10 -2
- package/src/server/Handler.test-d.ts +19 -33
- package/src/server/Handler.ts +2 -1
- package/src/server/index.ts +1 -0
- package/src/server/internal/handlers/codeAuth.ts +1 -0
- package/src/server/internal/handlers/deviceCode.test.ts +140 -0
- package/src/server/internal/handlers/deviceCode.ts +323 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { mkdtemp } from 'node:fs/promises'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
-
import { Address, Hex
|
|
4
|
+
import type { Address, Hex } from 'ox'
|
|
5
|
+
import { Hex as ox_Hex } from 'ox'
|
|
5
6
|
import { KeyAuthorization } from 'ox/tempo'
|
|
6
|
-
import { type Address as ViemAddress, parseUnits } from 'viem'
|
|
7
|
+
import { type Address as ViemAddress, hashMessage, parseUnits } from 'viem'
|
|
7
8
|
import { Actions, Addresses } from 'viem/tempo'
|
|
8
|
-
import { describe, expect, test } from 'vp/test'
|
|
9
|
-
import * as z from 'zod/mini'
|
|
9
|
+
import { describe, expect, test, vi } from 'vp/test'
|
|
10
|
+
import type * as z from 'zod/mini'
|
|
10
11
|
|
|
11
12
|
import { accounts, chain, getClient } from '../../test/config.js'
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
13
|
+
import { createDeviceCodeHost, submitVerify } from '../../test/deviceCode.js'
|
|
14
|
+
import { createJsonStorage, createServer } from '../../test/utils.js'
|
|
15
|
+
import type * as Rpc from '../core/zod/rpc.js'
|
|
14
16
|
import * as Handler from '../server/Handler.js'
|
|
15
17
|
import * as Provider from './Provider.js'
|
|
16
18
|
import * as Storage from './storage.js'
|
|
@@ -21,43 +23,12 @@ const accessKey_2 = accounts[2]!
|
|
|
21
23
|
const expiry = Math.floor(Date.now() / 1000) + 3_600
|
|
22
24
|
const expiry_2 = expiry + 60
|
|
23
25
|
|
|
24
|
-
async function authorize(
|
|
25
|
-
code: string,
|
|
26
|
-
options: {
|
|
27
|
-
accessKey?: typeof accessKey | undefined
|
|
28
|
-
expiry?: number | undefined
|
|
29
|
-
} = {},
|
|
30
|
-
) {
|
|
31
|
-
const { accessKey: key = accessKey, expiry: expiry_ = expiry } = options
|
|
32
|
-
|
|
33
|
-
const signed = await root.signKeyAuthorization(
|
|
34
|
-
{
|
|
35
|
-
accessKeyAddress: key.address,
|
|
36
|
-
keyType: key.keyType,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
chainId: BigInt(chain.id),
|
|
40
|
-
expiry: expiry_,
|
|
41
|
-
},
|
|
42
|
-
)
|
|
43
|
-
const keyAuthorization = KeyAuthorization.toRpc(signed)
|
|
44
|
-
|
|
45
|
-
return z.encode(CliAuth.authorizeRequest, {
|
|
46
|
-
accountAddress: root.address,
|
|
47
|
-
code,
|
|
48
|
-
keyAuthorization: z.decode(CliAuth.keyAuthorization, {
|
|
49
|
-
...keyAuthorization,
|
|
50
|
-
address: keyAuthorization.keyId,
|
|
51
|
-
}),
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
26
|
function connectRequest(
|
|
56
27
|
options: {
|
|
57
28
|
accessKey?: typeof accessKey | undefined
|
|
58
29
|
expiry?: number | undefined
|
|
59
30
|
method?: 'login' | 'register' | undefined
|
|
60
|
-
showDeposit?: z.output<typeof
|
|
31
|
+
showDeposit?: z.output<typeof Rpc.wallet_connect.showDeposit> | undefined
|
|
61
32
|
} = {},
|
|
62
33
|
) {
|
|
63
34
|
const { accessKey: key = accessKey, expiry: expiry_ = expiry, method, showDeposit } = options
|
|
@@ -80,104 +51,6 @@ function connectRequest(
|
|
|
80
51
|
} as const
|
|
81
52
|
}
|
|
82
53
|
|
|
83
|
-
function createHandler() {
|
|
84
|
-
let random = 0
|
|
85
|
-
|
|
86
|
-
return Handler.codeAuth({
|
|
87
|
-
path: '/cli-auth',
|
|
88
|
-
chains: [chain],
|
|
89
|
-
policy: {
|
|
90
|
-
update({ limits }) {
|
|
91
|
-
return { limits }
|
|
92
|
-
},
|
|
93
|
-
validate({ expiry: requestedExpiry, limits }) {
|
|
94
|
-
return {
|
|
95
|
-
expiry: requestedExpiry ?? expiry,
|
|
96
|
-
...(limits ? { limits } : {}),
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
random: () => {
|
|
101
|
-
const out = new Uint8Array(Array.from({ length: 8 }, (_, i) => random + i))
|
|
102
|
-
random += 8
|
|
103
|
-
return out
|
|
104
|
-
},
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function authorizePending(serverUrl: string, code: string) {
|
|
109
|
-
const response = await fetch(`${serverUrl}/cli-auth/pending/${code}`)
|
|
110
|
-
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
111
|
-
if (pending.action === 'updateAccessKey') throw new Error('Expected authorization request.')
|
|
112
|
-
const signed = await root.signKeyAuthorization(
|
|
113
|
-
{
|
|
114
|
-
accessKeyAddress: Address.fromPublicKey(PublicKey.from(pending.pubKey)),
|
|
115
|
-
keyType: pending.keyType,
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
chainId: pending.chainId,
|
|
119
|
-
expiry: pending.expiry,
|
|
120
|
-
...(pending.limits ? { limits: pending.limits } : {}),
|
|
121
|
-
},
|
|
122
|
-
)
|
|
123
|
-
const keyAuthorization = KeyAuthorization.toRpc(signed)
|
|
124
|
-
|
|
125
|
-
return z.encode(CliAuth.authorizeRequest, {
|
|
126
|
-
accountAddress: root.address,
|
|
127
|
-
code,
|
|
128
|
-
keyAuthorization: z.decode(CliAuth.keyAuthorization, {
|
|
129
|
-
...keyAuthorization,
|
|
130
|
-
address: keyAuthorization.keyId,
|
|
131
|
-
}),
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function completePending(serverUrl: string, code: string) {
|
|
136
|
-
const response = await fetch(`${serverUrl}/cli-auth/pending/${code}`)
|
|
137
|
-
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
138
|
-
if (pending.action !== 'updateAccessKey') return authorizePending(serverUrl, code)
|
|
139
|
-
|
|
140
|
-
if (pending.keyAuthorization) {
|
|
141
|
-
const signed = await root.signKeyAuthorization(
|
|
142
|
-
{
|
|
143
|
-
accessKeyAddress: pending.accessKeyAddress,
|
|
144
|
-
keyType: pending.keyAuthorization.keyType,
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
chainId: pending.chainId,
|
|
148
|
-
expiry: pending.keyAuthorization.expiry,
|
|
149
|
-
limits: pending.limits,
|
|
150
|
-
},
|
|
151
|
-
)
|
|
152
|
-
const replacement = KeyAuthorization.toRpc(signed)
|
|
153
|
-
return z.encode(CliAuth.authorizeRequest, {
|
|
154
|
-
action: 'updateAccessKey',
|
|
155
|
-
accountAddress: root.address,
|
|
156
|
-
chainId: pending.chainId,
|
|
157
|
-
code,
|
|
158
|
-
keyAuthorization: z.decode(CliAuth.keyAuthorization, {
|
|
159
|
-
...replacement,
|
|
160
|
-
address: replacement.keyId,
|
|
161
|
-
}),
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const client = getClient({ account: root })
|
|
166
|
-
for (const item of pending.limits)
|
|
167
|
-
await Actions.accessKey.updateLimitSync(client, {
|
|
168
|
-
accessKey: pending.accessKeyAddress,
|
|
169
|
-
limit: item.limit,
|
|
170
|
-
token: item.token,
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
return z.encode(CliAuth.authorizeRequest, {
|
|
174
|
-
action: 'updateAccessKey',
|
|
175
|
-
accountAddress: root.address,
|
|
176
|
-
chainId: pending.chainId,
|
|
177
|
-
code,
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
|
|
181
54
|
async function createStoragePath() {
|
|
182
55
|
return join(await mkdtemp(join(tmpdir(), 'accounts-cli-')), 'store.json')
|
|
183
56
|
}
|
|
@@ -214,25 +87,30 @@ const transferCall = Actions.token.transfer.call({
|
|
|
214
87
|
amount: parseUnits('1', 6),
|
|
215
88
|
})
|
|
216
89
|
|
|
90
|
+
function connectCapabilities(host: ReturnType<typeof createDeviceCodeHost>, index: number) {
|
|
91
|
+
const params = host.requests()[index]!.params as readonly {
|
|
92
|
+
capabilities?:
|
|
93
|
+
| NonNullable<Rpc.wallet_connect.Decoded['params']>[number]['capabilities']
|
|
94
|
+
| undefined
|
|
95
|
+
}[]
|
|
96
|
+
return params[0]?.capabilities
|
|
97
|
+
}
|
|
98
|
+
|
|
217
99
|
describe('Provider.create', () => {
|
|
218
|
-
test('default: bootstraps wallet_connect through the device
|
|
219
|
-
const
|
|
220
|
-
const server = await createServer(
|
|
100
|
+
test('default: bootstraps wallet_connect through the device-code flow', async () => {
|
|
101
|
+
const host = createDeviceCodeHost()
|
|
102
|
+
const server = await createServer(host.listener)
|
|
221
103
|
const opened: string[] = []
|
|
222
104
|
|
|
223
105
|
try {
|
|
224
106
|
const provider = Provider.create({
|
|
225
107
|
chains: [chain],
|
|
226
|
-
open: async (url) => {
|
|
108
|
+
open: async (url, prompt) => {
|
|
227
109
|
opened.push(url)
|
|
228
|
-
|
|
229
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
230
|
-
body: JSON.stringify(await authorize(code)),
|
|
231
|
-
headers: { 'content-type': 'application/json' },
|
|
232
|
-
method: 'POST',
|
|
233
|
-
})
|
|
110
|
+
await submitVerify(prompt)
|
|
234
111
|
},
|
|
235
|
-
host: `${server.url}/
|
|
112
|
+
host: `${server.url}/auth/device`,
|
|
113
|
+
storage: createJsonStorage(),
|
|
236
114
|
})
|
|
237
115
|
|
|
238
116
|
const result = await provider.request(connectRequest())
|
|
@@ -246,22 +124,25 @@ describe('Provider.create', () => {
|
|
|
246
124
|
}
|
|
247
125
|
: undefined
|
|
248
126
|
|
|
127
|
+
expect(opened).toHaveLength(1)
|
|
128
|
+
expect(opened[0]).toMatch(
|
|
129
|
+
new RegExp(`^${server.url}/auth/device/verify\\?user_code=[A-Z]{4}-[A-Z]{4}$`),
|
|
130
|
+
)
|
|
249
131
|
expect({
|
|
250
132
|
account: {
|
|
251
133
|
address: account.address,
|
|
252
134
|
capabilities: keyAuthorization ? { keyAuthorization } : {},
|
|
253
135
|
},
|
|
254
|
-
opened: opened.map((url) => url.replace(server.url, 'http://service')),
|
|
255
136
|
}).toMatchInlineSnapshot(`
|
|
256
137
|
{
|
|
257
138
|
"account": {
|
|
258
139
|
"address": "${root.address}",
|
|
259
140
|
"capabilities": {
|
|
260
141
|
"keyAuthorization": {
|
|
261
|
-
"address": "${accessKey.address}",
|
|
262
|
-
"chainId": "${
|
|
263
|
-
"expiry": "${
|
|
264
|
-
"keyId": "${accessKey.address}",
|
|
142
|
+
"address": "${accessKey.address.toLowerCase()}",
|
|
143
|
+
"chainId": "${ox_Hex.fromNumber(chain.id)}",
|
|
144
|
+
"expiry": "${ox_Hex.fromNumber(expiry)}",
|
|
145
|
+
"keyId": "${accessKey.address.toLowerCase()}",
|
|
265
146
|
"keyType": "secp256k1",
|
|
266
147
|
"signature": {
|
|
267
148
|
"type": "secp256k1",
|
|
@@ -269,36 +150,132 @@ describe('Provider.create', () => {
|
|
|
269
150
|
},
|
|
270
151
|
},
|
|
271
152
|
},
|
|
272
|
-
|
|
273
|
-
|
|
153
|
+
}
|
|
154
|
+
`)
|
|
155
|
+
} finally {
|
|
156
|
+
await server.closeAsync()
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('behavior: preserves wallet_connect authorization capabilities', async () => {
|
|
161
|
+
const host = createDeviceCodeHost()
|
|
162
|
+
const server = await createServer(host.listener)
|
|
163
|
+
let listener: Parameters<typeof createServer>[0] | undefined
|
|
164
|
+
const authServer = await createServer((request, response) => {
|
|
165
|
+
if (!listener) {
|
|
166
|
+
const auth = Handler.auth({ origin: authServer.url })
|
|
167
|
+
listener = Handler.compose([auth], { path: '/auth' }).listener
|
|
168
|
+
}
|
|
169
|
+
return listener(request, response)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const provider = Provider.create({
|
|
174
|
+
chains: [chain],
|
|
175
|
+
open: async (_url, prompt) => {
|
|
176
|
+
await submitVerify(prompt)
|
|
177
|
+
},
|
|
178
|
+
host: `${server.url}/auth/device`,
|
|
179
|
+
storage: createJsonStorage(),
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const result = await provider.request({
|
|
183
|
+
method: 'wallet_connect',
|
|
184
|
+
params: [
|
|
185
|
+
{
|
|
186
|
+
capabilities: {
|
|
187
|
+
auth: {
|
|
188
|
+
resources: [
|
|
189
|
+
'urn:nanocodex:memory:read',
|
|
190
|
+
'urn:nanocodex:connector:github:repo:read',
|
|
191
|
+
],
|
|
192
|
+
returnToken: true,
|
|
193
|
+
url: `${authServer.url}/auth`,
|
|
194
|
+
},
|
|
195
|
+
authorizeAccessKey: {
|
|
196
|
+
expiry,
|
|
197
|
+
keyType: accessKey.keyType,
|
|
198
|
+
publicKey: accessKey.publicKey,
|
|
199
|
+
scopes: [
|
|
200
|
+
{
|
|
201
|
+
address: Addresses.pathUsd,
|
|
202
|
+
selector: 'transfer(address,uint256)',
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
identity: { email: true },
|
|
207
|
+
method: 'login',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const capabilities = connectCapabilities(host, 0)
|
|
214
|
+
const connected = result.accounts[0]!.capabilities
|
|
215
|
+
const message = connected.personalSign?.message
|
|
216
|
+
expect({
|
|
217
|
+
auth: capabilities?.auth,
|
|
218
|
+
authToken: connected.auth?.token ? 'present' : 'missing',
|
|
219
|
+
identity: capabilities?.identity,
|
|
220
|
+
personalSign: {
|
|
221
|
+
keyAuthorization: connected.personalSign?.keyAuthorization ? 'present' : 'missing',
|
|
222
|
+
message: message ? 'present' : 'missing',
|
|
223
|
+
signature: connected.signature ? 'present' : 'missing',
|
|
224
|
+
witness:
|
|
225
|
+
message && connected.personalSign?.keyAuthorization
|
|
226
|
+
? KeyAuthorization.deserialize(connected.personalSign.keyAuthorization).witness ===
|
|
227
|
+
hashMessage(message)
|
|
228
|
+
: false,
|
|
229
|
+
},
|
|
230
|
+
scopes: capabilities?.authorizeAccessKey?.scopes,
|
|
231
|
+
}).toMatchInlineSnapshot(`
|
|
232
|
+
{
|
|
233
|
+
"auth": {
|
|
234
|
+
"challenge": "${authServer.url}/auth/challenge",
|
|
235
|
+
"logout": "${authServer.url}/auth/logout",
|
|
236
|
+
"resources": [
|
|
237
|
+
"urn:nanocodex:memory:read",
|
|
238
|
+
"urn:nanocodex:connector:github:repo:read",
|
|
239
|
+
],
|
|
240
|
+
"returnToken": true,
|
|
241
|
+
"verify": "${authServer.url}/auth",
|
|
242
|
+
},
|
|
243
|
+
"authToken": "present",
|
|
244
|
+
"identity": {
|
|
245
|
+
"email": true,
|
|
246
|
+
},
|
|
247
|
+
"personalSign": {
|
|
248
|
+
"keyAuthorization": "present",
|
|
249
|
+
"message": "present",
|
|
250
|
+
"signature": "present",
|
|
251
|
+
"witness": true,
|
|
252
|
+
},
|
|
253
|
+
"scopes": [
|
|
254
|
+
{
|
|
255
|
+
"address": "${Addresses.pathUsd}",
|
|
256
|
+
"selector": "transfer(address,uint256)",
|
|
257
|
+
},
|
|
274
258
|
],
|
|
275
259
|
}
|
|
276
260
|
`)
|
|
277
261
|
} finally {
|
|
262
|
+
await authServer.closeAsync()
|
|
278
263
|
await server.closeAsync()
|
|
279
264
|
}
|
|
280
265
|
})
|
|
281
266
|
|
|
282
|
-
test('behavior: forwards showDeposit through registration device
|
|
283
|
-
const
|
|
284
|
-
const server = await createServer(
|
|
285
|
-
const pendingShowDeposit: z.output<typeof CliAuth.pendingResponse>['showDeposit'][] = []
|
|
267
|
+
test('behavior: forwards showDeposit through registration device-code requests', async () => {
|
|
268
|
+
const host = createDeviceCodeHost()
|
|
269
|
+
const server = await createServer(host.listener)
|
|
286
270
|
|
|
287
271
|
try {
|
|
288
272
|
const provider = Provider.create({
|
|
289
273
|
chains: [chain],
|
|
290
|
-
open: async (
|
|
291
|
-
|
|
292
|
-
const response = await fetch(`${server.url}/cli-auth/pending/${code}`)
|
|
293
|
-
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
294
|
-
pendingShowDeposit.push(pending.showDeposit)
|
|
295
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
296
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
297
|
-
headers: { 'content-type': 'application/json' },
|
|
298
|
-
method: 'POST',
|
|
299
|
-
})
|
|
274
|
+
open: async (_url, prompt) => {
|
|
275
|
+
await submitVerify(prompt)
|
|
300
276
|
},
|
|
301
|
-
host: `${server.url}/
|
|
277
|
+
host: `${server.url}/auth/device`,
|
|
278
|
+
storage: createJsonStorage(),
|
|
302
279
|
})
|
|
303
280
|
|
|
304
281
|
await provider.request(
|
|
@@ -313,82 +290,56 @@ describe('Provider.create', () => {
|
|
|
313
290
|
}),
|
|
314
291
|
)
|
|
315
292
|
|
|
316
|
-
expect(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
]
|
|
293
|
+
expect(connectCapabilities(host, 0)?.showDeposit).toMatchInlineSnapshot(`
|
|
294
|
+
{
|
|
295
|
+
"amount": "50",
|
|
296
|
+
"displayName": "DoorDash",
|
|
297
|
+
"on": "register",
|
|
298
|
+
"token": "USDC",
|
|
299
|
+
}
|
|
325
300
|
`)
|
|
326
301
|
} finally {
|
|
327
302
|
await server.closeAsync()
|
|
328
303
|
}
|
|
329
304
|
})
|
|
330
305
|
|
|
331
|
-
test('behavior: forwards showDeposit through login device
|
|
332
|
-
const
|
|
333
|
-
const server = await createServer(
|
|
334
|
-
const pendingShowDeposit: z.output<typeof CliAuth.pendingResponse>['showDeposit'][] = []
|
|
306
|
+
test('behavior: forwards showDeposit through login device-code requests', async () => {
|
|
307
|
+
const host = createDeviceCodeHost()
|
|
308
|
+
const server = await createServer(host.listener)
|
|
335
309
|
|
|
336
310
|
try {
|
|
337
311
|
const provider = Provider.create({
|
|
338
312
|
chains: [chain],
|
|
339
|
-
open: async (
|
|
340
|
-
|
|
341
|
-
const response = await fetch(`${server.url}/cli-auth/pending/${code}`)
|
|
342
|
-
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
343
|
-
pendingShowDeposit.push(pending.showDeposit)
|
|
344
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
345
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
346
|
-
headers: { 'content-type': 'application/json' },
|
|
347
|
-
method: 'POST',
|
|
348
|
-
})
|
|
313
|
+
open: async (_url, prompt) => {
|
|
314
|
+
await submitVerify(prompt)
|
|
349
315
|
},
|
|
350
|
-
host: `${server.url}/
|
|
316
|
+
host: `${server.url}/auth/device`,
|
|
317
|
+
storage: createJsonStorage(),
|
|
351
318
|
})
|
|
352
319
|
|
|
353
|
-
await provider.request(
|
|
354
|
-
connectRequest({
|
|
355
|
-
method: 'login',
|
|
356
|
-
showDeposit: true,
|
|
357
|
-
}),
|
|
358
|
-
)
|
|
320
|
+
await provider.request(connectRequest({ method: 'login', showDeposit: true }))
|
|
359
321
|
|
|
360
|
-
expect(
|
|
361
|
-
[
|
|
362
|
-
true,
|
|
363
|
-
]
|
|
364
|
-
`)
|
|
322
|
+
expect(connectCapabilities(host, 0)?.showDeposit).toBe(true)
|
|
365
323
|
} finally {
|
|
366
324
|
await server.closeAsync()
|
|
367
325
|
}
|
|
368
326
|
})
|
|
369
327
|
|
|
370
|
-
test('behavior: forwards showDeposit through wallet_authorizeAccessKey device
|
|
371
|
-
const
|
|
372
|
-
const server = await createServer(
|
|
373
|
-
const pendingShowDeposit: z.output<typeof CliAuth.pendingResponse>['showDeposit'][] = []
|
|
328
|
+
test('behavior: forwards showDeposit through wallet_authorizeAccessKey device-code requests', async () => {
|
|
329
|
+
const host = createDeviceCodeHost()
|
|
330
|
+
const server = await createServer(host.listener)
|
|
374
331
|
|
|
375
332
|
try {
|
|
376
333
|
const provider = Provider.create({
|
|
377
334
|
chains: [chain],
|
|
378
|
-
open: async (
|
|
379
|
-
|
|
380
|
-
const response = await fetch(`${server.url}/cli-auth/pending/${code}`)
|
|
381
|
-
const pending = z.decode(CliAuth.pendingResponse, (await response.json()) as never)
|
|
382
|
-
pendingShowDeposit.push(pending.showDeposit)
|
|
383
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
384
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
385
|
-
headers: { 'content-type': 'application/json' },
|
|
386
|
-
method: 'POST',
|
|
387
|
-
})
|
|
335
|
+
open: async (_url, prompt) => {
|
|
336
|
+
await submitVerify(prompt)
|
|
388
337
|
},
|
|
389
|
-
host: `${server.url}/
|
|
338
|
+
host: `${server.url}/auth/device`,
|
|
339
|
+
storage: createJsonStorage(),
|
|
390
340
|
})
|
|
391
341
|
|
|
342
|
+
await provider.request(connectRequest())
|
|
392
343
|
await provider.request({
|
|
393
344
|
method: 'wallet_authorizeAccessKey',
|
|
394
345
|
params: [
|
|
@@ -401,19 +352,17 @@ describe('Provider.create', () => {
|
|
|
401
352
|
],
|
|
402
353
|
})
|
|
403
354
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
]
|
|
408
|
-
`)
|
|
355
|
+
const params = host.requests()[1]!.params as readonly { showDeposit?: unknown }[]
|
|
356
|
+
expect(host.requests()[1]!.method).toBe('wallet_authorizeAccessKey')
|
|
357
|
+
expect(params[0]?.showDeposit).toBe(true)
|
|
409
358
|
} finally {
|
|
410
359
|
await server.closeAsync()
|
|
411
360
|
}
|
|
412
361
|
})
|
|
413
362
|
|
|
414
363
|
test('behavior: browser-open failures surface the URL and code', async () => {
|
|
415
|
-
const
|
|
416
|
-
const server = await createServer(
|
|
364
|
+
const host = createDeviceCodeHost()
|
|
365
|
+
const server = await createServer(host.listener)
|
|
417
366
|
|
|
418
367
|
try {
|
|
419
368
|
const provider = Provider.create({
|
|
@@ -421,154 +370,117 @@ describe('Provider.create', () => {
|
|
|
421
370
|
open() {
|
|
422
371
|
throw new Error('browser unavailable')
|
|
423
372
|
},
|
|
424
|
-
host: `${server.url}/
|
|
373
|
+
host: `${server.url}/auth/device`,
|
|
374
|
+
storage: createJsonStorage(),
|
|
425
375
|
})
|
|
426
376
|
|
|
427
|
-
await
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
name: error.name,
|
|
435
|
-
}
|
|
436
|
-
}),
|
|
437
|
-
).resolves.toMatchInlineSnapshot(`
|
|
438
|
-
{
|
|
439
|
-
"code": -32603,
|
|
440
|
-
"message": "Failed to open browser for device code ABCD-EFGH. Open http://service/cli-auth?code=ABCDEFGH manually.",
|
|
441
|
-
"name": "RpcResponse.InternalError",
|
|
442
|
-
}
|
|
443
|
-
`)
|
|
377
|
+
const error = await provider.request(connectRequest()).then(
|
|
378
|
+
() => undefined,
|
|
379
|
+
(error: Error) => error,
|
|
380
|
+
)
|
|
381
|
+
expect(error?.message).toMatch(
|
|
382
|
+
/^Failed to surface device code [A-Z]{4}-[A-Z]{4}\. Open .*\/auth\/device\/verify\?user_code=[A-Z]{4}-[A-Z]{4} manually\.$/,
|
|
383
|
+
)
|
|
444
384
|
} finally {
|
|
445
385
|
await server.closeAsync()
|
|
446
386
|
}
|
|
447
387
|
})
|
|
448
388
|
|
|
389
|
+
test('behavior: prints the user code when the complete verification URI is absent', async () => {
|
|
390
|
+
const host = createDeviceCodeHost({ omitVerificationUriFull: true })
|
|
391
|
+
const server = await createServer(host.listener)
|
|
392
|
+
const write = vi.spyOn(process.stdout, 'write').mockImplementation(() => true)
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const provider = Provider.create({
|
|
396
|
+
chains: [chain],
|
|
397
|
+
open: async (url, prompt) => {
|
|
398
|
+
expect(url).toBe(prompt.verificationUri)
|
|
399
|
+
await submitVerify(prompt)
|
|
400
|
+
},
|
|
401
|
+
host: `${server.url}/auth/device`,
|
|
402
|
+
storage: createJsonStorage(),
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
await provider.request(connectRequest())
|
|
406
|
+
|
|
407
|
+
expect(write).toHaveBeenCalledWith(
|
|
408
|
+
expect.stringMatching(/^Enter code [A-Z]{4}-[A-Z]{4} at http/),
|
|
409
|
+
)
|
|
410
|
+
} finally {
|
|
411
|
+
write.mockRestore()
|
|
412
|
+
await server.closeAsync()
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
449
416
|
test('behavior: times out while waiting for authorization', async () => {
|
|
450
|
-
const
|
|
451
|
-
const server = await createServer(
|
|
417
|
+
const host = createDeviceCodeHost({ pollingInterval: 10 })
|
|
418
|
+
const server = await createServer(host.listener)
|
|
452
419
|
|
|
453
420
|
try {
|
|
454
421
|
const provider = Provider.create({
|
|
455
422
|
chains: [chain],
|
|
456
423
|
open() {},
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
424
|
+
host: `${server.url}/auth/device`,
|
|
425
|
+
storage: createJsonStorage(),
|
|
426
|
+
timeout: 100,
|
|
460
427
|
})
|
|
461
428
|
|
|
462
|
-
await
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
name: error.name,
|
|
470
|
-
}
|
|
471
|
-
}),
|
|
472
|
-
).resolves.toMatchInlineSnapshot(`
|
|
473
|
-
{
|
|
474
|
-
"code": -32603,
|
|
475
|
-
"message": "Timed out waiting for device code ABCD-EFGH. Continue at http://service/cli-auth?code=ABCDEFGH.",
|
|
476
|
-
"name": "RpcResponse.InternalError",
|
|
477
|
-
}
|
|
478
|
-
`)
|
|
429
|
+
const error = await provider.request(connectRequest()).then(
|
|
430
|
+
() => undefined,
|
|
431
|
+
(error: Error) => error,
|
|
432
|
+
)
|
|
433
|
+
expect(error?.message).toMatch(
|
|
434
|
+
/^Timed out waiting for device code [A-Z]{4}-[A-Z]{4}\. Continue at /,
|
|
435
|
+
)
|
|
479
436
|
} finally {
|
|
480
437
|
await server.closeAsync()
|
|
481
438
|
}
|
|
482
439
|
})
|
|
483
440
|
|
|
484
|
-
test('behavior:
|
|
485
|
-
const
|
|
486
|
-
const server = await createServer(
|
|
487
|
-
const opened: string[] = []
|
|
441
|
+
test('behavior: wallet_authorizeAccessKey requires a connected account', async () => {
|
|
442
|
+
const host = createDeviceCodeHost()
|
|
443
|
+
const server = await createServer(host.listener)
|
|
488
444
|
|
|
489
445
|
try {
|
|
490
446
|
const provider = Provider.create({
|
|
491
447
|
chains: [chain],
|
|
492
|
-
open
|
|
493
|
-
|
|
494
|
-
const code = new URL(url).searchParams.get('code')!
|
|
495
|
-
try {
|
|
496
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
497
|
-
body: JSON.stringify(
|
|
498
|
-
await authorize(code, { accessKey: accessKey_2, expiry: expiry_2 }),
|
|
499
|
-
),
|
|
500
|
-
headers: { 'content-type': 'application/json' },
|
|
501
|
-
method: 'POST',
|
|
502
|
-
})
|
|
503
|
-
} catch {}
|
|
448
|
+
open() {
|
|
449
|
+
throw new Error('Unexpected browser open.')
|
|
504
450
|
},
|
|
505
|
-
host: `${server.url}/
|
|
451
|
+
host: `${server.url}/auth/device`,
|
|
452
|
+
storage: createJsonStorage(),
|
|
506
453
|
})
|
|
507
454
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
})
|
|
514
|
-
const keyAuthorization = {
|
|
515
|
-
...result.keyAuthorization,
|
|
516
|
-
signature: {
|
|
517
|
-
type: result.keyAuthorization.signature.type,
|
|
518
|
-
},
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
expect({
|
|
522
|
-
keyAuthorization,
|
|
523
|
-
rootAddress: result.rootAddress,
|
|
524
|
-
opened: opened.map((url) => url.replace(server.url, 'http://service')),
|
|
525
|
-
}).toMatchInlineSnapshot(`
|
|
526
|
-
{
|
|
527
|
-
"keyAuthorization": {
|
|
528
|
-
"address": "${accessKey_2.address}",
|
|
529
|
-
"chainId": "${Hex.fromNumber(chain.id)}",
|
|
530
|
-
"expiry": "${Hex.fromNumber(expiry_2)}",
|
|
531
|
-
"keyId": "${accessKey_2.address}",
|
|
532
|
-
"keyType": "secp256k1",
|
|
533
|
-
"signature": {
|
|
534
|
-
"type": "secp256k1",
|
|
535
|
-
},
|
|
536
|
-
},
|
|
537
|
-
"opened": [
|
|
538
|
-
"http://service/cli-auth?code=ABCDEFGH",
|
|
455
|
+
await expect(
|
|
456
|
+
provider.request({
|
|
457
|
+
method: 'wallet_authorizeAccessKey',
|
|
458
|
+
params: [
|
|
459
|
+
{ expiry: expiry_2, keyType: accessKey_2.keyType, publicKey: accessKey_2.publicKey },
|
|
539
460
|
],
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
461
|
+
}),
|
|
462
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
463
|
+
`[Provider.DisconnectedError: No active account.]`,
|
|
464
|
+
)
|
|
543
465
|
} finally {
|
|
544
466
|
await server.closeAsync()
|
|
545
467
|
}
|
|
546
468
|
})
|
|
547
469
|
|
|
548
470
|
test('behavior: authorizes an access key for the active account', async () => {
|
|
549
|
-
const
|
|
550
|
-
const server = await createServer(
|
|
471
|
+
const host = createDeviceCodeHost()
|
|
472
|
+
const server = await createServer(host.listener)
|
|
551
473
|
const opened: string[] = []
|
|
552
474
|
|
|
553
475
|
try {
|
|
554
|
-
const approvals = [
|
|
555
|
-
(code: string) => authorize(code),
|
|
556
|
-
(code: string) => authorize(code, { accessKey: accessKey_2, expiry: expiry_2 }),
|
|
557
|
-
]
|
|
558
476
|
const provider = Provider.create({
|
|
559
477
|
chains: [chain],
|
|
560
|
-
open: async (url) => {
|
|
478
|
+
open: async (url, prompt) => {
|
|
561
479
|
opened.push(url)
|
|
562
|
-
|
|
563
|
-
if (!approve) throw new Error('Unexpected device code approval request.')
|
|
564
|
-
const code = new URL(url).searchParams.get('code')!
|
|
565
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
566
|
-
body: JSON.stringify(await approve(code)),
|
|
567
|
-
headers: { 'content-type': 'application/json' },
|
|
568
|
-
method: 'POST',
|
|
569
|
-
})
|
|
480
|
+
await submitVerify(prompt)
|
|
570
481
|
},
|
|
571
|
-
host: `${server.url}/
|
|
482
|
+
host: `${server.url}/auth/device`,
|
|
483
|
+
storage: createJsonStorage(),
|
|
572
484
|
})
|
|
573
485
|
|
|
574
486
|
await provider.request(connectRequest())
|
|
@@ -586,26 +498,22 @@ describe('Provider.create', () => {
|
|
|
586
498
|
},
|
|
587
499
|
}
|
|
588
500
|
|
|
501
|
+
expect(opened).toHaveLength(2)
|
|
589
502
|
expect({
|
|
590
503
|
keyAuthorization,
|
|
591
504
|
rootAddress: result.rootAddress,
|
|
592
|
-
opened: opened.map((url) => url.replace(server.url, 'http://service')),
|
|
593
505
|
}).toMatchInlineSnapshot(`
|
|
594
506
|
{
|
|
595
507
|
"keyAuthorization": {
|
|
596
|
-
"address": "${accessKey_2.address}",
|
|
597
|
-
"chainId": "${
|
|
598
|
-
"expiry": "${
|
|
599
|
-
"keyId": "${accessKey_2.address}",
|
|
508
|
+
"address": "${accessKey_2.address.toLowerCase()}",
|
|
509
|
+
"chainId": "${ox_Hex.fromNumber(chain.id)}",
|
|
510
|
+
"expiry": "${ox_Hex.fromNumber(expiry_2)}",
|
|
511
|
+
"keyId": "${accessKey_2.address.toLowerCase()}",
|
|
600
512
|
"keyType": "secp256k1",
|
|
601
513
|
"signature": {
|
|
602
514
|
"type": "secp256k1",
|
|
603
515
|
},
|
|
604
516
|
},
|
|
605
|
-
"opened": [
|
|
606
|
-
"http://service/cli-auth?code=ABCDEFGH",
|
|
607
|
-
"http://service/cli-auth?code=JKLMNPQR",
|
|
608
|
-
],
|
|
609
517
|
"rootAddress": "${root.address}",
|
|
610
518
|
}
|
|
611
519
|
`)
|
|
@@ -615,21 +523,17 @@ describe('Provider.create', () => {
|
|
|
615
523
|
})
|
|
616
524
|
|
|
617
525
|
test('behavior: rejects unsupported revokeAccessKey after bootstrap', async () => {
|
|
618
|
-
const
|
|
619
|
-
const server = await createServer(
|
|
526
|
+
const host = createDeviceCodeHost()
|
|
527
|
+
const server = await createServer(host.listener)
|
|
620
528
|
|
|
621
529
|
try {
|
|
622
530
|
const provider = Provider.create({
|
|
623
531
|
chains: [chain],
|
|
624
|
-
open: async (
|
|
625
|
-
|
|
626
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
627
|
-
body: JSON.stringify(await authorize(code)),
|
|
628
|
-
headers: { 'content-type': 'application/json' },
|
|
629
|
-
method: 'POST',
|
|
630
|
-
})
|
|
532
|
+
open: async (_url, prompt) => {
|
|
533
|
+
await submitVerify(prompt)
|
|
631
534
|
},
|
|
632
|
-
host: `${server.url}/
|
|
535
|
+
host: `${server.url}/auth/device`,
|
|
536
|
+
storage: createJsonStorage(),
|
|
633
537
|
})
|
|
634
538
|
|
|
635
539
|
await provider.request(connectRequest())
|
|
@@ -639,32 +543,25 @@ describe('Provider.create', () => {
|
|
|
639
543
|
method: 'wallet_revokeAccessKey',
|
|
640
544
|
params: [{ accessKeyAddress: accessKey.address, address: root.address }],
|
|
641
545
|
}),
|
|
642
|
-
).rejects.
|
|
643
|
-
`[Provider.UnsupportedMethodError: \`wallet_revokeAccessKey\` not supported by CLI adapter.]`,
|
|
644
|
-
)
|
|
546
|
+
).rejects.toThrow('`wallet_revokeAccessKey` not supported by device-code adapter.')
|
|
645
547
|
} finally {
|
|
646
548
|
await server.closeAsync()
|
|
647
549
|
}
|
|
648
550
|
})
|
|
649
551
|
|
|
650
552
|
test('behavior: generates, persists, and reuses a managed key during wallet_connect', async () => {
|
|
651
|
-
const
|
|
652
|
-
const server = await createServer(
|
|
553
|
+
const host = createDeviceCodeHost()
|
|
554
|
+
const server = await createServer(host.listener)
|
|
653
555
|
const storagePath = await createStoragePath()
|
|
654
556
|
const storage = Storage.filesystem({ path: storagePath })
|
|
655
557
|
|
|
656
558
|
try {
|
|
657
559
|
const provider = Provider.create({
|
|
658
560
|
chains: [chain],
|
|
659
|
-
open: async (
|
|
660
|
-
|
|
661
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
662
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
663
|
-
headers: { 'content-type': 'application/json' },
|
|
664
|
-
method: 'POST',
|
|
665
|
-
})
|
|
561
|
+
open: async (_url, prompt) => {
|
|
562
|
+
await submitVerify(prompt)
|
|
666
563
|
},
|
|
667
|
-
host: `${server.url}/
|
|
564
|
+
host: `${server.url}/auth/device`,
|
|
668
565
|
storage,
|
|
669
566
|
})
|
|
670
567
|
|
|
@@ -685,7 +582,7 @@ describe('Provider.create', () => {
|
|
|
685
582
|
open() {
|
|
686
583
|
throw new Error('Unexpected browser open.')
|
|
687
584
|
},
|
|
688
|
-
host: `${server.url}/
|
|
585
|
+
host: `${server.url}/auth/device`,
|
|
689
586
|
storage: storage_2,
|
|
690
587
|
})
|
|
691
588
|
const receipt_2 = await provider_2.request({
|
|
@@ -716,27 +613,73 @@ describe('Provider.create', () => {
|
|
|
716
613
|
}
|
|
717
614
|
})
|
|
718
615
|
|
|
616
|
+
test('behavior: signs messages locally with the managed access key', async () => {
|
|
617
|
+
const host = createDeviceCodeHost()
|
|
618
|
+
const server = await createServer(host.listener)
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const provider = Provider.create({
|
|
622
|
+
chains: [chain],
|
|
623
|
+
open: async (_url, prompt) => {
|
|
624
|
+
await submitVerify(prompt)
|
|
625
|
+
},
|
|
626
|
+
host: `${server.url}/auth/device`,
|
|
627
|
+
storage: createJsonStorage(),
|
|
628
|
+
})
|
|
629
|
+
const connected = await provider.request({
|
|
630
|
+
method: 'wallet_connect',
|
|
631
|
+
params: [{ capabilities: { authorizeAccessKey: { expiry: expiry_2 } } }],
|
|
632
|
+
})
|
|
633
|
+
const address = connected.accounts[0]!.address
|
|
634
|
+
|
|
635
|
+
const personal = await provider.request({
|
|
636
|
+
method: 'personal_sign',
|
|
637
|
+
params: ['0x68656c6c6f', address],
|
|
638
|
+
})
|
|
639
|
+
const typedData = await provider.request({
|
|
640
|
+
method: 'eth_signTypedData_v4',
|
|
641
|
+
params: [
|
|
642
|
+
address,
|
|
643
|
+
JSON.stringify({
|
|
644
|
+
domain: {},
|
|
645
|
+
message: { contents: 'hello' },
|
|
646
|
+
primaryType: 'Message',
|
|
647
|
+
types: {
|
|
648
|
+
EIP712Domain: [],
|
|
649
|
+
Message: [{ name: 'contents', type: 'string' }],
|
|
650
|
+
},
|
|
651
|
+
}),
|
|
652
|
+
],
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
expect(personal).toMatch(/^0x/)
|
|
656
|
+
expect(typedData).toMatch(/^0x/)
|
|
657
|
+
expect(host.requests()).toHaveLength(1)
|
|
658
|
+
} finally {
|
|
659
|
+
await server.closeAsync()
|
|
660
|
+
}
|
|
661
|
+
})
|
|
662
|
+
|
|
719
663
|
test('behavior: generates a managed key for wallet_authorizeAccessKey without publicKey', async () => {
|
|
720
|
-
const
|
|
721
|
-
const server = await createServer(
|
|
664
|
+
const host = createDeviceCodeHost()
|
|
665
|
+
const server = await createServer(host.listener)
|
|
722
666
|
const storagePath = await createStoragePath()
|
|
723
667
|
const storage = Storage.filesystem({ path: storagePath })
|
|
724
668
|
|
|
725
669
|
try {
|
|
726
670
|
const provider = Provider.create({
|
|
727
671
|
chains: [chain],
|
|
728
|
-
open: async (
|
|
729
|
-
|
|
730
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
731
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
732
|
-
headers: { 'content-type': 'application/json' },
|
|
733
|
-
method: 'POST',
|
|
734
|
-
})
|
|
672
|
+
open: async (_url, prompt) => {
|
|
673
|
+
await submitVerify(prompt)
|
|
735
674
|
},
|
|
736
|
-
host: `${server.url}/
|
|
675
|
+
host: `${server.url}/auth/device`,
|
|
737
676
|
storage,
|
|
738
677
|
})
|
|
739
678
|
|
|
679
|
+
const connected = await provider.request({
|
|
680
|
+
method: 'wallet_connect',
|
|
681
|
+
params: [{ capabilities: {} }],
|
|
682
|
+
})
|
|
740
683
|
const result = await provider.request({
|
|
741
684
|
method: 'wallet_authorizeAccessKey',
|
|
742
685
|
params: [{ expiry: expiry_2 }],
|
|
@@ -749,6 +692,7 @@ describe('Provider.create', () => {
|
|
|
749
692
|
})
|
|
750
693
|
const [entry] = await readAccessKeys(storage)
|
|
751
694
|
|
|
695
|
+
expect(connected.accounts[0]!.address).toBe(root.address)
|
|
752
696
|
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
753
697
|
expect({
|
|
754
698
|
access: entry!.access,
|
|
@@ -764,97 +708,53 @@ describe('Provider.create', () => {
|
|
|
764
708
|
}
|
|
765
709
|
})
|
|
766
710
|
|
|
767
|
-
test('behavior:
|
|
768
|
-
const
|
|
769
|
-
const server = await createServer(
|
|
711
|
+
test('behavior: wallet_updateAccessKey replaces an unpublished key authorization', async () => {
|
|
712
|
+
const host = createDeviceCodeHost()
|
|
713
|
+
const server = await createServer(host.listener)
|
|
770
714
|
const storagePath = await createStoragePath()
|
|
771
715
|
const storage = Storage.filesystem({ path: storagePath })
|
|
772
716
|
|
|
773
717
|
try {
|
|
774
718
|
const provider = Provider.create({
|
|
775
719
|
chains: [chain],
|
|
776
|
-
open: async (
|
|
777
|
-
|
|
778
|
-
await fetch(`${server.url}/cli-auth`, {
|
|
779
|
-
body: JSON.stringify(await authorizePending(server.url, code)),
|
|
780
|
-
headers: { 'content-type': 'application/json' },
|
|
781
|
-
method: 'POST',
|
|
782
|
-
})
|
|
720
|
+
open: async (_url, prompt) => {
|
|
721
|
+
await submitVerify(prompt)
|
|
783
722
|
},
|
|
784
|
-
host: `${server.url}/
|
|
723
|
+
host: `${server.url}/auth/device`,
|
|
785
724
|
storage,
|
|
786
725
|
})
|
|
787
726
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const receipt = await provider.request({
|
|
801
|
-
method: 'eth_sendTransactionSync',
|
|
802
|
-
params: [{ calls: [transferCall] }],
|
|
727
|
+
const result = await provider.request({
|
|
728
|
+
method: 'wallet_connect',
|
|
729
|
+
params: [
|
|
730
|
+
{
|
|
731
|
+
capabilities: {
|
|
732
|
+
authorizeAccessKey: {
|
|
733
|
+
expiry: expiry_2,
|
|
734
|
+
limits: [{ limit: ox_Hex.fromNumber(1n), token: Addresses.pathUsd }],
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
],
|
|
803
739
|
})
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
expect(first.keyAuthorization.keyType).toBe('secp256k1')
|
|
807
|
-
expect(second.keyAuthorization.keyType).toBe('p256')
|
|
808
|
-
expect(first.keyAuthorization.keyId).not.toBe(second.keyAuthorization.keyId)
|
|
809
|
-
expect(new Set(keys.map((key) => key.keyType))).toEqual(new Set(['secp256k1', 'p256']))
|
|
810
|
-
expect(receipt.status).toBe('0x1')
|
|
811
|
-
} finally {
|
|
812
|
-
await server.closeAsync()
|
|
813
|
-
}
|
|
814
|
-
})
|
|
815
|
-
|
|
816
|
-
test('wallet_updateAccessKey: replaces an unpublished key authorization', async () => {
|
|
817
|
-
const handler = createHandler()
|
|
818
|
-
const server = await createServer(handler.listener)
|
|
819
|
-
const storagePath = await createStoragePath()
|
|
820
|
-
const storage = Storage.filesystem({ path: storagePath })
|
|
740
|
+
const account = result.accounts[0]!
|
|
741
|
+
const accessKeyAddress = account.capabilities.keyAuthorization!.keyId
|
|
821
742
|
|
|
822
|
-
try {
|
|
823
|
-
const provider = Provider.create({
|
|
824
|
-
chains: [chain],
|
|
825
|
-
open: async (url) => {
|
|
826
|
-
const code = new URL(url).searchParams.get('code')!
|
|
827
|
-
const body = await completePending(server.url, code)
|
|
828
|
-
const response = await fetch(`${server.url}/cli-auth`, {
|
|
829
|
-
body: JSON.stringify(body),
|
|
830
|
-
headers: { 'content-type': 'application/json' },
|
|
831
|
-
method: 'POST',
|
|
832
|
-
})
|
|
833
|
-
if (!response.ok) throw new Error(await response.text())
|
|
834
|
-
},
|
|
835
|
-
host: `${server.url}/cli-auth`,
|
|
836
|
-
storage,
|
|
837
|
-
})
|
|
838
743
|
const updated = parseUnits('9', 6)
|
|
839
|
-
const { keyAuthorization, rootAddress } = await provider.request({
|
|
840
|
-
method: 'wallet_authorizeAccessKey',
|
|
841
|
-
params: [{ expiry, limits: [{ limit: Hex.fromNumber(1), token: Addresses.pathUsd }] }],
|
|
842
|
-
})
|
|
843
|
-
|
|
844
744
|
await provider.request({
|
|
845
745
|
method: 'wallet_updateAccessKey',
|
|
846
746
|
params: [
|
|
847
747
|
{
|
|
848
|
-
address:
|
|
849
|
-
accessKeyAddress
|
|
850
|
-
limits: [{ limit:
|
|
748
|
+
address: account.address,
|
|
749
|
+
accessKeyAddress,
|
|
750
|
+
limits: [{ limit: ox_Hex.fromNumber(updated), token: Addresses.pathUsd }],
|
|
851
751
|
},
|
|
852
752
|
],
|
|
853
753
|
})
|
|
854
754
|
|
|
855
755
|
const [stored] = provider.store.accessKeys.list({
|
|
856
|
-
|
|
857
|
-
|
|
756
|
+
accessKey: accessKeyAddress,
|
|
757
|
+
account: account.address,
|
|
858
758
|
chainId: chain.id,
|
|
859
759
|
})
|
|
860
760
|
expect(stored?.keyAuthorization?.limits).toEqual([
|
|
@@ -862,8 +762,8 @@ describe('Provider.create', () => {
|
|
|
862
762
|
])
|
|
863
763
|
expect(
|
|
864
764
|
await provider.store.accessKeys.getStatus({
|
|
865
|
-
|
|
866
|
-
|
|
765
|
+
accessKey: accessKeyAddress,
|
|
766
|
+
account: account.address,
|
|
867
767
|
chainId: chain.id,
|
|
868
768
|
client: getClient(),
|
|
869
769
|
}),
|
|
@@ -873,53 +773,49 @@ describe('Provider.create', () => {
|
|
|
873
773
|
}
|
|
874
774
|
})
|
|
875
775
|
|
|
876
|
-
test('
|
|
877
|
-
const
|
|
878
|
-
const server = await createServer(
|
|
776
|
+
test('behavior: wallet_updateAccessKey updates published spending limits through approval', async () => {
|
|
777
|
+
const host = createDeviceCodeHost()
|
|
778
|
+
const server = await createServer(host.listener)
|
|
879
779
|
const storagePath = await createStoragePath()
|
|
880
780
|
const storage = Storage.filesystem({ path: storagePath })
|
|
881
781
|
|
|
882
782
|
try {
|
|
883
783
|
const provider = Provider.create({
|
|
884
784
|
chains: [chain],
|
|
885
|
-
open: async (
|
|
886
|
-
|
|
887
|
-
const body = await completePending(server.url, code)
|
|
888
|
-
const response = await fetch(`${server.url}/cli-auth`, {
|
|
889
|
-
body: JSON.stringify(body),
|
|
890
|
-
headers: { 'content-type': 'application/json' },
|
|
891
|
-
method: 'POST',
|
|
892
|
-
})
|
|
893
|
-
if (!response.ok) throw new Error(await response.text())
|
|
785
|
+
open: async (_url, prompt) => {
|
|
786
|
+
await submitVerify(prompt)
|
|
894
787
|
},
|
|
895
|
-
host: `${server.url}/
|
|
788
|
+
host: `${server.url}/auth/device`,
|
|
896
789
|
storage,
|
|
897
790
|
})
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
method: '
|
|
901
|
-
params: [{ expiry }],
|
|
791
|
+
|
|
792
|
+
const result = await provider.request({
|
|
793
|
+
method: 'wallet_connect',
|
|
794
|
+
params: [{ capabilities: { authorizeAccessKey: { expiry: expiry_2 } } }],
|
|
902
795
|
})
|
|
903
|
-
|
|
796
|
+
const account = result.accounts[0]!
|
|
797
|
+
const accessKeyAddress = account.capabilities.keyAuthorization!.keyId
|
|
798
|
+
await fund(account.address)
|
|
904
799
|
await provider.request({
|
|
905
800
|
method: 'eth_sendTransactionSync',
|
|
906
801
|
params: [{ calls: [transferCall] }],
|
|
907
802
|
})
|
|
908
803
|
|
|
804
|
+
const updated = parseUnits('9', 6)
|
|
909
805
|
await provider.request({
|
|
910
806
|
method: 'wallet_updateAccessKey',
|
|
911
807
|
params: [
|
|
912
808
|
{
|
|
913
|
-
address:
|
|
914
|
-
accessKeyAddress
|
|
915
|
-
limits: [{ limit:
|
|
809
|
+
address: account.address,
|
|
810
|
+
accessKeyAddress,
|
|
811
|
+
limits: [{ limit: ox_Hex.fromNumber(updated), token: Addresses.pathUsd }],
|
|
916
812
|
},
|
|
917
813
|
],
|
|
918
814
|
})
|
|
919
815
|
|
|
920
816
|
const { remaining } = await Actions.accessKey.getRemainingLimit(getClient(), {
|
|
921
|
-
account:
|
|
922
|
-
accessKey:
|
|
817
|
+
account: account.address,
|
|
818
|
+
accessKey: accessKeyAddress,
|
|
923
819
|
token: Addresses.pathUsd,
|
|
924
820
|
})
|
|
925
821
|
expect(remaining).toBe(updated)
|
|
@@ -927,4 +823,52 @@ describe('Provider.create', () => {
|
|
|
927
823
|
await server.closeAsync()
|
|
928
824
|
}
|
|
929
825
|
})
|
|
826
|
+
|
|
827
|
+
test('behavior: regenerates a managed key when the requested key type changes', async () => {
|
|
828
|
+
const host = createDeviceCodeHost()
|
|
829
|
+
const server = await createServer(host.listener)
|
|
830
|
+
const storagePath = await createStoragePath()
|
|
831
|
+
const storage = Storage.filesystem({ path: storagePath })
|
|
832
|
+
|
|
833
|
+
try {
|
|
834
|
+
const provider = Provider.create({
|
|
835
|
+
chains: [chain],
|
|
836
|
+
open: async (_url, prompt) => {
|
|
837
|
+
await submitVerify(prompt)
|
|
838
|
+
},
|
|
839
|
+
host: `${server.url}/auth/device`,
|
|
840
|
+
storage,
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
await provider.request({
|
|
844
|
+
method: 'wallet_connect',
|
|
845
|
+
params: [{ capabilities: {} }],
|
|
846
|
+
})
|
|
847
|
+
// secp256k1 is requested explicitly; p256 is the default — a genuine
|
|
848
|
+
// type change that regenerates the managed key.
|
|
849
|
+
const first = await provider.request({
|
|
850
|
+
method: 'wallet_authorizeAccessKey',
|
|
851
|
+
params: [{ expiry, keyType: 'secp256k1' }],
|
|
852
|
+
})
|
|
853
|
+
const second = await provider.request({
|
|
854
|
+
method: 'wallet_authorizeAccessKey',
|
|
855
|
+
params: [{ expiry: expiry_2, keyType: 'p256' }],
|
|
856
|
+
})
|
|
857
|
+
await fund(second.rootAddress)
|
|
858
|
+
|
|
859
|
+
const receipt = await provider.request({
|
|
860
|
+
method: 'eth_sendTransactionSync',
|
|
861
|
+
params: [{ calls: [transferCall] }],
|
|
862
|
+
})
|
|
863
|
+
const keys = await readAccessKeys(storage)
|
|
864
|
+
|
|
865
|
+
expect(first.keyAuthorization.keyType).toBe('secp256k1')
|
|
866
|
+
expect(second.keyAuthorization.keyType).toBe('p256')
|
|
867
|
+
expect(first.keyAuthorization.keyId).not.toBe(second.keyAuthorization.keyId)
|
|
868
|
+
expect(new Set(keys.map((key) => key.keyType))).toEqual(new Set(['secp256k1', 'p256']))
|
|
869
|
+
expect(receipt.status).toBe('0x1')
|
|
870
|
+
} finally {
|
|
871
|
+
await server.closeAsync()
|
|
872
|
+
}
|
|
873
|
+
})
|
|
930
874
|
})
|