ethagent 1.1.2 → 2.0.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/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { openImageFilePicker } from '../../../profile/imagePicker.js'
|
|
3
|
+
import { readOwnerAddressField } from '../../../identityCompat.js'
|
|
4
|
+
import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
|
|
5
|
+
import type { ProfileUpdates, Step } from '../../identityHubReducer.js'
|
|
6
|
+
import { readCustodyMode } from '../../model/custody.js'
|
|
7
|
+
import { OperatorWalletsScreen } from './OperatorWalletsScreen.js'
|
|
8
|
+
import { EDIT_PROFILE_STEPS, EditProfileFlow } from '../profile/EditProfileFlow.js'
|
|
9
|
+
import { FlowTimeline } from '../../components/FlowTimeline.js'
|
|
10
|
+
import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
|
|
11
|
+
|
|
12
|
+
type StepOf<K extends Step['kind']> = Extract<Step, { kind: K }>
|
|
13
|
+
|
|
14
|
+
type IdentityHubEnsStep = StepOf<
|
|
15
|
+
| 'manage-ens-operators'
|
|
16
|
+
| 'edit-profile-name'
|
|
17
|
+
| 'edit-profile-description'
|
|
18
|
+
| 'edit-profile-image'
|
|
19
|
+
| 'edit-profile-review'
|
|
20
|
+
| 'edit-profile-ens'
|
|
21
|
+
| 'ens-records-tx'
|
|
22
|
+
| 'ens-setup-registry-tx'
|
|
23
|
+
| 'ens-setup-records-tx'
|
|
24
|
+
| 'public-profile-signing'
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
type IdentityHubEnsFlowProps = {
|
|
28
|
+
step: IdentityHubEnsStep
|
|
29
|
+
walletSession: BrowserWalletReady | null
|
|
30
|
+
onSetStep: (step: Step) => void
|
|
31
|
+
onBack: () => void
|
|
32
|
+
onWalletReady: (session: BrowserWalletReady | null) => void
|
|
33
|
+
onTriggerRebackup: (backStep: Step, profileUpdates?: ProfileUpdates) => void
|
|
34
|
+
onTriggerPublicProfileSave: (backStep: Step, profileUpdates: ProfileUpdates) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isIdentityHubEnsStep(step: Step): step is IdentityHubEnsStep {
|
|
38
|
+
return step.kind === 'manage-ens-operators'
|
|
39
|
+
|| step.kind === 'edit-profile-name'
|
|
40
|
+
|| step.kind === 'edit-profile-description'
|
|
41
|
+
|| step.kind === 'edit-profile-image'
|
|
42
|
+
|| step.kind === 'edit-profile-review'
|
|
43
|
+
|| step.kind === 'edit-profile-ens'
|
|
44
|
+
|| step.kind === 'ens-records-tx'
|
|
45
|
+
|| step.kind === 'ens-setup-registry-tx'
|
|
46
|
+
|| step.kind === 'ens-setup-records-tx'
|
|
47
|
+
|| step.kind === 'public-profile-signing'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const IdentityHubEnsFlow: React.FC<IdentityHubEnsFlowProps> = ({
|
|
51
|
+
step,
|
|
52
|
+
walletSession,
|
|
53
|
+
onSetStep,
|
|
54
|
+
onBack,
|
|
55
|
+
onWalletReady,
|
|
56
|
+
onTriggerRebackup,
|
|
57
|
+
onTriggerPublicProfileSave,
|
|
58
|
+
}) => {
|
|
59
|
+
if (step.kind === 'manage-ens-operators') {
|
|
60
|
+
return (
|
|
61
|
+
<OperatorWalletsScreen
|
|
62
|
+
identity={step.identity}
|
|
63
|
+
registry={step.registry}
|
|
64
|
+
walletSession={walletSession}
|
|
65
|
+
notice={step.notice}
|
|
66
|
+
error={step.error}
|
|
67
|
+
onSave={updates => onTriggerRebackup(step.returnTo ?? { kind: 'menu' }, updates)}
|
|
68
|
+
onWalletReady={onWalletReady}
|
|
69
|
+
onBack={onBack}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isEditProfileStep(step)) {
|
|
75
|
+
return (
|
|
76
|
+
<EditProfileFlow
|
|
77
|
+
step={step}
|
|
78
|
+
onNameSubmit={name => {
|
|
79
|
+
if (step.kind !== 'edit-profile-name') return
|
|
80
|
+
onSetStep({
|
|
81
|
+
kind: 'edit-profile-description',
|
|
82
|
+
identity: step.identity,
|
|
83
|
+
registry: step.registry,
|
|
84
|
+
name,
|
|
85
|
+
description: step.description,
|
|
86
|
+
imagePath: step.imagePath,
|
|
87
|
+
returnTo: step.returnTo,
|
|
88
|
+
})
|
|
89
|
+
}}
|
|
90
|
+
onDescriptionSubmit={description => {
|
|
91
|
+
if (step.kind !== 'edit-profile-description') return
|
|
92
|
+
onSetStep({ kind: 'edit-profile-image', identity: step.identity, registry: step.registry, name: step.name, description, imagePath: step.imagePath, returnTo: step.returnTo })
|
|
93
|
+
}}
|
|
94
|
+
onIconSubmit={iconPath => {
|
|
95
|
+
if (step.kind !== 'edit-profile-image') return
|
|
96
|
+
onSetStep({ kind: 'edit-profile-review', identity: step.identity, registry: step.registry, name: step.name, description: step.description, ...(iconPath !== undefined ? { imagePath: iconPath } : {}), returnTo: step.returnTo })
|
|
97
|
+
}}
|
|
98
|
+
onIconPick={() => {
|
|
99
|
+
if (step.kind !== 'edit-profile-image') return
|
|
100
|
+
const iconStep = step
|
|
101
|
+
void openImageFilePicker()
|
|
102
|
+
.then(result => {
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
onSetStep({ ...iconStep, error: result.cancelled ? 'icon selection cancelled.' : `${result.error}` })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
onSetStep({
|
|
108
|
+
kind: 'edit-profile-review',
|
|
109
|
+
identity: iconStep.identity,
|
|
110
|
+
registry: iconStep.registry,
|
|
111
|
+
name: iconStep.name,
|
|
112
|
+
description: iconStep.description,
|
|
113
|
+
imagePath: result.file,
|
|
114
|
+
returnTo: iconStep.returnTo,
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
.catch((err: unknown) => {
|
|
118
|
+
onSetStep({ ...iconStep, error: `${(err as Error).message}` })
|
|
119
|
+
})
|
|
120
|
+
}}
|
|
121
|
+
onReviewSave={() => {
|
|
122
|
+
if (step.kind !== 'edit-profile-review') return
|
|
123
|
+
const updates: ProfileUpdates = {
|
|
124
|
+
name: step.name,
|
|
125
|
+
description: step.description,
|
|
126
|
+
...(step.imagePath !== undefined ? { imagePath: step.imagePath } : {}),
|
|
127
|
+
}
|
|
128
|
+
onTriggerPublicProfileSave(step.returnTo ?? { kind: 'continuity-public' }, updates)
|
|
129
|
+
}}
|
|
130
|
+
onEnsLink={(fullName, options) => {
|
|
131
|
+
if (step.kind !== 'edit-profile-ens') return
|
|
132
|
+
const state = (step.identity.state ?? {}) as Record<string, unknown>
|
|
133
|
+
const savedOwnerAddress = readOwnerAddressField(state) ?? ''
|
|
134
|
+
const updates: ProfileUpdates = {
|
|
135
|
+
ensName: fullName,
|
|
136
|
+
...(options.mode === 'advanced' && options.ownerAddress && !savedOwnerAddress ? { ownerAddress: options.ownerAddress } : {}),
|
|
137
|
+
...(options.mode === 'advanced' && options.operatorWallet ? {
|
|
138
|
+
approvedOperatorWallets: [options.operatorWallet],
|
|
139
|
+
activeOperatorAddress: options.operatorWallet,
|
|
140
|
+
} : {}),
|
|
141
|
+
}
|
|
142
|
+
onTriggerRebackup(step.returnTo ?? { kind: 'menu' }, updates)
|
|
143
|
+
}}
|
|
144
|
+
onEnsUnlink={() => {
|
|
145
|
+
if (step.kind !== 'edit-profile-ens') return
|
|
146
|
+
onTriggerRebackup(step.returnTo ?? { kind: 'menu' }, { ensName: '' })
|
|
147
|
+
}}
|
|
148
|
+
onEnsRecordsUpdate={(fullName, records, options, clearRecords, currentRecords) => {
|
|
149
|
+
if (step.kind !== 'edit-profile-ens') return
|
|
150
|
+
onSetStep({
|
|
151
|
+
kind: 'ens-records-tx',
|
|
152
|
+
identity: step.identity,
|
|
153
|
+
registry: step.registry,
|
|
154
|
+
fullName,
|
|
155
|
+
records,
|
|
156
|
+
...(currentRecords ? { currentRecords } : {}),
|
|
157
|
+
...(clearRecords ? { clearRecords: true } : {}),
|
|
158
|
+
...(options.mode === 'advanced' && options.ownerAddress ? { ownerAddress: options.ownerAddress } : {}),
|
|
159
|
+
returnTo: step.returnTo ?? { kind: 'menu' },
|
|
160
|
+
})
|
|
161
|
+
}}
|
|
162
|
+
onEnsSetup={setup => {
|
|
163
|
+
if (step.kind !== 'edit-profile-ens') return
|
|
164
|
+
if (setup.registryAction === 'none') {
|
|
165
|
+
onSetStep({
|
|
166
|
+
kind: 'ens-setup-records-tx',
|
|
167
|
+
identity: step.identity,
|
|
168
|
+
registry: step.registry,
|
|
169
|
+
setup,
|
|
170
|
+
returnTo: step.returnTo ?? { kind: 'menu' },
|
|
171
|
+
})
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
onSetStep({
|
|
175
|
+
kind: 'ens-setup-registry-tx',
|
|
176
|
+
identity: step.identity,
|
|
177
|
+
registry: step.registry,
|
|
178
|
+
setup,
|
|
179
|
+
returnTo: step.returnTo ?? { kind: 'menu' },
|
|
180
|
+
})
|
|
181
|
+
}}
|
|
182
|
+
onManageOperatorWalletAccess={() => {
|
|
183
|
+
if (step.kind !== 'edit-profile-ens') return
|
|
184
|
+
onSetStep({
|
|
185
|
+
kind: 'manage-ens-operators',
|
|
186
|
+
identity: step.identity,
|
|
187
|
+
registry: step.registry,
|
|
188
|
+
returnTo: { kind: 'edit-profile-ens', identity: step.identity, registry: step.registry, returnTo: step.returnTo, initialView: 'advanced' },
|
|
189
|
+
})
|
|
190
|
+
}}
|
|
191
|
+
onBack={onBack}
|
|
192
|
+
onMenu={() => onSetStep(step.returnTo ?? { kind: 'continuity-public' })}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (step.kind === 'ens-records-tx') {
|
|
198
|
+
return (
|
|
199
|
+
<WalletApprovalScreen
|
|
200
|
+
title={step.clearRecords ? 'Unlink ENS' : 'Update ENS Records'}
|
|
201
|
+
subtitle={step.clearRecords
|
|
202
|
+
? `Ethereum Mainnet: sign one transaction to clear ethagent record values on ${step.fullName}. Requires gas.`
|
|
203
|
+
: `Ethereum Mainnet: sign one transaction to set ENS records on ${step.fullName}. Requires gas.`}
|
|
204
|
+
walletSession={walletSession}
|
|
205
|
+
label="waiting for wallet transaction..."
|
|
206
|
+
onCancel={() => onSetStep({ kind: 'edit-profile-ens', identity: step.identity, registry: step.registry, returnTo: step.returnTo })}
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (step.kind === 'ens-setup-registry-tx') {
|
|
212
|
+
const signer = step.setup.mode === 'simple' ? 'Connected wallet' : 'Owner wallet'
|
|
213
|
+
return (
|
|
214
|
+
<WalletApprovalScreen
|
|
215
|
+
title={step.setup.mode === 'simple' ? 'Use Connected Wallet' : 'Use Owner Wallet'}
|
|
216
|
+
subtitle={`${signer} signs one Ethereum Mainnet ENS registry transaction for ${step.setup.fullName}.`}
|
|
217
|
+
walletSession={walletSession}
|
|
218
|
+
label={step.setup.mode === 'simple' ? 'waiting for connected wallet transaction...' : 'waiting for owner wallet transaction...'}
|
|
219
|
+
onCancel={() => onSetStep({
|
|
220
|
+
kind: 'edit-profile-ens',
|
|
221
|
+
identity: step.identity,
|
|
222
|
+
registry: step.registry,
|
|
223
|
+
returnTo: step.returnTo,
|
|
224
|
+
...(step.setup.mode === 'advanced' ? { initialView: 'advanced' as const } : {}),
|
|
225
|
+
})}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (step.kind === 'ens-setup-records-tx') {
|
|
231
|
+
const signer = step.setup.mode === 'simple' ? 'Connected wallet' : 'Owner wallet'
|
|
232
|
+
return (
|
|
233
|
+
<WalletApprovalScreen
|
|
234
|
+
title={step.setup.mode === 'simple' ? 'Use Connected Wallet' : 'Use Owner Wallet'}
|
|
235
|
+
subtitle={`${signer} signs one Ethereum Mainnet resolver transaction for ${step.setup.fullName}.`}
|
|
236
|
+
walletSession={walletSession}
|
|
237
|
+
label={step.setup.mode === 'simple' ? 'waiting for connected wallet transaction...' : 'waiting for owner wallet transaction...'}
|
|
238
|
+
onCancel={() => onSetStep({
|
|
239
|
+
kind: 'edit-profile-ens',
|
|
240
|
+
identity: step.identity,
|
|
241
|
+
registry: step.registry,
|
|
242
|
+
returnTo: step.returnTo,
|
|
243
|
+
...(step.setup.mode === 'advanced' ? { initialView: 'advanced' as const } : {}),
|
|
244
|
+
})}
|
|
245
|
+
/>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const approval = publicProfileWalletApprovalView(step)
|
|
250
|
+
return (
|
|
251
|
+
<WalletApprovalScreen
|
|
252
|
+
title={approval.title}
|
|
253
|
+
subtitle={approval.subtitle}
|
|
254
|
+
walletSession={walletSession}
|
|
255
|
+
label={approval.label}
|
|
256
|
+
onCancel={() => onSetStep(step.returnTo ?? { kind: 'continuity-public' })}
|
|
257
|
+
/>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function publicProfileWalletApprovalView(step: StepOf<'public-profile-signing'>): {
|
|
262
|
+
title: string
|
|
263
|
+
subtitle: React.ReactNode
|
|
264
|
+
label: string
|
|
265
|
+
} {
|
|
266
|
+
if (usesAdvancedSetup(step)) {
|
|
267
|
+
return {
|
|
268
|
+
title: 'Use Wallet',
|
|
269
|
+
subtitle: 'Sign the public profile and ERC-8004 token URI update.',
|
|
270
|
+
label: 'waiting for wallet signature...',
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
title: 'Use Wallet',
|
|
275
|
+
subtitle: <FlowTimeline steps={EDIT_PROFILE_STEPS} current={5} />,
|
|
276
|
+
label: 'waiting for wallet signature...',
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function usesAdvancedSetup(step: StepOf<'public-profile-signing'>): boolean {
|
|
281
|
+
const state = (step.identity.state ?? {}) as Record<string, unknown>
|
|
282
|
+
const custodyMode = step.profileUpdates?.custodyMode ?? readCustodyMode(state)
|
|
283
|
+
const ownerAddress = step.profileUpdates?.ownerAddress ?? readOwnerAddressField(state)
|
|
284
|
+
return custodyMode === 'advanced' && typeof ownerAddress === 'string' && ownerAddress.trim().length > 0
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isEditProfileStep(step: IdentityHubEnsStep): step is StepOf<
|
|
288
|
+
| 'edit-profile-name'
|
|
289
|
+
| 'edit-profile-description'
|
|
290
|
+
| 'edit-profile-image'
|
|
291
|
+
| 'edit-profile-review'
|
|
292
|
+
| 'edit-profile-ens'
|
|
293
|
+
> {
|
|
294
|
+
return step.kind === 'edit-profile-name'
|
|
295
|
+
|| step.kind === 'edit-profile-description'
|
|
296
|
+
|| step.kind === 'edit-profile-image'
|
|
297
|
+
|| step.kind === 'edit-profile-review'
|
|
298
|
+
|| step.kind === 'edit-profile-ens'
|
|
299
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Box, Text } from 'ink'
|
|
3
|
+
import { getAddress, isAddress, type Address } from 'viem'
|
|
4
|
+
import { Surface } from '../../../../ui/Surface.js'
|
|
5
|
+
import { Select, type SelectOption } from '../../../../ui/Select.js'
|
|
6
|
+
import { Spinner } from '../../../../ui/Spinner.js'
|
|
7
|
+
import { theme } from '../../../../ui/theme.js'
|
|
8
|
+
import { useAppInput } from '../../../../app/input/AppInputProvider.js'
|
|
9
|
+
import { openExternalUrl } from '../../../../utils/openExternal.js'
|
|
10
|
+
import type { EthagentIdentity } from '../../../../storage/config.js'
|
|
11
|
+
import { readOwnerAddressField } from '../../../identityCompat.js'
|
|
12
|
+
import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
|
|
13
|
+
import {
|
|
14
|
+
createWalletRestoreAccessChallenge,
|
|
15
|
+
createWalletRestoreAccessKey,
|
|
16
|
+
} from '../../../continuity/envelope.js'
|
|
17
|
+
import { requestBrowserWalletSignature, type BrowserWalletReady } from '../../../wallet/browserWallet.js'
|
|
18
|
+
import { FlowTimeline } from '../../components/FlowTimeline.js'
|
|
19
|
+
import { OPEN_BROWSER_HINT } from '../../components/WalletApprovalScreen.js'
|
|
20
|
+
import { readCustodyMode } from '../../model/custody.js'
|
|
21
|
+
import { shortAddress } from '../../model/format.js'
|
|
22
|
+
import type { ProfileUpdates } from '../../identityHubReducer.js'
|
|
23
|
+
import {
|
|
24
|
+
normalizeApprovedOperatorWallets,
|
|
25
|
+
removeApprovedOperatorWallet,
|
|
26
|
+
upsertApprovedOperatorWallet,
|
|
27
|
+
type ApprovedOperatorWalletRecord,
|
|
28
|
+
} from '../../operatorWallets.js'
|
|
29
|
+
import {
|
|
30
|
+
reconcileWalletSetup,
|
|
31
|
+
type RecordsFixPlan,
|
|
32
|
+
} from '../../reconciliation/index.js'
|
|
33
|
+
|
|
34
|
+
type OperatorPhase =
|
|
35
|
+
| { kind: 'main'; notice?: string; error?: string }
|
|
36
|
+
| { kind: 'signing' }
|
|
37
|
+
|
|
38
|
+
type OperatorAction =
|
|
39
|
+
| 'add-browser'
|
|
40
|
+
| 'back'
|
|
41
|
+
| 'remove-all'
|
|
42
|
+
| `remove:${string}`
|
|
43
|
+
| `activate:${string}`
|
|
44
|
+
|
|
45
|
+
type OperatorWalletsScreenProps = {
|
|
46
|
+
identity: EthagentIdentity
|
|
47
|
+
registry: Erc8004RegistryConfig
|
|
48
|
+
walletSession: BrowserWalletReady | null
|
|
49
|
+
notice?: string
|
|
50
|
+
error?: string
|
|
51
|
+
onSave: (updates: ProfileUpdates) => void
|
|
52
|
+
onWalletReady: (session: BrowserWalletReady | null) => void
|
|
53
|
+
onBack: () => void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const OperatorWalletsScreen: React.FC<OperatorWalletsScreenProps> = ({
|
|
57
|
+
identity,
|
|
58
|
+
registry,
|
|
59
|
+
walletSession,
|
|
60
|
+
notice,
|
|
61
|
+
error,
|
|
62
|
+
onSave,
|
|
63
|
+
onWalletReady,
|
|
64
|
+
onBack,
|
|
65
|
+
}) => {
|
|
66
|
+
const state = (identity.state ?? {}) as Record<string, unknown>
|
|
67
|
+
const custodyMode = readCustodyMode(state)
|
|
68
|
+
const ownerAddressRaw = readOwnerAddressField(state)
|
|
69
|
+
const ownerAddress = ownerAddressRaw && isAddress(ownerAddressRaw, { strict: false }) ? getAddress(ownerAddressRaw) : undefined
|
|
70
|
+
const activeOperatorAddress = readStateAddress(state, 'activeOperatorAddress')
|
|
71
|
+
const restoreAccessEpoch = readStateNumber(state, 'restoreAccessEpoch') ?? 0
|
|
72
|
+
const records = normalizeApprovedOperatorWallets(state.approvedOperatorWallets)
|
|
73
|
+
const [phase, setPhase] = React.useState<OperatorPhase>({ kind: 'main', notice, error })
|
|
74
|
+
const [fixPlan, setFixPlan] = React.useState<RecordsFixPlan | null>(null)
|
|
75
|
+
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
setPhase(current => current.kind === 'main' ? { kind: 'main', notice, error } : current)
|
|
78
|
+
}, [notice, error])
|
|
79
|
+
|
|
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
|
+
const saveOperators = React.useCallback((
|
|
97
|
+
approvedOperatorWallets: ApprovedOperatorWalletRecord[],
|
|
98
|
+
activeOperator: Address | '' | undefined,
|
|
99
|
+
) => {
|
|
100
|
+
const updates: ProfileUpdates = {
|
|
101
|
+
custodyMode: 'advanced',
|
|
102
|
+
...(ownerAddress ? { ownerAddress } : {}),
|
|
103
|
+
approvedOperatorWallets,
|
|
104
|
+
restoreAccessEpoch: restoreAccessEpoch + 1,
|
|
105
|
+
}
|
|
106
|
+
if (activeOperator !== undefined) updates.activeOperatorAddress = activeOperator
|
|
107
|
+
onSave(updates)
|
|
108
|
+
}, [ownerAddress, onSave, restoreAccessEpoch])
|
|
109
|
+
|
|
110
|
+
const addRecord = React.useCallback((record: ApprovedOperatorWalletRecord) => {
|
|
111
|
+
if (!ownerAddress) {
|
|
112
|
+
setPhase({ kind: 'main', error: 'advanced custody needs an owner wallet before managing operator wallets' })
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (record.address.toLowerCase() === ownerAddress.toLowerCase()) {
|
|
116
|
+
setPhase({ kind: 'main', error: 'operator wallet must differ from the owner wallet' })
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
const next = upsertApprovedOperatorWallet(records, record)
|
|
120
|
+
const active = activeOperatorAddress ?? record.address
|
|
121
|
+
saveOperators(next, active)
|
|
122
|
+
}, [activeOperatorAddress, ownerAddress, records, saveOperators])
|
|
123
|
+
|
|
124
|
+
const startBrowserSignature = React.useCallback(() => {
|
|
125
|
+
if (!ownerAddress) {
|
|
126
|
+
setPhase({ kind: 'main', error: 'advanced custody needs an owner wallet before managing operator wallets' })
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (!identity.agentId) {
|
|
130
|
+
setPhase({ kind: 'main', error: 'agent token ID is required before authorizing a wallet' })
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const token = restoreAccessToken(registry, identity.agentId)
|
|
134
|
+
const nextEpoch = restoreAccessEpoch + 1
|
|
135
|
+
setPhase({ kind: 'signing' })
|
|
136
|
+
requestBrowserWalletSignature({
|
|
137
|
+
chainId: registry.chainId,
|
|
138
|
+
purpose: 'operator-proof',
|
|
139
|
+
messageForAccount: account => createWalletRestoreAccessChallenge({
|
|
140
|
+
token,
|
|
141
|
+
ownerAddress: ownerAddress,
|
|
142
|
+
walletAddress: account,
|
|
143
|
+
accessEpoch: nextEpoch,
|
|
144
|
+
purpose: 'restore-operator',
|
|
145
|
+
}),
|
|
146
|
+
onReady: onWalletReady,
|
|
147
|
+
}).then(wallet => {
|
|
148
|
+
onWalletReady(null)
|
|
149
|
+
const restoreAccessKey = createWalletRestoreAccessKey({
|
|
150
|
+
token,
|
|
151
|
+
ownerAddress: ownerAddress,
|
|
152
|
+
walletAddress: wallet.account,
|
|
153
|
+
walletSignature: wallet.signature,
|
|
154
|
+
accessEpoch: nextEpoch,
|
|
155
|
+
createdAt: new Date().toISOString(),
|
|
156
|
+
purpose: 'restore-operator',
|
|
157
|
+
})
|
|
158
|
+
addRecord({
|
|
159
|
+
address: wallet.account,
|
|
160
|
+
challenge: wallet.message,
|
|
161
|
+
verifiedAt: restoreAccessKey.createdAt,
|
|
162
|
+
restoreAccessKey,
|
|
163
|
+
})
|
|
164
|
+
}).catch((err: unknown) => {
|
|
165
|
+
onWalletReady(null)
|
|
166
|
+
setPhase({ kind: 'main', error: err instanceof Error ? err.message : String(err) })
|
|
167
|
+
})
|
|
168
|
+
}, [addRecord, ownerAddress, identity.agentId, onWalletReady, registry, restoreAccessEpoch])
|
|
169
|
+
|
|
170
|
+
if (custodyMode !== 'advanced' || !ownerAddress) {
|
|
171
|
+
return (
|
|
172
|
+
<Surface
|
|
173
|
+
title="Operator Wallets"
|
|
174
|
+
subtitle="Advanced custody must be set up before operator wallets can be managed."
|
|
175
|
+
footer={footerHint('enter select · esc back')}
|
|
176
|
+
>
|
|
177
|
+
<Box flexDirection="column">
|
|
178
|
+
<Text color={theme.dim}>Switch to Advanced custody from the Custody Mode menu to capture an owner wallet.</Text>
|
|
179
|
+
<Text color={theme.dim}>Operator wallets can be changed later and never receive token control.</Text>
|
|
180
|
+
{phase.kind === 'main' && phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
|
|
181
|
+
</Box>
|
|
182
|
+
<Box marginTop={1}>
|
|
183
|
+
<Select<'back'>
|
|
184
|
+
options={[
|
|
185
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
186
|
+
{ value: 'back', label: 'Back', hint: 'Return to the previous screen', role: 'utility' },
|
|
187
|
+
]}
|
|
188
|
+
hintLayout="inline"
|
|
189
|
+
onSubmit={() => onBack()}
|
|
190
|
+
onCancel={onBack}
|
|
191
|
+
/>
|
|
192
|
+
</Box>
|
|
193
|
+
</Surface>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (phase.kind === 'signing') {
|
|
198
|
+
return (
|
|
199
|
+
<WalletWaitSurface
|
|
200
|
+
title="Authorize Wallet"
|
|
201
|
+
subtitle={
|
|
202
|
+
<Box flexDirection="column">
|
|
203
|
+
<FlowTimeline steps={['Verify Operator', 'Sign Backup', 'Publish Snapshot', 'Approve ENS', 'Approve Vault']} current={1} />
|
|
204
|
+
<Text color={theme.dim}>Operator wallet signs restore access. Owner wallet then re-attests the backup, publishes the new snapshot, and authorizes the operator onchain (ENS + vault).</Text>
|
|
205
|
+
</Box>
|
|
206
|
+
}
|
|
207
|
+
walletSession={walletSession}
|
|
208
|
+
onCancel={() => {
|
|
209
|
+
onWalletReady(null)
|
|
210
|
+
setPhase({ kind: 'main' })
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const options = operatorOptions({
|
|
217
|
+
records,
|
|
218
|
+
activeOperatorAddress,
|
|
219
|
+
})
|
|
220
|
+
const phaseNotice = phase.kind === 'main' ? phase.notice : undefined
|
|
221
|
+
const phaseError = phase.kind === 'main' ? phase.error : undefined
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Surface
|
|
225
|
+
title="Operator Wallets"
|
|
226
|
+
subtitle="Owner wallet controls this list. The active operator wallet signs frequent updates; others stay authorized."
|
|
227
|
+
footer={footerHint('enter select · esc back')}
|
|
228
|
+
>
|
|
229
|
+
<Box flexDirection="column">
|
|
230
|
+
<Text>
|
|
231
|
+
<Text color={theme.dim}>{'Owner Wallet'.padEnd(18)}</Text>
|
|
232
|
+
<Text color={theme.text}>{shortAddress(ownerAddress)}</Text>
|
|
233
|
+
</Text>
|
|
234
|
+
<Box marginTop={1} flexDirection="column">
|
|
235
|
+
<Text color={theme.dim}>Operator Wallets</Text>
|
|
236
|
+
{records.length > 0
|
|
237
|
+
? records.map(record => {
|
|
238
|
+
const isActive = record.address.toLowerCase() === activeOperatorAddress?.toLowerCase()
|
|
239
|
+
const approved = record.verifiedAt ? `approved ${record.verifiedAt.slice(0, 10)}` : null
|
|
240
|
+
const meta = [isActive ? 'active' : null, approved].filter(Boolean).join(' · ')
|
|
241
|
+
return (
|
|
242
|
+
<Text key={record.address}>
|
|
243
|
+
<Text color={isActive ? theme.accentPeriwinkle : theme.text}>
|
|
244
|
+
{shortAddress(record.address)}
|
|
245
|
+
</Text>
|
|
246
|
+
{meta ? <Text color={theme.dim}>{` ${meta}`}</Text> : null}
|
|
247
|
+
</Text>
|
|
248
|
+
)
|
|
249
|
+
})
|
|
250
|
+
: <Text color={theme.dim}>No operator wallets saved.</Text>}
|
|
251
|
+
</Box>
|
|
252
|
+
<Box marginTop={1} flexDirection="column">
|
|
253
|
+
<Text color={theme.dim}>Add as many operator wallets as needed; unlink any saved wallet here.</Text>
|
|
254
|
+
<Text color={theme.dim}>No approve(), setApprovalForAll(), transferFrom(), or token approval is requested.</Text>
|
|
255
|
+
</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
|
+
{phaseNotice ? <Text color={theme.accentPeriwinkle}>{phaseNotice}</Text> : null}
|
|
267
|
+
{phaseError ? <Text color={theme.accentError}>{phaseError}</Text> : null}
|
|
268
|
+
</Box>
|
|
269
|
+
<Box marginTop={1}>
|
|
270
|
+
<Select<OperatorAction>
|
|
271
|
+
options={options}
|
|
272
|
+
hintLayout="inline"
|
|
273
|
+
maxVisible={10}
|
|
274
|
+
onSubmit={choice => {
|
|
275
|
+
if (choice === 'add-browser') return startBrowserSignature()
|
|
276
|
+
if (choice === 'back') return onBack()
|
|
277
|
+
if (choice === 'remove-all') {
|
|
278
|
+
try {
|
|
279
|
+
saveOperators([], '')
|
|
280
|
+
} catch (err: unknown) {
|
|
281
|
+
setPhase({ kind: 'main', error: err instanceof Error ? err.message : String(err) })
|
|
282
|
+
}
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
if (choice.startsWith('remove:')) {
|
|
286
|
+
try {
|
|
287
|
+
const address = getAddress(choice.slice('remove:'.length))
|
|
288
|
+
const next = removeApprovedOperatorWallet(records, address)
|
|
289
|
+
const removedActive = activeOperatorAddress?.toLowerCase() === address.toLowerCase()
|
|
290
|
+
const nextActive = removedActive ? '' : activeOperatorAddress ?? ''
|
|
291
|
+
saveOperators(next, nextActive)
|
|
292
|
+
} catch (err: unknown) {
|
|
293
|
+
setPhase({ kind: 'main', error: err instanceof Error ? err.message : String(err) })
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (choice.startsWith('activate:')) {
|
|
297
|
+
try {
|
|
298
|
+
const address = getAddress(choice.slice('activate:'.length))
|
|
299
|
+
saveOperators(records, address)
|
|
300
|
+
} catch (err: unknown) {
|
|
301
|
+
setPhase({ kind: 'main', error: err instanceof Error ? err.message : String(err) })
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}}
|
|
305
|
+
onCancel={onBack}
|
|
306
|
+
/>
|
|
307
|
+
</Box>
|
|
308
|
+
</Surface>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function operatorOptions(args: {
|
|
313
|
+
records: ApprovedOperatorWalletRecord[]
|
|
314
|
+
activeOperatorAddress: Address | undefined
|
|
315
|
+
}): Array<SelectOption<OperatorAction>> {
|
|
316
|
+
const options: Array<SelectOption<OperatorAction>> = [
|
|
317
|
+
{ value: 'add-browser', role: 'section', label: 'Operator Wallets' },
|
|
318
|
+
{ value: 'add-browser', label: 'Add Wallet', hint: 'Connect wallet to sign restore access' },
|
|
319
|
+
]
|
|
320
|
+
if (args.records[0]) options.push({ value: `remove:${args.records[0].address}`, role: 'section', label: 'Operator Wallets' })
|
|
321
|
+
for (const record of args.records) {
|
|
322
|
+
const active = args.activeOperatorAddress?.toLowerCase() === record.address.toLowerCase()
|
|
323
|
+
if (!active) {
|
|
324
|
+
options.push({
|
|
325
|
+
value: `activate:${record.address}`,
|
|
326
|
+
label: `Set Active: ${shortAddress(record.address)}`,
|
|
327
|
+
hint: 'Make this the active operator wallet (owner wallet signs)',
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
options.push({
|
|
331
|
+
value: `remove:${record.address}`,
|
|
332
|
+
label: `Unlink ${shortAddress(record.address)}${active ? ' (active)' : ''}`,
|
|
333
|
+
hint: active
|
|
334
|
+
? 'Active operator wallet. Unlinking clears the active slot; pick a new active from the list.'
|
|
335
|
+
: 'Remove this wallet from the approved list (owner wallet signs)',
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
if (args.records.length > 1) {
|
|
339
|
+
options.push({
|
|
340
|
+
value: 'remove-all',
|
|
341
|
+
label: 'Unlink All Operator Wallets',
|
|
342
|
+
hint: 'Remove every approved operator wallet (owner wallet signs)',
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
options.push(
|
|
346
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
347
|
+
{ value: 'back', label: 'Back', hint: 'Return to the previous screen', role: 'utility' },
|
|
348
|
+
)
|
|
349
|
+
return options
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const WalletWaitSurface: React.FC<{
|
|
353
|
+
title: string
|
|
354
|
+
subtitle: React.ReactNode
|
|
355
|
+
walletSession: BrowserWalletReady | null
|
|
356
|
+
onCancel: () => void
|
|
357
|
+
}> = ({ title, subtitle, walletSession, onCancel }) => {
|
|
358
|
+
useAppInput((_input, key) => {
|
|
359
|
+
if (key.escape) onCancel()
|
|
360
|
+
if (key.return && walletSession?.url) {
|
|
361
|
+
openExternalUrl(walletSession.url)
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
return (
|
|
365
|
+
<Surface title={title} subtitle={subtitle} footer={footerHint('esc cancels')}>
|
|
366
|
+
<Box marginTop={1}>
|
|
367
|
+
<Spinner label={walletSession ? 'waiting for wallet signature...' : 'opening wallet request...'} />
|
|
368
|
+
</Box>
|
|
369
|
+
{walletSession ? (
|
|
370
|
+
<Box flexDirection="column">
|
|
371
|
+
<Text color={theme.accentPeriwinkle} underline>{walletSession.url}</Text>
|
|
372
|
+
<Text color={theme.dim}>{OPEN_BROWSER_HINT}</Text>
|
|
373
|
+
</Box>
|
|
374
|
+
) : null}
|
|
375
|
+
</Surface>
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
|
|
380
|
+
|
|
381
|
+
function readStateNumber(state: Record<string, unknown>, key: string): number | undefined {
|
|
382
|
+
const value = state[key]
|
|
383
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function restoreAccessToken(registry: Erc8004RegistryConfig, agentId: string) {
|
|
387
|
+
return {
|
|
388
|
+
chainId: registry.chainId,
|
|
389
|
+
identityRegistryAddress: registry.identityRegistryAddress,
|
|
390
|
+
agentId,
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function readStateAddress(state: Record<string, unknown>, key: string): Address | undefined {
|
|
395
|
+
const value = state[key]
|
|
396
|
+
if (typeof value !== 'string' || !isAddress(value, { strict: false })) return undefined
|
|
397
|
+
return getAddress(value)
|
|
398
|
+
}
|