accounts 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -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/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/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/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/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,4 +1,5 @@
|
|
|
1
1
|
import { describe, expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type { DeviceCode } from 'wata'
|
|
2
3
|
|
|
3
4
|
import type * as CoreProvider from '../core/Provider.js'
|
|
4
5
|
import type { cli } from './adapter.js'
|
|
@@ -8,9 +9,9 @@ describe('create', () => {
|
|
|
8
9
|
test('accepts CLI bootstrap options', () => {
|
|
9
10
|
expectTypeOf<Parameters<typeof Provider.create>[0]>().toMatchTypeOf<{
|
|
10
11
|
host?: string | undefined
|
|
11
|
-
open?: ((url: string) => Promise<void> | void) | undefined
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
open?: ((url: string, prompt: DeviceCode.Prompt) => Promise<void> | void) | undefined
|
|
13
|
+
pollingInterval?: number | undefined
|
|
14
|
+
timeout?: number | undefined
|
|
14
15
|
}>()
|
|
15
16
|
})
|
|
16
17
|
|
package/src/cli/Provider.ts
CHANGED
|
@@ -8,11 +8,11 @@ import * as Storage from './storage.js'
|
|
|
8
8
|
*/
|
|
9
9
|
export function create(options: create.Options): create.ReturnType {
|
|
10
10
|
const {
|
|
11
|
-
host = 'https://wallet.tempo.xyz/
|
|
11
|
+
host = 'https://wallet.tempo.xyz/auth/device',
|
|
12
12
|
open,
|
|
13
|
-
|
|
13
|
+
pollingInterval,
|
|
14
14
|
storage = Storage.filesystem(),
|
|
15
|
-
|
|
15
|
+
timeout,
|
|
16
16
|
...rest
|
|
17
17
|
} = options
|
|
18
18
|
|
|
@@ -28,8 +28,8 @@ export function create(options: create.Options): create.ReturnType {
|
|
|
28
28
|
adapter: cli({
|
|
29
29
|
host,
|
|
30
30
|
...(open ? { open } : {}),
|
|
31
|
-
...(
|
|
32
|
-
...(
|
|
31
|
+
...(pollingInterval !== undefined ? { pollingInterval } : {}),
|
|
32
|
+
...(timeout !== undefined ? { timeout } : {}),
|
|
33
33
|
}),
|
|
34
34
|
...(mpp ? { mpp } : {}),
|
|
35
35
|
storage,
|
|
@@ -41,7 +41,7 @@ export declare namespace create {
|
|
|
41
41
|
CoreProvider.create.Options & cli.Options,
|
|
42
42
|
'adapter' | 'authorizeAccessKey' | 'host'
|
|
43
43
|
> & {
|
|
44
|
-
/**
|
|
44
|
+
/** Base URL of the wallet's device-code endpoints. @default "https://wallet.tempo.xyz/auth/device" */
|
|
45
45
|
host?: string | undefined
|
|
46
46
|
}
|
|
47
47
|
export type ReturnType = CoreProvider.create.ReturnType
|
package/src/cli/adapter.ts
CHANGED
|
@@ -1,377 +1,149 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import {
|
|
3
|
-
import { Base64, Hash, Hex, Provider as core_Provider, RpcResponse } from 'ox'
|
|
2
|
+
import { Provider as core_Provider } from 'ox'
|
|
4
3
|
import { KeyAuthorization } from 'ox/tempo'
|
|
4
|
+
import { hashMessage, hashTypedData } from 'viem'
|
|
5
|
+
import type { DeviceCode } from 'wata'
|
|
5
6
|
import * as z from 'zod/mini'
|
|
6
7
|
|
|
7
|
-
import * as Adapter from '../core/Adapter.js'
|
|
8
|
+
import type * as Adapter from '../core/Adapter.js'
|
|
9
|
+
import { deviceCode } from '../core/adapters/deviceCode/deviceCode.js'
|
|
8
10
|
import * as Keystore from '../core/Keystore.js'
|
|
9
|
-
import * as
|
|
11
|
+
import * as Rpc from '../core/zod/rpc.js'
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
* Creates a CLI bootstrap adapter backed by the device
|
|
14
|
+
* Creates a CLI bootstrap adapter backed by the Wata device-code transport
|
|
15
|
+
* (OAuth 2.0 Device Authorization Grant, RFC 8628 with PKCE).
|
|
16
|
+
*
|
|
17
|
+
* Authorization opens the wallet's verification page in a browser; the wallet
|
|
18
|
+
* responds with a signed key authorization once the user approves.
|
|
13
19
|
*/
|
|
14
20
|
export function cli(options: cli.Options): Adapter.Adapter {
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
accessKey: Awaited<ReturnType<Keystore.Keystore['createKey']>>,
|
|
31
|
-
keyAuthorization: z.output<typeof CliAuth.keyAuthorization>,
|
|
32
|
-
) {
|
|
33
|
-
const signed = KeyAuthorization.fromRpc(z.encode(CliAuth.keyAuthorization, keyAuthorization))
|
|
34
|
-
store.accessKeys.add({
|
|
35
|
-
account: address,
|
|
36
|
-
authorization: signed,
|
|
37
|
-
handle: accessKey.handle,
|
|
38
|
-
publicKey: accessKey.publicKey,
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function authorize(request: {
|
|
43
|
-
account?: Adapter.authorizeAccessKey.ReturnType['rootAddress'] | undefined
|
|
44
|
-
authorizeAccessKey: Adapter.authorizeAccessKey.Parameters | undefined
|
|
45
|
-
method: 'wallet_authorizeAccessKey' | 'wallet_connect'
|
|
46
|
-
showDeposit?: Adapter.createAccount.Parameters['showDeposit'] | undefined
|
|
47
|
-
}) {
|
|
48
|
-
const {
|
|
49
|
-
host,
|
|
50
|
-
open = defaultOpen,
|
|
51
|
-
pollIntervalMs = 2_000,
|
|
52
|
-
timeoutMs = 5 * 60 * 1_000,
|
|
53
|
-
} = options
|
|
54
|
-
const { account, authorizeAccessKey, method } = request
|
|
55
|
-
|
|
56
|
-
// p256 by default; secp256k1 only when explicitly requested.
|
|
57
|
-
const generatedKeyType = authorizeAccessKey?.keyType === 'secp256k1' ? 'secp256k1' : 'p256'
|
|
58
|
-
const generatedAccessKey =
|
|
59
|
-
authorizeAccessKey && !authorizeAccessKey.publicKey && !authorizeAccessKey.address
|
|
60
|
-
? await keystores[generatedKeyType].createKey()
|
|
61
|
-
: undefined
|
|
62
|
-
|
|
63
|
-
const publicKey = authorizeAccessKey?.publicKey ?? generatedAccessKey?.publicKey
|
|
64
|
-
const keyType =
|
|
65
|
-
authorizeAccessKey?.keyType ?? (generatedAccessKey ? generatedKeyType : undefined)
|
|
66
|
-
|
|
67
|
-
if (!publicKey)
|
|
68
|
-
throw new RpcResponse.InvalidParamsError({
|
|
69
|
-
message:
|
|
70
|
-
method === 'wallet_connect'
|
|
71
|
-
? '`wallet_connect` on the CLI adapter requires `capabilities.authorizeAccessKey`.'
|
|
72
|
-
: '`wallet_authorizeAccessKey` on the CLI adapter requires key parameters.',
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const codeVerifier = createCodeVerifier()
|
|
76
|
-
const codeChallenge = createCodeChallenge(codeVerifier)
|
|
77
|
-
const body: z.output<typeof CliAuth.createRequest> = {
|
|
78
|
-
...(account ? { account } : {}),
|
|
79
|
-
chainId: BigInt(store.getState().chainId),
|
|
80
|
-
codeChallenge,
|
|
81
|
-
...(typeof authorizeAccessKey?.expiry !== 'undefined'
|
|
82
|
-
? { expiry: authorizeAccessKey.expiry }
|
|
83
|
-
: {}),
|
|
84
|
-
...(keyType ? { keyType } : {}),
|
|
85
|
-
...(authorizeAccessKey?.limits ? { limits: authorizeAccessKey.limits } : {}),
|
|
86
|
-
pubKey: publicKey,
|
|
87
|
-
...(request.showDeposit !== undefined ? { showDeposit: request.showDeposit } : {}),
|
|
88
|
-
}
|
|
89
|
-
const created = await post({
|
|
90
|
-
body,
|
|
91
|
-
request: CliAuth.createRequest,
|
|
92
|
-
response: CliAuth.createResponse,
|
|
93
|
-
url: getApiUrl(host, 'code'),
|
|
94
|
-
})
|
|
95
|
-
const url = getBrowserUrl(host, created.code)
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await open(url)
|
|
99
|
-
} catch (error) {
|
|
100
|
-
throw new OpenError(url, created.code, error)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const startedAt = Date.now()
|
|
104
|
-
|
|
105
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
106
|
-
const result = await post({
|
|
107
|
-
body: {
|
|
108
|
-
codeVerifier,
|
|
109
|
-
} satisfies z.output<typeof CliAuth.pollRequest>,
|
|
110
|
-
request: CliAuth.pollRequest,
|
|
111
|
-
response: CliAuth.pollResponse,
|
|
112
|
-
url: getApiUrl(host, `poll/${created.code}`),
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
if (result.status === 'pending') {
|
|
116
|
-
await sleep(pollIntervalMs)
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
if (result.status === 'expired')
|
|
120
|
-
throw new Error('Device code expired before authorization completed.')
|
|
121
|
-
if (result.action === 'updateAccessKey')
|
|
122
|
-
throw new Error('Device code action does not match the authorization request.')
|
|
123
|
-
|
|
124
|
-
if (generatedAccessKey)
|
|
125
|
-
await saveGeneratedAccessKey(
|
|
126
|
-
result.accountAddress,
|
|
127
|
-
generatedAccessKey,
|
|
128
|
-
result.keyAuthorization,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
return result
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
throw new TimeoutError(url, created.code)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function update(parameters: Adapter.updateAccessKey.Parameters) {
|
|
138
|
-
const {
|
|
139
|
-
host,
|
|
140
|
-
open = defaultOpen,
|
|
141
|
-
pollIntervalMs = 2_000,
|
|
142
|
-
timeoutMs = 5 * 60 * 1_000,
|
|
143
|
-
} = options
|
|
144
|
-
const chainId = Number(parameters.chainId ?? store.getState().chainId)
|
|
145
|
-
const current = store.accessKeys.list({
|
|
146
|
-
accessKey: parameters.accessKeyAddress,
|
|
147
|
-
account: parameters.address,
|
|
148
|
-
chainId,
|
|
149
|
-
})[0]
|
|
150
|
-
const status = current?.keyAuthorization
|
|
151
|
-
? await store.accessKeys.getStatus({
|
|
152
|
-
accessKey: parameters.accessKeyAddress,
|
|
153
|
-
account: parameters.address,
|
|
154
|
-
chainId,
|
|
155
|
-
client: getClient({ chainId }),
|
|
156
|
-
})
|
|
157
|
-
: undefined
|
|
158
|
-
const pendingKeyAuthorization =
|
|
159
|
-
status === 'pending' && current?.keyAuthorization
|
|
160
|
-
? KeyAuthorization.toRpc(current.keyAuthorization)
|
|
161
|
-
: undefined
|
|
162
|
-
const codeVerifier = createCodeVerifier()
|
|
163
|
-
const body = {
|
|
164
|
-
action: 'updateAccessKey',
|
|
165
|
-
accessKeyAddress: parameters.accessKeyAddress,
|
|
166
|
-
account: parameters.address,
|
|
167
|
-
chainId: BigInt(chainId),
|
|
168
|
-
codeChallenge: createCodeChallenge(codeVerifier),
|
|
169
|
-
...(pendingKeyAuthorization
|
|
170
|
-
? {
|
|
171
|
-
keyAuthorization: z.decode(CliAuth.keyAuthorization, {
|
|
172
|
-
...pendingKeyAuthorization,
|
|
173
|
-
address: pendingKeyAuthorization.keyId,
|
|
174
|
-
}),
|
|
175
|
-
}
|
|
176
|
-
: {}),
|
|
177
|
-
limits: parameters.limits,
|
|
178
|
-
} satisfies z.output<typeof CliAuth.createRequest>
|
|
179
|
-
const created = await post({
|
|
180
|
-
body,
|
|
181
|
-
request: CliAuth.createRequest,
|
|
182
|
-
response: CliAuth.createResponse,
|
|
183
|
-
url: getApiUrl(host, 'code'),
|
|
184
|
-
})
|
|
185
|
-
const url = getBrowserUrl(host, created.code)
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
await open(url)
|
|
189
|
-
} catch (error) {
|
|
190
|
-
throw new OpenError(url, created.code, error)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const startedAt = Date.now()
|
|
194
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
195
|
-
const result = await post({
|
|
196
|
-
body: { codeVerifier },
|
|
197
|
-
request: CliAuth.pollRequest,
|
|
198
|
-
response: CliAuth.pollResponse,
|
|
199
|
-
url: getApiUrl(host, `poll/${created.code}`),
|
|
21
|
+
const {
|
|
22
|
+
host,
|
|
23
|
+
name = 'Tempo CLI',
|
|
24
|
+
open = defaultOpen,
|
|
25
|
+
pollingInterval,
|
|
26
|
+
rdns = 'xyz.tempo.cli',
|
|
27
|
+
timeout,
|
|
28
|
+
} = options
|
|
29
|
+
|
|
30
|
+
return deviceCode({
|
|
31
|
+
actions: ({ getClient, request, store }) => {
|
|
32
|
+
async function getAccessKey(address: `0x${string}`) {
|
|
33
|
+
const account = await store.accessKeys.select({
|
|
34
|
+
account: address,
|
|
35
|
+
chainId: store.getState().chainId,
|
|
200
36
|
})
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
if (result.status === 'expired')
|
|
206
|
-
throw new Error('Device code expired before access key update completed.')
|
|
207
|
-
if (result.action !== 'updateAccessKey')
|
|
208
|
-
throw new Error('Device code action does not match the access key update request.')
|
|
209
|
-
if (result.keyAuthorization) {
|
|
210
|
-
store.accessKeys.updateAuthorization({
|
|
211
|
-
accessKey: parameters.accessKeyAddress,
|
|
212
|
-
account: parameters.address,
|
|
213
|
-
authorization: KeyAuthorization.fromRpc(
|
|
214
|
-
z.encode(CliAuth.keyAuthorization, result.keyAuthorization),
|
|
215
|
-
),
|
|
216
|
-
chainId,
|
|
37
|
+
if (!account)
|
|
38
|
+
throw new core_Provider.UnauthorizedError({
|
|
39
|
+
message: `Account "${address}" cannot sign with an access key.`,
|
|
217
40
|
})
|
|
218
|
-
|
|
219
|
-
return
|
|
41
|
+
return account
|
|
220
42
|
}
|
|
221
43
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
actions: {
|
|
227
|
-
async authorizeAccessKey(parameters) {
|
|
228
|
-
const { accounts, activeAccount } = store.getState()
|
|
229
|
-
const account = accounts[activeAccount]?.address
|
|
230
|
-
const result = await authorize({
|
|
231
|
-
...(account ? { account } : {}),
|
|
232
|
-
authorizeAccessKey: parameters,
|
|
233
|
-
method: 'wallet_authorizeAccessKey',
|
|
234
|
-
...(parameters.showDeposit !== undefined
|
|
235
|
-
? { showDeposit: parameters.showDeposit }
|
|
236
|
-
: {}),
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
if (!account)
|
|
240
|
-
store.setState({
|
|
241
|
-
accounts: [{ address: result.accountAddress }],
|
|
242
|
-
activeAccount: 0,
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
keyAuthorization: z.encode(CliAuth.keyAuthorization, result.keyAuthorization),
|
|
247
|
-
rootAddress: result.accountAddress,
|
|
248
|
-
}
|
|
44
|
+
return {
|
|
45
|
+
async signPersonalMessage(parameters) {
|
|
46
|
+
const account = await getAccessKey(parameters.address)
|
|
47
|
+
return await account.sign({ hash: hashMessage({ raw: parameters.data }) })
|
|
249
48
|
},
|
|
250
|
-
async
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const result = await authorize({
|
|
255
|
-
authorizeAccessKey: parameters?.authorizeAccessKey,
|
|
256
|
-
method: 'wallet_connect',
|
|
257
|
-
...(parameters?.showDeposit !== undefined
|
|
258
|
-
? { showDeposit: parameters.showDeposit }
|
|
259
|
-
: {}),
|
|
49
|
+
async signTypedData(parameters) {
|
|
50
|
+
const account = await getAccessKey(parameters.address)
|
|
51
|
+
return await account.sign({
|
|
52
|
+
hash: hashTypedData(JSON.parse(parameters.data) as never),
|
|
260
53
|
})
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
accounts: [
|
|
264
|
-
{
|
|
265
|
-
address: result.accountAddress,
|
|
266
|
-
capabilities: {},
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
keyAuthorization: z.encode(CliAuth.keyAuthorization, result.keyAuthorization),
|
|
270
|
-
}
|
|
271
54
|
},
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
55
|
+
// Limit updates run their own ceremony: the wallet returns a
|
|
56
|
+
// replacement signed authorization for a pending (unpublished) key,
|
|
57
|
+
// or applies the update on-chain for a published one.
|
|
58
|
+
async updateAccessKey(parameters) {
|
|
59
|
+
const chainId = Number(parameters.chainId ?? store.getState().chainId)
|
|
60
|
+
const current = store.accessKeys.list({
|
|
61
|
+
accessKey: parameters.accessKeyAddress,
|
|
62
|
+
account: parameters.address,
|
|
63
|
+
chainId,
|
|
64
|
+
})[0]
|
|
65
|
+
const status = current?.keyAuthorization
|
|
66
|
+
? await store.accessKeys.getStatus({
|
|
67
|
+
accessKey: parameters.accessKeyAddress,
|
|
68
|
+
account: parameters.address,
|
|
69
|
+
chainId,
|
|
70
|
+
client: getClient({ chainId }),
|
|
71
|
+
})
|
|
72
|
+
: undefined
|
|
73
|
+
const pendingKeyAuthorization =
|
|
74
|
+
status === 'pending' && current?.keyAuthorization
|
|
75
|
+
? KeyAuthorization.toRpc(current.keyAuthorization)
|
|
76
|
+
: undefined
|
|
77
|
+
|
|
78
|
+
const encoded = z.encode(Rpc.wallet_updateAccessKey.parameters, {
|
|
79
|
+
...parameters,
|
|
80
|
+
chainId: BigInt(chainId),
|
|
282
81
|
})
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
82
|
+
const result = (await request({
|
|
83
|
+
context: { account: parameters.address, chainId },
|
|
84
|
+
method: 'wallet_updateAccessKey',
|
|
85
|
+
params: [
|
|
286
86
|
{
|
|
287
|
-
|
|
288
|
-
|
|
87
|
+
...encoded,
|
|
88
|
+
...(pendingKeyAuthorization ? { keyAuthorization: pendingKeyAuthorization } : {}),
|
|
289
89
|
},
|
|
290
90
|
],
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
91
|
+
})) as { keyAuthorization?: KeyAuthorization.Rpc | undefined } | null | undefined
|
|
92
|
+
|
|
93
|
+
if (result?.keyAuthorization)
|
|
94
|
+
store.accessKeys.updateAuthorization({
|
|
95
|
+
accessKey: parameters.accessKeyAddress,
|
|
96
|
+
account: parameters.address,
|
|
97
|
+
authorization: KeyAuthorization.fromRpc(result.keyAuthorization),
|
|
98
|
+
chainId,
|
|
99
|
+
})
|
|
299
100
|
},
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
// The CLI persists to string-based filesystem storage, so its p256
|
|
104
|
+
// default opts into an extractable WebCrypto key (a non-extractable one
|
|
105
|
+
// could not survive a restart). secp256k1 stays available for explicit
|
|
106
|
+
// requests.
|
|
107
|
+
keystores: {
|
|
108
|
+
p256: Keystore.webCryptoP256({ extractable: true }),
|
|
109
|
+
secp256k1: Keystore.secp256k1(),
|
|
110
|
+
},
|
|
111
|
+
meta: { name },
|
|
112
|
+
// Only the ceremonies (bootstrap and limit updates) open a browser;
|
|
113
|
+
// everything else signs locally with the stored access key or fails fast.
|
|
114
|
+
methods: ['wallet_connect', 'wallet_authorizeAccessKey', 'wallet_updateAccessKey'],
|
|
115
|
+
name,
|
|
116
|
+
async onPrompt(prompt) {
|
|
117
|
+
if (!prompt.verificationUriFull) {
|
|
118
|
+
const code = prompt.userCode.replace(/^(.{4})(.{4})$/, '$1-$2')
|
|
119
|
+
process.stdout.write(`Enter code ${code} at ${prompt.verificationUri}\n`)
|
|
120
|
+
}
|
|
121
|
+
await open(prompt.verificationUriFull ?? prompt.verificationUri, prompt)
|
|
122
|
+
},
|
|
123
|
+
rdns,
|
|
124
|
+
...(pollingInterval !== undefined ? { pollingInterval } : {}),
|
|
125
|
+
...(timeout !== undefined ? { timeout } : {}),
|
|
126
|
+
url: host,
|
|
314
127
|
})
|
|
315
128
|
}
|
|
316
129
|
|
|
317
130
|
export declare namespace cli {
|
|
318
131
|
export type Options = {
|
|
319
|
-
/**
|
|
132
|
+
/** Base URL of the wallet's device-code endpoints (derives `/register` and `/token`). */
|
|
320
133
|
host: string
|
|
321
134
|
/** Provider display name. @default "Tempo CLI" */
|
|
322
135
|
name?: string | undefined
|
|
323
136
|
/** Browser opener override. */
|
|
324
|
-
open?: ((url: string) => Promise<void> | void) | undefined
|
|
325
|
-
/** Poll
|
|
326
|
-
|
|
137
|
+
open?: ((url: string, prompt: DeviceCode.Prompt) => Promise<void> | void) | undefined
|
|
138
|
+
/** Poll cadence override in milliseconds. Defaults to the host-advertised interval. */
|
|
139
|
+
pollingInterval?: number | undefined
|
|
327
140
|
/** Reverse-DNS provider identifier. @default "xyz.tempo.cli" */
|
|
328
141
|
rdns?: string | undefined
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
class OpenError extends Error {
|
|
335
|
-
code: string
|
|
336
|
-
override cause?: unknown | undefined
|
|
337
|
-
url: string
|
|
338
|
-
|
|
339
|
-
constructor(url: string, code: string, cause?: unknown) {
|
|
340
|
-
super(`Failed to open browser for device code ${formatCode(code)}. Open ${url} manually.`)
|
|
341
|
-
this.name = 'OpenError'
|
|
342
|
-
this.code = code
|
|
343
|
-
this.cause = cause
|
|
344
|
-
this.url = url
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
class TimeoutError extends Error {
|
|
349
|
-
code: string
|
|
350
|
-
url: string
|
|
351
|
-
|
|
352
|
-
constructor(url: string, code: string) {
|
|
353
|
-
super(`Timed out waiting for device code ${formatCode(code)}. Continue at ${url}.`)
|
|
354
|
-
this.name = 'TimeoutError'
|
|
355
|
-
this.code = code
|
|
356
|
-
this.url = url
|
|
142
|
+
/** Overall wait for user approval in milliseconds. @default 300000 */
|
|
143
|
+
timeout?: number | undefined
|
|
357
144
|
}
|
|
358
145
|
}
|
|
359
146
|
|
|
360
|
-
function createCodeChallenge(codeVerifier: string) {
|
|
361
|
-
return Base64.fromBytes(Hash.sha256(Hex.fromString(codeVerifier), { as: 'Bytes' }), {
|
|
362
|
-
pad: false,
|
|
363
|
-
url: true,
|
|
364
|
-
})
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function createCodeVerifier() {
|
|
368
|
-
return Base64.fromBytes(Hex.toBytes(Hex.random(32)), { pad: false, url: true })
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function formatCode(code: string) {
|
|
372
|
-
return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code
|
|
373
|
-
}
|
|
374
|
-
|
|
375
147
|
function defaultOpen(url: string) {
|
|
376
148
|
const command =
|
|
377
149
|
process.platform === 'darwin'
|
|
@@ -386,44 +158,3 @@ function defaultOpen(url: string) {
|
|
|
386
158
|
})
|
|
387
159
|
child.unref()
|
|
388
160
|
}
|
|
389
|
-
|
|
390
|
-
function getApiUrl(serviceUrl: string, path: string) {
|
|
391
|
-
const url = new URL(serviceUrl)
|
|
392
|
-
url.pathname = `${url.pathname.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
|
393
|
-
url.search = ''
|
|
394
|
-
return url.toString()
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function getBrowserUrl(serviceUrl: string, code: string) {
|
|
398
|
-
const url = new URL(serviceUrl)
|
|
399
|
-
url.searchParams.set('code', code)
|
|
400
|
-
return url.toString()
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async function post<
|
|
404
|
-
const request extends z.ZodMiniType,
|
|
405
|
-
const response extends z.ZodMiniType,
|
|
406
|
-
>(options: {
|
|
407
|
-
body: z.output<request>
|
|
408
|
-
request: request
|
|
409
|
-
response: response
|
|
410
|
-
url: string
|
|
411
|
-
}): Promise<z.output<response>> {
|
|
412
|
-
const result = await fetch(options.url, {
|
|
413
|
-
body: JSON.stringify(z.encode(options.request, options.body)),
|
|
414
|
-
headers: { 'content-type': 'application/json' },
|
|
415
|
-
method: 'POST',
|
|
416
|
-
})
|
|
417
|
-
const json = (await result.json().catch(() => ({}))) as z.input<response>
|
|
418
|
-
|
|
419
|
-
if (!result.ok) {
|
|
420
|
-
const error = (json as { error?: unknown }).error
|
|
421
|
-
throw new Error(typeof error === 'string' ? error : `Request failed: ${result.status}`)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return z.decode(options.response, json)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function unsupported(message: string) {
|
|
428
|
-
return new core_Provider.UnsupportedMethodError({ message })
|
|
429
|
-
}
|