ethagent 2.1.0 → 2.2.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/README.md +2 -2
- package/package.json +1 -1
- package/src/auth/openaiOAuth/credentials.ts +47 -0
- package/src/auth/openaiOAuth/crypto.ts +23 -0
- package/src/auth/openaiOAuth/index.ts +238 -0
- package/src/auth/openaiOAuth/landingPage.ts +125 -0
- package/src/auth/openaiOAuth/listener.ts +151 -0
- package/src/auth/openaiOAuth/refresh.ts +70 -0
- package/src/auth/openaiOAuth/shared.ts +115 -0
- package/src/chat/chatSessionState.ts +2 -1
- package/src/chat/commands.ts +2 -1
- package/src/identity/ens/agentRecords.ts +5 -19
- package/src/identity/ens/ensAutomation/setup.ts +0 -1
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/hub/OperationalRoutes.tsx +2 -11
- package/src/identity/hub/components/IdentitySummary.tsx +8 -3
- package/src/identity/hub/components/MenuScreen.tsx +1 -2
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +1 -3
- package/src/identity/hub/effects/ens/transactions.ts +15 -15
- package/src/identity/hub/effects/index.ts +0 -1
- package/src/identity/hub/effects/profile/profileState.ts +12 -4
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +37 -159
- package/src/identity/hub/effects/rebackup/runRebackup.ts +2 -2
- package/src/identity/hub/effects/restoreAdmin.ts +2 -61
- package/src/identity/hub/effects/shared/sync.ts +3 -44
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +1 -39
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +5 -3
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +1 -1
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +80 -175
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +20 -75
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +16 -56
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +0 -18
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -136
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +5 -4
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +56 -205
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +7 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +0 -31
- package/src/identity/hub/flows/ens/ensEditCopy.ts +1 -1
- package/src/identity/hub/flows/ens/ensEditTypes.ts +6 -20
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +7 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +5 -5
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +0 -1
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +1 -34
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +0 -4
- package/src/identity/hub/reconciliation/index.ts +0 -7
- package/src/identity/hub/reconciliation/walletSetup.ts +1 -194
- package/src/identity/wallet/browserWallet/types.ts +0 -5
- package/src/identity/wallet/page/copy.ts +1 -31
- package/src/identity/wallet/walletPurposeCompat.ts +0 -2
- package/src/models/ModelPicker.tsx +246 -8
- package/src/models/catalog.ts +28 -1
- package/src/models/modelPickerOptions.ts +15 -1
- package/src/providers/openai-responses-format.ts +156 -0
- package/src/providers/openai-responses.ts +276 -0
- package/src/providers/registry.ts +85 -8
- package/src/runtime/systemPrompt.ts +1 -1
- package/src/runtime/turn.ts +0 -1
- package/src/storage/secrets.ts +4 -1
- package/src/tools/privateContinuityEditTool.ts +6 -0
- package/src/utils/openExternal.ts +20 -10
- package/src/identity/ens/ensRegistration.ts +0 -199
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import {
|
|
3
|
+
import { type Address } from 'viem'
|
|
4
4
|
import { Surface } from '../../../../ui/Surface.js'
|
|
5
5
|
import { Select, type SelectOption } from '../../../../ui/Select.js'
|
|
6
|
-
import { TextInput } from '../../../../ui/TextInput.js'
|
|
7
6
|
import { Spinner } from '../../../../ui/Spinner.js'
|
|
8
7
|
import { theme } from '../../../../ui/theme.js'
|
|
9
8
|
import {
|
|
10
|
-
createMainnetClient,
|
|
11
9
|
isEthDomain,
|
|
12
10
|
normalizeEthDomain,
|
|
13
11
|
} from '../../../ens/ensLookup.js'
|
|
14
|
-
import {
|
|
15
|
-
generateRegistrationSecret,
|
|
16
|
-
ONE_YEAR_SECONDS,
|
|
17
|
-
readNameAvailable,
|
|
18
|
-
readRentPrice,
|
|
19
|
-
validateRegistrableName,
|
|
20
|
-
} from '../../../ens/ensRegistration.js'
|
|
21
12
|
import { isRootEthName } from '../../../ens/ensAutomation.js'
|
|
22
13
|
import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
|
|
23
14
|
import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
|
|
24
15
|
import {
|
|
25
|
-
readIdentityStateString,
|
|
26
16
|
type CustodyMode,
|
|
27
17
|
} from '../../model/custody.js'
|
|
28
18
|
import { shortAddress } from '../../model/format.js'
|
|
@@ -30,8 +20,9 @@ import {
|
|
|
30
20
|
recordsDiffHasChanges,
|
|
31
21
|
type EnsLinkOptions,
|
|
32
22
|
} from './ensEditCopy.js'
|
|
23
|
+
import { openExternalUrl } from '../../../../utils/openExternal.js'
|
|
24
|
+
import { TextInput } from '../../../../ui/TextInput.js'
|
|
33
25
|
import {
|
|
34
|
-
EnsSetupRow,
|
|
35
26
|
EnsStatusBanner,
|
|
36
27
|
footerHint,
|
|
37
28
|
SubdomainEntry,
|
|
@@ -42,18 +33,15 @@ import {
|
|
|
42
33
|
ReviewScreen,
|
|
43
34
|
SimpleEnsIssueScreen,
|
|
44
35
|
} from './EnsEditReviewScreens.js'
|
|
45
|
-
import {
|
|
46
|
-
EscCancel,
|
|
47
|
-
RegisterRootCommitRunner,
|
|
48
|
-
RegisterRootTxRunner,
|
|
49
|
-
RegisterRootWaitScreen,
|
|
50
|
-
} from './EnsEditRunners.js'
|
|
36
|
+
import { EscCancel } from './EnsEditRunners.js'
|
|
51
37
|
import type {
|
|
52
38
|
DiscoveryState,
|
|
53
39
|
EnsEditProps,
|
|
54
40
|
EnsPhase,
|
|
55
41
|
} from './ensEditTypes.js'
|
|
56
42
|
|
|
43
|
+
const ENS_DOMAINS_URL = 'https://app.ens.domains'
|
|
44
|
+
|
|
57
45
|
type SimpleScreenProps = {
|
|
58
46
|
phase: EnsPhase
|
|
59
47
|
discovery: DiscoveryState
|
|
@@ -69,8 +57,9 @@ type SimpleScreenProps = {
|
|
|
69
57
|
setOperatorWalletSession: (session: BrowserWalletReady | null) => void
|
|
70
58
|
setPhase: (phase: EnsPhase) => void
|
|
71
59
|
cancelDiscoveryToModeSelect: () => void
|
|
72
|
-
runDiscovery: () => void
|
|
60
|
+
runDiscovery: (mode?: 'simple' | 'advanced') => void
|
|
73
61
|
runValidation: (fullName: string, mode: 'simple' | 'advanced', phaseOwnerAddress?: Address, operatorWallet?: Address) => Promise<void>
|
|
62
|
+
runAdvancedRootCheck: (rootName: string) => void
|
|
74
63
|
backToSimpleSubdomain: (fullName: string) => void
|
|
75
64
|
runSimpleCreatePreflight: (fullName: string) => void
|
|
76
65
|
onEnsSetup: EnsEditProps['onEnsSetup']
|
|
@@ -96,6 +85,7 @@ export function renderSimpleEnsPhase({
|
|
|
96
85
|
cancelDiscoveryToModeSelect,
|
|
97
86
|
runDiscovery,
|
|
98
87
|
runValidation,
|
|
88
|
+
runAdvancedRootCheck,
|
|
99
89
|
backToSimpleSubdomain,
|
|
100
90
|
runSimpleCreatePreflight,
|
|
101
91
|
onEnsSetup,
|
|
@@ -126,7 +116,7 @@ export function renderSimpleEnsPhase({
|
|
|
126
116
|
}
|
|
127
117
|
|
|
128
118
|
if (phase.kind === 'pick-parent') {
|
|
129
|
-
type DomainAction = `pick:${string}` | '
|
|
119
|
+
type DomainAction = `pick:${string}` | 'open-ens-domains' | 'manual' | 'retry' | 'back'
|
|
130
120
|
const ownedNames = discovery.status === 'ok' || discovery.status === 'error' ? discovery.names : []
|
|
131
121
|
const errorMessage = discovery.status === 'error' ? 'Root ENS Lookup Failed' : null
|
|
132
122
|
const warningMessage = discovery.status === 'ok' ? discovery.warning : null
|
|
@@ -144,10 +134,10 @@ export function renderSimpleEnsPhase({
|
|
|
144
134
|
})),
|
|
145
135
|
]
|
|
146
136
|
: []),
|
|
147
|
-
{ value: '
|
|
148
|
-
{ value: '
|
|
149
|
-
...(noOwnedNames
|
|
150
|
-
? [{ value: 'retry' as DomainAction, label: 'Scan Again', hint: '
|
|
137
|
+
{ value: 'open-ens-domains' as DomainAction, role: 'section' as const, label: 'No Parent Name?' },
|
|
138
|
+
{ value: 'open-ens-domains' as DomainAction, label: 'Register .eth Name', hint: 'Open the ENS app in your browser; come back when this wallet owns one' },
|
|
139
|
+
...(noOwnedNames || discovery.status === 'ok'
|
|
140
|
+
? [{ value: 'retry' as DomainAction, label: 'Scan Again', hint: 'Re-run root .eth name discovery for this wallet' }]
|
|
151
141
|
: []),
|
|
152
142
|
...(discovery.status === 'error'
|
|
153
143
|
? [
|
|
@@ -159,6 +149,7 @@ export function renderSimpleEnsPhase({
|
|
|
159
149
|
{ value: 'back', label: 'Back', hint: 'Return to setup type', role: 'utility' },
|
|
160
150
|
]
|
|
161
151
|
|
|
152
|
+
const advancedMode = phase.mode === 'advanced'
|
|
162
153
|
return (
|
|
163
154
|
<Surface
|
|
164
155
|
title="Assign ENS Name"
|
|
@@ -166,198 +157,45 @@ export function renderSimpleEnsPhase({
|
|
|
166
157
|
footer={footerHint('enter select · esc back')}
|
|
167
158
|
>
|
|
168
159
|
<EnsStatusBanner identity={identity} noRootEnsName={noOwnedNames} />
|
|
160
|
+
{advancedMode
|
|
161
|
+
? <Text color={theme.dim}>Owner wallet: <Text color={theme.text}>{shortAddress(ownerAddress)}</Text></Text>
|
|
162
|
+
: null}
|
|
169
163
|
{validationError ? <Text color={theme.accentError}>{validationError}</Text> : null}
|
|
164
|
+
{phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
|
|
170
165
|
{errorMessage ? <Text color={theme.accentError}>{errorMessage}: {discovery.status === 'error' ? discovery.message : ''}</Text> : null}
|
|
171
166
|
{warningMessage ? <Text color={theme.accentPeriwinkle}>{warningMessage}</Text> : null}
|
|
167
|
+
{noOwnedNames
|
|
168
|
+
? (
|
|
169
|
+
<Box marginTop={1} flexDirection="column">
|
|
170
|
+
<Text color={theme.dim}>This wallet does not own a parent <Text color={theme.text}>.eth</Text> name yet.</Text>
|
|
171
|
+
<Text color={theme.dim}>Register one at <Text color={theme.text}>{ENS_DOMAINS_URL}</Text>, then come back and Scan Again.</Text>
|
|
172
|
+
</Box>
|
|
173
|
+
)
|
|
174
|
+
: null}
|
|
172
175
|
<Box marginTop={1}>
|
|
173
176
|
<Select<DomainAction>
|
|
174
177
|
options={options}
|
|
175
178
|
hintLayout="inline"
|
|
176
179
|
onSubmit={choice => {
|
|
177
180
|
if (choice === 'back') return setPhase({ kind: 'mode-select' })
|
|
178
|
-
if (choice === 'manual') { setPhase({ kind: 'manual-parent' }); return }
|
|
179
|
-
if (choice === '
|
|
181
|
+
if (choice === 'manual') { setPhase({ kind: 'manual-parent', ...(advancedMode ? { mode: 'advanced' as const } : {}) }); return }
|
|
182
|
+
if (choice === 'open-ens-domains') {
|
|
183
|
+
openExternalUrl(ENS_DOMAINS_URL)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
180
186
|
if (choice === 'retry') {
|
|
181
|
-
runDiscovery()
|
|
187
|
+
runDiscovery(advancedMode ? 'advanced' : 'simple')
|
|
182
188
|
return
|
|
183
189
|
}
|
|
184
190
|
if (choice.startsWith('pick:')) {
|
|
185
191
|
const name = choice.slice('pick:'.length)
|
|
186
|
-
if (name)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
</Surface>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (phase.kind === 'register-root-input') {
|
|
197
|
-
return (
|
|
198
|
-
<Surface
|
|
199
|
-
title="Register an ENS Name"
|
|
200
|
-
subtitle="Pick a name to register on Ethereum mainnet for 1 year. The connected wallet pays and becomes the owner."
|
|
201
|
-
footer={footerHint('enter continues · esc back')}
|
|
202
|
-
>
|
|
203
|
-
{phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
|
|
204
|
-
<Box marginTop={1}>
|
|
205
|
-
<TextInput
|
|
206
|
-
placeholder="myname"
|
|
207
|
-
validate={value => {
|
|
208
|
-
const result = validateRegistrableName(value)
|
|
209
|
-
return result.ok ? null : result.detail
|
|
210
|
-
}}
|
|
211
|
-
onSubmit={value => {
|
|
212
|
-
const result = validateRegistrableName(value)
|
|
213
|
-
if (!result.ok) {
|
|
214
|
-
setPhase({ kind: 'register-root-input', error: result.detail })
|
|
215
|
-
return
|
|
216
|
-
}
|
|
217
|
-
const secret = generateRegistrationSecret()
|
|
218
|
-
setPhase({ kind: 'register-root-pricing', label: result.label, secret, busy: true })
|
|
219
|
-
const client = createMainnetClient()
|
|
220
|
-
Promise.all([
|
|
221
|
-
readNameAvailable(client, result.label),
|
|
222
|
-
readRentPrice(client, result.label, BigInt(ONE_YEAR_SECONDS)),
|
|
223
|
-
])
|
|
224
|
-
.then(([available, price]) => {
|
|
225
|
-
if (!available) {
|
|
226
|
-
setPhase({ kind: 'register-root-input', error: `${result.label}.eth is already registered. Pick a different name.` })
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
setPhase({ kind: 'register-root-pricing', label: result.label, secret, busy: false, price })
|
|
230
|
-
})
|
|
231
|
-
.catch((err: unknown) => {
|
|
232
|
-
setPhase({ kind: 'register-root-input', error: err instanceof Error ? err.message : String(err) })
|
|
233
|
-
})
|
|
234
|
-
}}
|
|
235
|
-
onCancel={() => setPhase({ kind: 'pick-parent' })}
|
|
236
|
-
/>
|
|
237
|
-
</Box>
|
|
238
|
-
</Surface>
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (phase.kind === 'register-root-pricing') {
|
|
243
|
-
const fullName = `${phase.label}.eth`
|
|
244
|
-
if (phase.busy || !phase.price) {
|
|
245
|
-
return (
|
|
246
|
-
<Surface
|
|
247
|
-
title="Register an ENS Name"
|
|
248
|
-
subtitle={`Checking availability and price for ${fullName}...`}
|
|
249
|
-
footer={footerHint('esc back')}
|
|
250
|
-
>
|
|
251
|
-
<Box marginTop={1}><Text color={theme.textSubtle}>Reading from Ethereum mainnet...</Text></Box>
|
|
252
|
-
</Surface>
|
|
253
|
-
)
|
|
254
|
-
}
|
|
255
|
-
const totalEth = formatEther(phase.price.total)
|
|
256
|
-
const baseEth = formatEther(phase.price.base)
|
|
257
|
-
const premiumEth = formatEther(phase.price.premium)
|
|
258
|
-
return (
|
|
259
|
-
<Surface
|
|
260
|
-
title="Register an ENS Name"
|
|
261
|
-
subtitle={`${fullName} is available. Review the cost and continue with two transactions.`}
|
|
262
|
-
footer={footerHint('enter select · esc back')}
|
|
263
|
-
>
|
|
264
|
-
<Box flexDirection="column">
|
|
265
|
-
<EnsSetupRow label="Name" value={fullName} />
|
|
266
|
-
<EnsSetupRow label="Owner" value={shortAddress(ownerAddress)} />
|
|
267
|
-
<EnsSetupRow label="Duration" value="1 year" />
|
|
268
|
-
<EnsSetupRow label="Rent" value={`${baseEth} ETH`} />
|
|
269
|
-
{phase.price.premium > 0n ? <EnsSetupRow label="Premium" value={`${premiumEth} ETH`} /> : null}
|
|
270
|
-
<EnsSetupRow label="Total" value={`${totalEth} ETH`} />
|
|
271
|
-
<Box marginTop={1}><Text color={theme.dim}>Two transactions: a commit, a 60-second wait, then the registration. Keep this tab open through both.</Text></Box>
|
|
272
|
-
<Box marginTop={1}><Text color={theme.dim}>Manage renewals on https://app.ens.domains/ before the year is up.</Text></Box>
|
|
273
|
-
</Box>
|
|
274
|
-
<Box marginTop={1}>
|
|
275
|
-
<Select<'commit' | 'pick-different' | 'back'>
|
|
276
|
-
options={[
|
|
277
|
-
{ value: 'commit', role: 'section', label: 'Continue' },
|
|
278
|
-
{ value: 'commit', label: 'Continue to Commit', hint: 'Sign the first transaction' },
|
|
279
|
-
{ value: 'pick-different', label: 'Pick a Different Name', hint: 'Return to name input' },
|
|
280
|
-
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
281
|
-
{ value: 'back', label: 'Back', hint: 'Return to root selection', role: 'utility' },
|
|
282
|
-
]}
|
|
283
|
-
hintLayout="inline"
|
|
284
|
-
onSubmit={choice => {
|
|
285
|
-
if (choice === 'commit') {
|
|
286
|
-
setPhase({ kind: 'register-root-commit-tx', label: phase.label, secret: phase.secret, price: phase.price! })
|
|
287
|
-
return
|
|
288
|
-
}
|
|
289
|
-
if (choice === 'pick-different') return setPhase({ kind: 'register-root-input' })
|
|
290
|
-
setPhase({ kind: 'pick-parent' })
|
|
291
|
-
}}
|
|
292
|
-
onCancel={() => setPhase({ kind: 'pick-parent' })}
|
|
293
|
-
/>
|
|
294
|
-
</Box>
|
|
295
|
-
</Surface>
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (phase.kind === 'register-root-commit-tx') {
|
|
300
|
-
return (
|
|
301
|
-
<RegisterRootCommitRunner
|
|
302
|
-
phase={phase}
|
|
303
|
-
ownerAddress={ownerAddress}
|
|
304
|
-
walletSession={operatorWalletSession}
|
|
305
|
-
onWalletReady={setOperatorWalletSession}
|
|
306
|
-
onCommitted={() => setPhase({ kind: 'register-root-wait', label: phase.label, secret: phase.secret, price: phase.price, commitMinedAt: Date.now() })}
|
|
307
|
-
onError={msg => setPhase({ kind: 'register-root-input', error: msg })}
|
|
308
|
-
/>
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (phase.kind === 'register-root-wait') {
|
|
313
|
-
return (
|
|
314
|
-
<RegisterRootWaitScreen
|
|
315
|
-
phase={phase}
|
|
316
|
-
onReady={() => setPhase({ kind: 'register-root-tx', label: phase.label, secret: phase.secret, price: phase.price })}
|
|
317
|
-
onCancel={() => setPhase({ kind: 'register-root-input', error: 'Commit cancelled. Names must be re-committed; secrets do not carry over.' })}
|
|
318
|
-
/>
|
|
319
|
-
)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (phase.kind === 'register-root-tx') {
|
|
323
|
-
return (
|
|
324
|
-
<RegisterRootTxRunner
|
|
325
|
-
phase={phase}
|
|
326
|
-
ownerAddress={ownerAddress}
|
|
327
|
-
walletSession={operatorWalletSession}
|
|
328
|
-
onWalletReady={setOperatorWalletSession}
|
|
329
|
-
onRegistered={() => setPhase({ kind: 'register-root-done', fullName: `${phase.label}.eth` })}
|
|
330
|
-
onError={msg => setPhase({ kind: 'register-root-input', error: msg })}
|
|
331
|
-
/>
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (phase.kind === 'register-root-done') {
|
|
336
|
-
return (
|
|
337
|
-
<Surface
|
|
338
|
-
title="ENS Name Registered"
|
|
339
|
-
subtitle={`${phase.fullName} is now owned by ${shortAddress(ownerAddress)}.`}
|
|
340
|
-
footer={footerHint('enter select · esc back')}
|
|
341
|
-
>
|
|
342
|
-
<Box flexDirection="column">
|
|
343
|
-
<Text color={theme.text}>Manage renewals and other settings on https://app.ens.domains/ before the year is up.</Text>
|
|
344
|
-
<Text color={theme.dim}>The name will appear in the picker on the next scan.</Text>
|
|
345
|
-
</Box>
|
|
346
|
-
<Box marginTop={1}>
|
|
347
|
-
<Select<'scan' | 'back'>
|
|
348
|
-
options={[
|
|
349
|
-
{ value: 'scan', role: 'section', label: 'Next' },
|
|
350
|
-
{ value: 'scan', label: 'Scan Again', hint: 'Re-run root ENS name discovery' },
|
|
351
|
-
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
352
|
-
{ value: 'back', label: 'Back to ENS', hint: 'Return to ENS menu', role: 'utility' },
|
|
353
|
-
]}
|
|
354
|
-
hintLayout="inline"
|
|
355
|
-
onSubmit={choice => {
|
|
356
|
-
if (choice === 'scan') {
|
|
357
|
-
runDiscovery()
|
|
358
|
-
return
|
|
192
|
+
if (!name) return
|
|
193
|
+
if (advancedMode) {
|
|
194
|
+
runAdvancedRootCheck(name)
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
setPhase({ kind: 'pick-subdomain', parent: name })
|
|
359
198
|
}
|
|
360
|
-
setPhase({ kind: 'mode-select' })
|
|
361
199
|
}}
|
|
362
200
|
onCancel={() => setPhase({ kind: 'mode-select' })}
|
|
363
201
|
/>
|
|
@@ -367,6 +205,7 @@ export function renderSimpleEnsPhase({
|
|
|
367
205
|
}
|
|
368
206
|
|
|
369
207
|
if (phase.kind === 'manual-parent') {
|
|
208
|
+
const advancedMode = phase.mode === 'advanced'
|
|
370
209
|
return (
|
|
371
210
|
<Surface
|
|
372
211
|
title="Your Root .eth Name"
|
|
@@ -374,11 +213,15 @@ export function renderSimpleEnsPhase({
|
|
|
374
213
|
footer={footerHint('enter continues · esc back')}
|
|
375
214
|
>
|
|
376
215
|
{statusBanner}
|
|
216
|
+
{advancedMode
|
|
217
|
+
? <Text color={theme.dim}>Owner wallet: <Text color={theme.text}>{shortAddress(ownerAddress)}</Text></Text>
|
|
218
|
+
: null}
|
|
377
219
|
<Box marginTop={1}>
|
|
378
220
|
<Text color={theme.dim}>Only root .eth names on Ethereum mainnet are supported.</Text>
|
|
379
221
|
</Box>
|
|
222
|
+
{phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
|
|
380
223
|
<TextInput
|
|
381
|
-
key=
|
|
224
|
+
key={`edit-ens-parent-manual-${advancedMode ? 'advanced' : 'simple'}`}
|
|
382
225
|
placeholder="e.g. name.eth"
|
|
383
226
|
validate={value => {
|
|
384
227
|
const v = normalizeEthDomain(value)
|
|
@@ -387,8 +230,15 @@ export function renderSimpleEnsPhase({
|
|
|
387
230
|
if (!isRootEthName(v)) return 'Enter a root .eth name, e.g. name.eth'
|
|
388
231
|
return null
|
|
389
232
|
}}
|
|
390
|
-
onSubmit={value =>
|
|
391
|
-
|
|
233
|
+
onSubmit={value => {
|
|
234
|
+
const root = normalizeEthDomain(value)
|
|
235
|
+
if (advancedMode) {
|
|
236
|
+
runAdvancedRootCheck(root)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
setPhase({ kind: 'pick-subdomain', parent: root })
|
|
240
|
+
}}
|
|
241
|
+
onCancel={() => setPhase({ kind: 'pick-parent', ...(advancedMode ? { mode: 'advanced' as const } : {}) })}
|
|
392
242
|
/>
|
|
393
243
|
</Surface>
|
|
394
244
|
)
|
|
@@ -399,7 +249,8 @@ export function renderSimpleEnsPhase({
|
|
|
399
249
|
<SubdomainEntry
|
|
400
250
|
parent={phase.parent}
|
|
401
251
|
ownerAddress={ownerAddress}
|
|
402
|
-
|
|
252
|
+
initialValue={phase.label}
|
|
253
|
+
placeholder={agentNameSuggestion || 'subdomain name'}
|
|
403
254
|
error={phase.error}
|
|
404
255
|
onConfirm={fullName => { void runValidation(fullName, 'simple') }}
|
|
405
256
|
onBack={() => setPhase({ kind: 'pick-parent' })}
|
|
@@ -8,6 +8,7 @@ import { OperatorWalletsScreen } from './OperatorWalletsScreen.js'
|
|
|
8
8
|
import { EDIT_PROFILE_STEPS, EditProfileFlow } from '../profile/EditProfileFlow.js'
|
|
9
9
|
import { FlowTimeline } from '../../components/FlowTimeline.js'
|
|
10
10
|
import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
|
|
11
|
+
import type { AgentReconciliation } from '../../reconciliation/index.js'
|
|
11
12
|
|
|
12
13
|
type StepOf<K extends Step['kind']> = Extract<Step, { kind: K }>
|
|
13
14
|
|
|
@@ -27,11 +28,13 @@ type IdentityHubEnsStep = StepOf<
|
|
|
27
28
|
type IdentityHubEnsFlowProps = {
|
|
28
29
|
step: IdentityHubEnsStep
|
|
29
30
|
walletSession: BrowserWalletReady | null
|
|
31
|
+
reconciliation: AgentReconciliation
|
|
30
32
|
onSetStep: (step: Step) => void
|
|
31
33
|
onBack: () => void
|
|
32
34
|
onWalletReady: (session: BrowserWalletReady | null) => void
|
|
33
35
|
onTriggerRebackup: (backStep: Step, profileUpdates?: ProfileUpdates) => void
|
|
34
36
|
onTriggerPublicProfileSave: (backStep: Step, profileUpdates: ProfileUpdates) => void
|
|
37
|
+
onWithdrawTokenForEns: (step: Step) => void
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export function isIdentityHubEnsStep(step: Step): step is IdentityHubEnsStep {
|
|
@@ -50,11 +53,13 @@ export function isIdentityHubEnsStep(step: Step): step is IdentityHubEnsStep {
|
|
|
50
53
|
export const IdentityHubEnsFlow: React.FC<IdentityHubEnsFlowProps> = ({
|
|
51
54
|
step,
|
|
52
55
|
walletSession,
|
|
56
|
+
reconciliation,
|
|
53
57
|
onSetStep,
|
|
54
58
|
onBack,
|
|
55
59
|
onWalletReady,
|
|
56
60
|
onTriggerRebackup,
|
|
57
61
|
onTriggerPublicProfileSave,
|
|
62
|
+
onWithdrawTokenForEns,
|
|
58
63
|
}) => {
|
|
59
64
|
if (step.kind === 'manage-ens-operators') {
|
|
60
65
|
return (
|
|
@@ -75,6 +80,8 @@ export const IdentityHubEnsFlow: React.FC<IdentityHubEnsFlowProps> = ({
|
|
|
75
80
|
return (
|
|
76
81
|
<EditProfileFlow
|
|
77
82
|
step={step}
|
|
83
|
+
reconciliation={reconciliation}
|
|
84
|
+
onWithdrawToken={() => onWithdrawTokenForEns(step)}
|
|
78
85
|
onNameSubmit={name => {
|
|
79
86
|
if (step.kind !== 'edit-profile-name') return
|
|
80
87
|
onSetStep({
|
|
@@ -26,10 +26,6 @@ import {
|
|
|
26
26
|
upsertApprovedOperatorWallet,
|
|
27
27
|
type ApprovedOperatorWalletRecord,
|
|
28
28
|
} from '../../operatorWallets.js'
|
|
29
|
-
import {
|
|
30
|
-
reconcileWalletSetup,
|
|
31
|
-
type RecordsFixPlan,
|
|
32
|
-
} from '../../reconciliation/index.js'
|
|
33
29
|
|
|
34
30
|
type OperatorPhase =
|
|
35
31
|
| { kind: 'main'; notice?: string; error?: string }
|
|
@@ -71,28 +67,11 @@ export const OperatorWalletsScreen: React.FC<OperatorWalletsScreenProps> = ({
|
|
|
71
67
|
const restoreAccessEpoch = readStateNumber(state, 'restoreAccessEpoch') ?? 0
|
|
72
68
|
const records = normalizeApprovedOperatorWallets(state.approvedOperatorWallets)
|
|
73
69
|
const [phase, setPhase] = React.useState<OperatorPhase>({ kind: 'main', notice, error })
|
|
74
|
-
const [fixPlan, setFixPlan] = React.useState<RecordsFixPlan | null>(null)
|
|
75
70
|
|
|
76
71
|
React.useEffect(() => {
|
|
77
72
|
setPhase(current => current.kind === 'main' ? { kind: 'main', notice, error } : current)
|
|
78
73
|
}, [notice, error])
|
|
79
74
|
|
|
80
|
-
React.useEffect(() => {
|
|
81
|
-
if (custodyMode !== 'advanced' || records.length === 0) {
|
|
82
|
-
setFixPlan(null)
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
let cancelled = false
|
|
86
|
-
reconcileWalletSetup({ identity, registry })
|
|
87
|
-
.then(plan => { if (!cancelled) setFixPlan(plan) })
|
|
88
|
-
.catch(() => { if (!cancelled) setFixPlan(null) })
|
|
89
|
-
return () => { cancelled = true }
|
|
90
|
-
}, [identity, registry, custodyMode, records.length])
|
|
91
|
-
|
|
92
|
-
const driftItems = fixPlan?.items.filter(item =>
|
|
93
|
-
item.kind === 'missing-approval' || item.kind === 'stale-approval',
|
|
94
|
-
) ?? []
|
|
95
|
-
|
|
96
75
|
const saveOperators = React.useCallback((
|
|
97
76
|
approvedOperatorWallets: ApprovedOperatorWalletRecord[],
|
|
98
77
|
activeOperator: Address | '' | undefined,
|
|
@@ -253,16 +232,6 @@ export const OperatorWalletsScreen: React.FC<OperatorWalletsScreenProps> = ({
|
|
|
253
232
|
<Text color={theme.dim}>Add as many operator wallets as needed; unlink any saved wallet here.</Text>
|
|
254
233
|
<Text color={theme.dim}>No approve(), setApprovalForAll(), transferFrom(), or token approval is requested.</Text>
|
|
255
234
|
</Box>
|
|
256
|
-
{driftItems.length > 0
|
|
257
|
-
? (
|
|
258
|
-
<Box marginTop={1} flexDirection="column">
|
|
259
|
-
<Text color="#e8a070">
|
|
260
|
-
{`! ENS resolver drift: ${driftItems.length} operator wallet${driftItems.length === 1 ? '' : 's'} missing onchain approval.`}
|
|
261
|
-
</Text>
|
|
262
|
-
<Text color={theme.dim}>Run "Fix Records" from Custody Mode to re-sync; onchain ENS writes will fail until then.</Text>
|
|
263
|
-
</Box>
|
|
264
|
-
)
|
|
265
|
-
: null}
|
|
266
235
|
{phaseNotice ? <Text color={theme.accentPeriwinkle}>{phaseNotice}</Text> : null}
|
|
267
236
|
{phaseError ? <Text color={theme.accentError}>{phaseError}</Text> : null}
|
|
268
237
|
</Box>
|
|
@@ -18,7 +18,7 @@ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolea
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function emptyAgentEnsRecords(): AgentEnsRecords {
|
|
21
|
-
return { token: ''
|
|
21
|
+
return { token: '' }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
|
|
@@ -13,26 +13,15 @@ import type {
|
|
|
13
13
|
EnsSubdomainDeletePlan,
|
|
14
14
|
} from '../../../ens/ensAutomation.js'
|
|
15
15
|
import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
|
|
16
|
+
import type { AgentReconciliation } from '../../reconciliation/index.js'
|
|
16
17
|
import type { EnsLinkOptions } from './ensEditCopy.js'
|
|
17
18
|
|
|
18
19
|
export type EnsIssueValidation = Extract<EnsValidation, { ok: false }>
|
|
19
20
|
|
|
20
|
-
export type RegisterCommitPhase = {
|
|
21
|
-
label: string
|
|
22
|
-
secret: `0x${string}`
|
|
23
|
-
price: { base: bigint; premium: bigint; total: bigint }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
21
|
export type SimpleEnsPhase =
|
|
27
|
-
| { kind: 'discovering' }
|
|
28
|
-
| { kind: 'pick-parent' }
|
|
29
|
-
| { kind: 'manual-parent' }
|
|
30
|
-
| { kind: 'register-root-input'; error?: string }
|
|
31
|
-
| { kind: 'register-root-pricing'; label: string; secret: `0x${string}`; busy: boolean; error?: string; price?: { base: bigint; premium: bigint; total: bigint } }
|
|
32
|
-
| { kind: 'register-root-commit-tx'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint } }
|
|
33
|
-
| { kind: 'register-root-wait'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint }; commitMinedAt: number }
|
|
34
|
-
| { kind: 'register-root-tx'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint } }
|
|
35
|
-
| { kind: 'register-root-done'; fullName: string }
|
|
22
|
+
| { kind: 'discovering'; mode?: 'simple' | 'advanced' }
|
|
23
|
+
| { kind: 'pick-parent'; mode?: 'simple' | 'advanced'; error?: string }
|
|
24
|
+
| { kind: 'manual-parent'; mode?: 'simple' | 'advanced'; error?: string }
|
|
36
25
|
| { kind: 'pick-subdomain'; parent: string; label?: string; error?: string }
|
|
37
26
|
| { kind: 'simple-name-missing'; fullName: string; validation: EnsIssueValidation }
|
|
38
27
|
| { kind: 'simple-create-preflight'; rootName: string; label: string; fullName: string }
|
|
@@ -43,14 +32,9 @@ export type SimpleEnsPhase =
|
|
|
43
32
|
|
|
44
33
|
export type AdvancedEnsPhase =
|
|
45
34
|
| { kind: 'advanced-transfer-check' }
|
|
46
|
-
| { kind: 'advanced-root'; rootName?: string; error?: string }
|
|
47
35
|
| { kind: 'advanced-root-check'; rootName: string }
|
|
48
36
|
| { kind: 'advanced-subdomain'; rootName: string; label?: string; error?: string }
|
|
49
37
|
| { kind: 'advanced-subdomain-check'; rootName: string; label: string }
|
|
50
|
-
| { kind: 'advanced-operator-wallet'; rootName: string; label: string; registryAction?: EnsRegistryAction; error?: string }
|
|
51
|
-
| { kind: 'advanced-operator-wallet-manual'; rootName: string; label: string; error?: string }
|
|
52
|
-
| { kind: 'advanced-operator-wallet-connecting'; rootName: string; label: string }
|
|
53
|
-
| { kind: 'advanced-preflight'; rootName: string; label: string; operatorWallet: Address }
|
|
54
38
|
| { kind: 'advanced-review'; setup: EnsSetupPlan }
|
|
55
39
|
| { kind: 'advanced-manual'; fallback: EnsSetupBlockedPlan }
|
|
56
40
|
|
|
@@ -81,11 +65,13 @@ export type DiscoveryState =
|
|
|
81
65
|
export type EnsEditProps = {
|
|
82
66
|
identity: EthagentIdentity
|
|
83
67
|
registry: Erc8004RegistryConfig
|
|
68
|
+
reconciliation: AgentReconciliation
|
|
84
69
|
onEnsLink: (fullName: string, options: EnsLinkOptions) => void
|
|
85
70
|
onEnsUnlink: () => void
|
|
86
71
|
onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
|
|
87
72
|
onEnsSetup: (setup: EnsSetupPlan) => void
|
|
88
73
|
onManageOperatorWalletAccess: () => void
|
|
74
|
+
onWithdrawToken: () => void
|
|
89
75
|
initialView?: 'advanced'
|
|
90
76
|
onBack: () => void
|
|
91
77
|
}
|
|
@@ -11,9 +11,11 @@ import { readIdentityStateString } from '../../model/custody.js'
|
|
|
11
11
|
import { FlowTimeline } from '../../components/FlowTimeline.js'
|
|
12
12
|
import { validateAgentIconReference } from '../../../profile/agentIcon.js'
|
|
13
13
|
import { EnsEditFlow, type EnsLinkOptions } from '../ens/EnsEditFlow.js'
|
|
14
|
+
import type { AgentReconciliation } from '../../reconciliation/index.js'
|
|
14
15
|
|
|
15
16
|
type EditProfileFlowProps = {
|
|
16
17
|
step: Extract<Step, { kind: 'edit-profile-name' | 'edit-profile-description' | 'edit-profile-image' | 'edit-profile-review' | 'edit-profile-ens' }>
|
|
18
|
+
reconciliation: AgentReconciliation
|
|
17
19
|
onNameSubmit: (name: string) => void
|
|
18
20
|
onDescriptionSubmit: (description: string) => void
|
|
19
21
|
onIconSubmit: (iconPath?: string) => void
|
|
@@ -24,6 +26,7 @@ type EditProfileFlowProps = {
|
|
|
24
26
|
onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
|
|
25
27
|
onEnsSetup: (setup: EnsSetupPlan) => void
|
|
26
28
|
onManageOperatorWalletAccess: () => void
|
|
29
|
+
onWithdrawToken: () => void
|
|
27
30
|
onBack: () => void
|
|
28
31
|
onMenu: () => void
|
|
29
32
|
}
|
|
@@ -35,6 +38,7 @@ const EDIT_DESCRIPTION_FOOTER = 'enter next · shift+enter newline · esc back'
|
|
|
35
38
|
|
|
36
39
|
export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
|
|
37
40
|
step,
|
|
41
|
+
reconciliation,
|
|
38
42
|
onNameSubmit,
|
|
39
43
|
onDescriptionSubmit,
|
|
40
44
|
onIconSubmit,
|
|
@@ -45,6 +49,7 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
|
|
|
45
49
|
onEnsRecordsUpdate,
|
|
46
50
|
onEnsSetup,
|
|
47
51
|
onManageOperatorWalletAccess,
|
|
52
|
+
onWithdrawToken,
|
|
48
53
|
onBack,
|
|
49
54
|
onMenu,
|
|
50
55
|
}) => {
|
|
@@ -90,11 +95,13 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
|
|
|
90
95
|
<EnsEditFlow
|
|
91
96
|
identity={step.identity}
|
|
92
97
|
registry={step.registry}
|
|
98
|
+
reconciliation={reconciliation}
|
|
93
99
|
onEnsLink={onEnsLink}
|
|
94
100
|
onEnsUnlink={onEnsUnlink}
|
|
95
101
|
onEnsRecordsUpdate={onEnsRecordsUpdate}
|
|
96
102
|
onEnsSetup={onEnsSetup}
|
|
97
103
|
onManageOperatorWalletAccess={onManageOperatorWalletAccess}
|
|
104
|
+
onWithdrawToken={onWithdrawToken}
|
|
98
105
|
initialView={step.initialView}
|
|
99
106
|
onBack={onBack}
|
|
100
107
|
/>
|
|
@@ -108,8 +108,8 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
108
108
|
<Select<'ens' | 'token-id' | 'back'>
|
|
109
109
|
options={[
|
|
110
110
|
{ value: 'ens', role: 'section', label: 'Recovery Key' },
|
|
111
|
-
{ value: 'ens', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain
|
|
112
|
-
{ value: 'token-id', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID
|
|
111
|
+
{ value: 'ens', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain' },
|
|
112
|
+
{ value: 'token-id', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID' },
|
|
113
113
|
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
114
114
|
{ value: 'back', label: 'Back', hint: 'Pick a different network', role: 'utility' },
|
|
115
115
|
]}
|
|
@@ -146,7 +146,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
146
146
|
subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
|
|
147
147
|
footer={footerHint(step.busy ? 'Looking up...' : 'enter continue · esc back')}
|
|
148
148
|
>
|
|
149
|
-
<Text color={theme.dim}>The integer token ID assigned at mint
|
|
149
|
+
<Text color={theme.dim}>The integer token ID assigned at mint.</Text>
|
|
150
150
|
<TextInput
|
|
151
151
|
placeholder="45744"
|
|
152
152
|
onSubmit={value => onTokenIdSubmit(value.trim())}
|
|
@@ -202,8 +202,8 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
202
202
|
}
|
|
203
203
|
}),
|
|
204
204
|
{ value: '__ens__', role: 'section', label: 'Recovery Key' },
|
|
205
|
-
{ value: '__ens__', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain
|
|
206
|
-
{ value: '__token-id__', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID
|
|
205
|
+
{ value: '__ens__', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain' },
|
|
206
|
+
{ value: '__token-id__', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID' },
|
|
207
207
|
{ value: '__back__', role: 'section', label: 'Navigation' },
|
|
208
208
|
{ value: '__back__', label: 'Back', hint: 'Return to the previous step', role: 'utility' },
|
|
209
209
|
]}
|