ethagent 1.1.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +124 -32
- package/package.json +8 -3
- 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,324 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text } from 'ink'
|
|
3
|
+
import { Surface } from '../../../../ui/Surface.js'
|
|
4
|
+
import { Select } from '../../../../ui/Select.js'
|
|
5
|
+
import { TextInput } from '../../../../ui/TextInput.js'
|
|
6
|
+
import { theme } from '../../../../ui/theme.js'
|
|
7
|
+
import { normalizeErc8004RegistryConfig } from '../../../registry/erc8004.js'
|
|
8
|
+
import {
|
|
9
|
+
isCurrentAgentCandidate,
|
|
10
|
+
tokenCandidateHint,
|
|
11
|
+
tokenCandidateSelectLabel,
|
|
12
|
+
} from '../../model/identity.js'
|
|
13
|
+
import { networkLabel } from '../../model/network.js'
|
|
14
|
+
import { shortAddress } from '../../model/format.js'
|
|
15
|
+
import { registryConfigFromConfig } from '../../../registry/registryConfig.js'
|
|
16
|
+
import type { Step } from '../../identityHubReducer.js'
|
|
17
|
+
import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
|
|
18
|
+
import { BusyScreen } from '../../components/BusyScreen.js'
|
|
19
|
+
import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
|
|
20
|
+
import type { EthagentConfig } from '../../../../storage/config.js'
|
|
21
|
+
import { restoreSignatureRequestForStep } from '../../effects/restore/index.js'
|
|
22
|
+
import type { RestoreProgress } from '../../effects/types.js'
|
|
23
|
+
|
|
24
|
+
type RestoreStep = Exclude<Extract<Step, { kind: `restore-${string}` }>, { kind: 'restore-wallet' | 'restore-network' }>
|
|
25
|
+
|
|
26
|
+
type RestoreFlowProps = {
|
|
27
|
+
step: RestoreStep
|
|
28
|
+
config?: EthagentConfig
|
|
29
|
+
walletSession: BrowserWalletReady | null
|
|
30
|
+
restoreProgress: RestoreProgress | null
|
|
31
|
+
onRestoreRegistrySubmit: (value: string) => void
|
|
32
|
+
onRetryDiscovery: () => void
|
|
33
|
+
onTokenSelect: (tokenId: string) => void
|
|
34
|
+
onEnsSubmit: (value: string) => void
|
|
35
|
+
onTokenIdSubmit: (value: string) => void
|
|
36
|
+
onPickRecoveryMethod: (choice: 'ens' | 'token-id') => void
|
|
37
|
+
onBack: () => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
|
|
41
|
+
|
|
42
|
+
export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
43
|
+
step,
|
|
44
|
+
config,
|
|
45
|
+
walletSession,
|
|
46
|
+
restoreProgress,
|
|
47
|
+
onRestoreRegistrySubmit,
|
|
48
|
+
onRetryDiscovery,
|
|
49
|
+
onTokenSelect,
|
|
50
|
+
onEnsSubmit,
|
|
51
|
+
onTokenIdSubmit,
|
|
52
|
+
onPickRecoveryMethod,
|
|
53
|
+
onBack,
|
|
54
|
+
}) => {
|
|
55
|
+
const purpose = 'purpose' in step ? step.purpose ?? 'restore' : 'restore'
|
|
56
|
+
const isSwitch = purpose === 'switch'
|
|
57
|
+
|
|
58
|
+
if (step.kind === 'restore-registry') {
|
|
59
|
+
const resolution = registryConfigFromConfig(config)
|
|
60
|
+
return (
|
|
61
|
+
<Surface
|
|
62
|
+
title={`${resolution.network ? networkLabel(resolution.network).charAt(0).toUpperCase() + networkLabel(resolution.network).slice(1) : ''} Agent Registry`}
|
|
63
|
+
subtitle={step.error ? `Lookup failed: ${step.error}` : 'Paste the agent registry address for this network.'}
|
|
64
|
+
footer={footerHint('enter discover · esc back')}
|
|
65
|
+
>
|
|
66
|
+
<Text color={theme.dim}>RPC defaults to {resolution.defaultRpcUrl}</Text>
|
|
67
|
+
<TextInput
|
|
68
|
+
initialValue={config?.erc8004?.identityRegistryAddress ?? ''}
|
|
69
|
+
placeholder="0x registry address"
|
|
70
|
+
validate={value => {
|
|
71
|
+
try {
|
|
72
|
+
normalizeErc8004RegistryConfig({
|
|
73
|
+
chainId: resolution.chainId,
|
|
74
|
+
rpcUrl: resolution.config?.rpcUrl ?? resolution.defaultRpcUrl,
|
|
75
|
+
identityRegistryAddress: value.trim(),
|
|
76
|
+
})
|
|
77
|
+
return null
|
|
78
|
+
} catch (err: unknown) {
|
|
79
|
+
return (err as Error).message
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
onSubmit={onRestoreRegistrySubmit}
|
|
83
|
+
onCancel={onBack}
|
|
84
|
+
/>
|
|
85
|
+
</Surface>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (step.kind === 'restore-discovering') {
|
|
90
|
+
return (
|
|
91
|
+
<BusyScreen
|
|
92
|
+
title={isSwitch ? 'Finding Agents' : 'Finding Agents'}
|
|
93
|
+
subtitle={step.ownerHandle}
|
|
94
|
+
label="checking tokens this wallet directly owns..."
|
|
95
|
+
onCancel={onBack}
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (step.kind === 'restore-recovery-input') {
|
|
101
|
+
return (
|
|
102
|
+
<Surface
|
|
103
|
+
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
104
|
+
subtitle="The connected wallet doesn't directly own an agent token on this network."
|
|
105
|
+
footer={footerHint('enter select · esc back')}
|
|
106
|
+
>
|
|
107
|
+
<Text color={theme.dim}>If this wallet is an approved operator wallet, choose how to find the agent token.</Text>
|
|
108
|
+
<Select<'ens' | 'token-id' | 'back'>
|
|
109
|
+
options={[
|
|
110
|
+
{ value: 'ens', role: 'section', label: 'Recovery Key' },
|
|
111
|
+
{ value: 'ens', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain (e.g. agent.example.eth)' },
|
|
112
|
+
{ value: 'token-id', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID (e.g. 45744)' },
|
|
113
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
114
|
+
{ value: 'back', label: 'Back', hint: 'Pick a different network', role: 'utility' },
|
|
115
|
+
]}
|
|
116
|
+
hintLayout="inline"
|
|
117
|
+
onSubmit={value => value === 'back' ? onBack() : onPickRecoveryMethod(value)}
|
|
118
|
+
onCancel={onBack}
|
|
119
|
+
/>
|
|
120
|
+
</Surface>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (step.kind === 'restore-ens-input') {
|
|
125
|
+
return (
|
|
126
|
+
<Surface
|
|
127
|
+
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
128
|
+
subtitle="Enter the agent's ENS name to decrypt with an authorized operator wallet."
|
|
129
|
+
footer={footerHint(step.busy ? 'Looking up...' : 'enter continue · esc back')}
|
|
130
|
+
>
|
|
131
|
+
<Text color={theme.dim}>The full agent subdomain, e.g. agent.example.eth.</Text>
|
|
132
|
+
<TextInput
|
|
133
|
+
placeholder="agent.example.eth"
|
|
134
|
+
onSubmit={value => onEnsSubmit(value.trim())}
|
|
135
|
+
onCancel={onBack}
|
|
136
|
+
/>
|
|
137
|
+
{step.error ? <Text color={theme.accentError}>{step.error}</Text> : null}
|
|
138
|
+
</Surface>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (step.kind === 'restore-token-id-input') {
|
|
143
|
+
return (
|
|
144
|
+
<Surface
|
|
145
|
+
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
146
|
+
subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
|
|
147
|
+
footer={footerHint(step.busy ? 'Looking up...' : 'enter continue · esc back')}
|
|
148
|
+
>
|
|
149
|
+
<Text color={theme.dim}>The integer token ID assigned at mint (for example, 45744). Store it alongside your wallet seed so the agent stays recoverable even if its ENS record is cleared.</Text>
|
|
150
|
+
<TextInput
|
|
151
|
+
placeholder="45744"
|
|
152
|
+
onSubmit={value => onTokenIdSubmit(value.trim())}
|
|
153
|
+
onCancel={onBack}
|
|
154
|
+
/>
|
|
155
|
+
{step.error ? <Text color={theme.accentError}>{step.error}</Text> : null}
|
|
156
|
+
</Surface>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (step.kind === 'restore-not-found') {
|
|
161
|
+
const view = restoreNotFoundView(step)
|
|
162
|
+
return (
|
|
163
|
+
<Surface
|
|
164
|
+
title={view.title}
|
|
165
|
+
subtitle={view.subtitle}
|
|
166
|
+
footer={footerHint('enter continue · esc back')}
|
|
167
|
+
>
|
|
168
|
+
<Text color={theme.dim}>{view.detail}</Text>
|
|
169
|
+
<Select<'retry' | 'network'>
|
|
170
|
+
options={[
|
|
171
|
+
{ value: 'retry', role: 'section', label: 'Next' },
|
|
172
|
+
{ value: 'retry', label: 'Retry Search', hint: 'Search token ownership and ERC-8004 restore access again' },
|
|
173
|
+
{ value: 'network', label: 'Choose Network', hint: 'Search a different ERC-8004 network' },
|
|
174
|
+
]}
|
|
175
|
+
hintLayout="inline"
|
|
176
|
+
onSubmit={choice => {
|
|
177
|
+
if (choice === 'retry') onRetryDiscovery()
|
|
178
|
+
else onBack()
|
|
179
|
+
}}
|
|
180
|
+
onCancel={onBack}
|
|
181
|
+
/>
|
|
182
|
+
</Surface>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (step.kind === 'restore-select-token') {
|
|
187
|
+
return (
|
|
188
|
+
<Surface
|
|
189
|
+
title={isSwitch ? 'Load an Agent' : 'Choose Your Agent'}
|
|
190
|
+
subtitle={step.ownerHandle}
|
|
191
|
+
footer={footerHint('enter select · esc back')}
|
|
192
|
+
>
|
|
193
|
+
<Select<string>
|
|
194
|
+
options={[
|
|
195
|
+
{ value: 'section:available-agents', role: 'section', label: 'Available Agents' },
|
|
196
|
+
...step.candidates.map(candidate => {
|
|
197
|
+
const current = isSwitch && isCurrentAgentCandidate(config?.identity, candidate)
|
|
198
|
+
return {
|
|
199
|
+
value: candidate.agentId.toString(),
|
|
200
|
+
label: tokenCandidateSelectLabel(candidate, current),
|
|
201
|
+
hint: tokenCandidateHint(candidate),
|
|
202
|
+
}
|
|
203
|
+
}),
|
|
204
|
+
{ value: '__ens__', role: 'section', label: 'Recovery Key' },
|
|
205
|
+
{ value: '__ens__', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain (e.g. agent.example.eth)' },
|
|
206
|
+
{ value: '__token-id__', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID (e.g. 45744)' },
|
|
207
|
+
{ value: '__back__', role: 'section', label: 'Navigation' },
|
|
208
|
+
{ value: '__back__', label: 'Back', hint: 'Return to the previous step', role: 'utility' },
|
|
209
|
+
]}
|
|
210
|
+
hintLayout="inline"
|
|
211
|
+
onSubmit={value => {
|
|
212
|
+
if (value === '__ens__') onPickRecoveryMethod('ens')
|
|
213
|
+
else if (value === '__token-id__') onPickRecoveryMethod('token-id')
|
|
214
|
+
else if (value === '__back__') onBack()
|
|
215
|
+
else onTokenSelect(value)
|
|
216
|
+
}}
|
|
217
|
+
onCancel={onBack}
|
|
218
|
+
/>
|
|
219
|
+
</Surface>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (step.kind === 'restore-fetching') {
|
|
224
|
+
return (
|
|
225
|
+
<BusyScreen
|
|
226
|
+
title={isSwitch ? 'Loading Agent' : 'Restoring Your Agent'}
|
|
227
|
+
subtitle="IPFS"
|
|
228
|
+
label="opening encrypted state from IPFS..."
|
|
229
|
+
onCancel={onBack}
|
|
230
|
+
/>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (step.kind === 'restore-authorizing') {
|
|
235
|
+
const view = restoreAuthorizationView(step, isSwitch)
|
|
236
|
+
if (restoreProgress) {
|
|
237
|
+
return (
|
|
238
|
+
<BusyScreen
|
|
239
|
+
title={view.progressTitle}
|
|
240
|
+
subtitle="Wallet Signature Received"
|
|
241
|
+
label={restoreProgress.label}
|
|
242
|
+
/>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
return (
|
|
246
|
+
<WalletApprovalScreen
|
|
247
|
+
title={view.title}
|
|
248
|
+
subtitle={view.subtitle}
|
|
249
|
+
walletSession={walletSession}
|
|
250
|
+
label={view.label}
|
|
251
|
+
onCancel={onBack}
|
|
252
|
+
/>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function networkLabelForRegistry(registry: { chainId: number }): string {
|
|
260
|
+
const network = registry.chainId === 1 ? 'mainnet'
|
|
261
|
+
: registry.chainId === 8453 ? 'base'
|
|
262
|
+
: undefined
|
|
263
|
+
return network ? networkLabel(network) : `chain ${registry.chainId}`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function restoreNotFoundView(
|
|
267
|
+
step: Extract<RestoreStep, { kind: 'restore-not-found' }>,
|
|
268
|
+
): { title: string; subtitle: string; detail: string } {
|
|
269
|
+
const network = networkLabelForRegistry(step.registry)
|
|
270
|
+
const address = step.requesterAddress ?? step.ownerHandle
|
|
271
|
+
if (step.reason === 'cancelled') {
|
|
272
|
+
return {
|
|
273
|
+
title: 'Search Cancelled',
|
|
274
|
+
subtitle: `Stopped scanning ${network} for ${shortAddress(address)}.`,
|
|
275
|
+
detail: 'No restore target was selected. Retry to keep searching, or choose a different network.',
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (step.reason === 'no-owner-or-operator') {
|
|
279
|
+
return {
|
|
280
|
+
title: 'No Agent Access Found',
|
|
281
|
+
subtitle: `${shortAddress(address)} has no ERC-8004 agent access on ${network}.`,
|
|
282
|
+
detail: step.requesterAddress
|
|
283
|
+
? 'Checked token ownership and indexed ERC-8004 restore metadata. This wallet owns no agent token and is not listed as an operator wallet.'
|
|
284
|
+
: 'Checked token ownership for this name. Operator-wallet metadata lookup requires a wallet address.',
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
title: 'Agent Search Incomplete',
|
|
289
|
+
subtitle: `ERC-8004 ownership or restore metadata could not be checked completely on ${network}.`,
|
|
290
|
+
detail: 'No restore target was selected. Retry the search or choose another network.',
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function restoreAuthorizationView(
|
|
295
|
+
step: Extract<RestoreStep, { kind: 'restore-authorizing' }>,
|
|
296
|
+
isSwitch: boolean,
|
|
297
|
+
): { title: string; subtitle: string; label: string; progressTitle: string } {
|
|
298
|
+
const owner = step.candidate.ownerAddress
|
|
299
|
+
let requester: string | undefined = step.requesterAddress
|
|
300
|
+
let role: 'owner-wallet' | 'operator-wallet' = 'owner-wallet'
|
|
301
|
+
try {
|
|
302
|
+
const request = restoreSignatureRequestForStep(step)
|
|
303
|
+
requester = request.expectedAccount
|
|
304
|
+
role = request.role
|
|
305
|
+
} catch {
|
|
306
|
+
requester = step.requesterAddress
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (role === 'operator-wallet' && requester) {
|
|
310
|
+
return {
|
|
311
|
+
title: 'Operator Wallet Required',
|
|
312
|
+
subtitle: `Sign with the operator wallet ${shortAddress(requester)} to decrypt this snapshot.`,
|
|
313
|
+
label: 'waiting for operator wallet signature...',
|
|
314
|
+
progressTitle: isSwitch ? 'Loading Agent' : 'Restoring Your Agent',
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
title: 'Owner Wallet Required',
|
|
320
|
+
subtitle: `This encrypted snapshot requires the owner wallet ${shortAddress(owner)}.`,
|
|
321
|
+
label: 'waiting for owner wallet signature...',
|
|
322
|
+
progressTitle: isSwitch ? 'Loading Agent' : 'Restoring Your Agent',
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import type { EthagentConfig } from '../../../../storage/config.js'
|
|
3
|
+
import {
|
|
4
|
+
runRestoreAuthorize,
|
|
5
|
+
runRestoreConnectWallet,
|
|
6
|
+
runRestoreDiscover,
|
|
7
|
+
runRestoreFetch,
|
|
8
|
+
} from '../../effects/restore/index.js'
|
|
9
|
+
import type { EffectCallbacks } from '../../effects/types.js'
|
|
10
|
+
import type { Step } from '../../identityHubReducer.js'
|
|
11
|
+
|
|
12
|
+
const MIN_BUSY_ERROR_MS = 2000
|
|
13
|
+
|
|
14
|
+
function waitForMinimumBusyTime(startedAt: number): Promise<void> {
|
|
15
|
+
const elapsed = Date.now() - startedAt
|
|
16
|
+
if (elapsed >= MIN_BUSY_ERROR_MS) return Promise.resolve()
|
|
17
|
+
return new Promise(resolve => setTimeout(resolve, MIN_BUSY_ERROR_MS - elapsed))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type RestoreFlowEffectsArgs = {
|
|
21
|
+
step: Step
|
|
22
|
+
config: EthagentConfig | undefined
|
|
23
|
+
callbacks: EffectCallbacks
|
|
24
|
+
handleStepError: (err: unknown, backStep: Step, softCancel?: Step) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useRestoreFlowEffects(args: RestoreFlowEffectsArgs): void {
|
|
28
|
+
const { step, config, callbacks, handleStepError } = args
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (step.kind !== 'restore-discovering') return
|
|
32
|
+
let cancelled = false
|
|
33
|
+
const startedAt = Date.now()
|
|
34
|
+
const abortController = new AbortController()
|
|
35
|
+
const stepWithSignal = { ...step, abortSignal: abortController.signal }
|
|
36
|
+
runRestoreDiscover(stepWithSignal, config, callbacks)
|
|
37
|
+
.catch(async (err: unknown) => {
|
|
38
|
+
await waitForMinimumBusyTime(startedAt)
|
|
39
|
+
if (cancelled) return
|
|
40
|
+
handleStepError(err, { kind: 'restore-network', ownerHandle: step.ownerHandle, purpose: step.purpose })
|
|
41
|
+
})
|
|
42
|
+
return () => {
|
|
43
|
+
cancelled = true
|
|
44
|
+
abortController.abort()
|
|
45
|
+
}
|
|
46
|
+
}, [step])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (step.kind !== 'restore-wallet') return
|
|
50
|
+
let cancelled = false
|
|
51
|
+
runRestoreConnectWallet(step, callbacks)
|
|
52
|
+
.catch((err: unknown) => { if (!cancelled) handleStepError(err, { kind: 'menu' }) })
|
|
53
|
+
return () => { cancelled = true }
|
|
54
|
+
}, [step])
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (step.kind !== 'restore-fetching') return
|
|
58
|
+
let cancelled = false
|
|
59
|
+
const startedAt = Date.now()
|
|
60
|
+
runRestoreFetch(step, callbacks)
|
|
61
|
+
.catch(async (err: unknown) => {
|
|
62
|
+
await waitForMinimumBusyTime(startedAt)
|
|
63
|
+
if (!cancelled) handleStepError(err, { kind: 'restore-network', ownerHandle: step.requesterAddress ?? step.candidate.ownerAddress, purpose: step.purpose })
|
|
64
|
+
})
|
|
65
|
+
return () => { cancelled = true }
|
|
66
|
+
}, [step])
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (step.kind !== 'restore-authorizing') return
|
|
70
|
+
let cancelled = false
|
|
71
|
+
runRestoreAuthorize(step, callbacks)
|
|
72
|
+
.catch((err: unknown) => {
|
|
73
|
+
if (!cancelled) handleStepError(err, { kind: 'restore-network', ownerHandle: step.requesterAddress ?? step.candidate.ownerAddress, purpose: step.purpose })
|
|
74
|
+
})
|
|
75
|
+
return () => { cancelled = true }
|
|
76
|
+
}, [step])
|
|
77
|
+
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '
|
|
4
|
-
import { Select } from '
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import type { Step } from '../identityHubReducer.js'
|
|
9
|
-
|
|
10
|
-
const PINATA_API_KEYS_URL = 'https://app.pinata.cloud/developers/api-keys'
|
|
3
|
+
import { Surface } from '../../../../ui/Surface.js'
|
|
4
|
+
import { Select } from '../../../../ui/Select.js'
|
|
5
|
+
import { theme } from '../../../../ui/theme.js'
|
|
6
|
+
import { PinataJwtInput } from '../../components/PinataJwtInput.js'
|
|
7
|
+
import type { Step } from '../../identityHubReducer.js'
|
|
11
8
|
|
|
12
9
|
type StorageCredentialAction = 'edit' | 'forget' | 'back'
|
|
13
10
|
|
|
14
11
|
export const STORAGE_CREDENTIAL_FORGET_COPY = [
|
|
15
12
|
'removes the saved IPFS storage token from this machine.',
|
|
16
13
|
'existing pinned IPFS backups are not deleted.',
|
|
17
|
-
'
|
|
14
|
+
'new encrypted snapshots cannot be pinned with that account until you save a token again.',
|
|
18
15
|
'agent identity and sessions stay on this machine.',
|
|
19
16
|
] as const
|
|
20
17
|
|
|
@@ -41,39 +38,21 @@ export const StorageCredentialScreen: React.FC<StorageCredentialScreenProps> = (
|
|
|
41
38
|
}) => {
|
|
42
39
|
if (step.kind === 'storage-credential-input') {
|
|
43
40
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
<PinataJwtInput
|
|
42
|
+
inputKey="storage-credential-input"
|
|
43
|
+
title="IPFS Storage"
|
|
44
|
+
subtitle={step.error ?? 'Save the Pinata JWT used to pin encrypted snapshots.'}
|
|
47
45
|
footer={footer}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<Text color={theme.accentPrimary} underline>{PINATA_API_KEYS_URL}</Text>
|
|
52
|
-
</Text>
|
|
53
|
-
<Text color={theme.dim}>Stored encrypted on this device. Used only for IPFS pinning.</Text>
|
|
54
|
-
<TextInput
|
|
55
|
-
key="storage-credential-input"
|
|
56
|
-
isSecret
|
|
57
|
-
placeholder="Pinata JWT"
|
|
58
|
-
validate={v => {
|
|
59
|
-
try {
|
|
60
|
-
extractPinataJwt(v)
|
|
61
|
-
return null
|
|
62
|
-
} catch (err: unknown) {
|
|
63
|
-
return (err as Error).message
|
|
64
|
-
}
|
|
65
|
-
}}
|
|
66
|
-
onSubmit={onSubmit}
|
|
67
|
-
onCancel={onCancel}
|
|
68
|
-
/>
|
|
69
|
-
</Surface>
|
|
46
|
+
onSubmit={onSubmit}
|
|
47
|
+
onCancel={onCancel}
|
|
48
|
+
/>
|
|
70
49
|
)
|
|
71
50
|
}
|
|
72
51
|
|
|
73
52
|
if (step.kind === 'storage-credential-forget-confirm') {
|
|
74
53
|
return (
|
|
75
54
|
<Surface
|
|
76
|
-
title="Forget IPFS Storage
|
|
55
|
+
title="Forget IPFS Storage?"
|
|
77
56
|
subtitle="This only removes the local token used for pinning."
|
|
78
57
|
footer={footer}
|
|
79
58
|
>
|
|
@@ -83,14 +62,14 @@ export const StorageCredentialScreen: React.FC<StorageCredentialScreenProps> = (
|
|
|
83
62
|
))}
|
|
84
63
|
</Box>
|
|
85
64
|
<Box marginTop={1}>
|
|
86
|
-
<Text color={theme.
|
|
65
|
+
<Text color={theme.accentPeriwinkle}>Remove the token from this machine?</Text>
|
|
87
66
|
</Box>
|
|
88
67
|
<Box marginTop={1}>
|
|
89
68
|
<Select<StorageCredentialAction>
|
|
90
69
|
options={[
|
|
91
|
-
{ value: 'forget', role: 'section',
|
|
70
|
+
{ value: 'forget', role: 'section', label: 'Credential' },
|
|
92
71
|
{ value: 'forget', label: 'Forget Credential', hint: 'Remove local IPFS pinning token' },
|
|
93
|
-
{ value: 'back', role: 'section',
|
|
72
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
94
73
|
{ value: 'back', label: 'Keep Credential', hint: 'Return without changing storage access', role: 'utility' },
|
|
95
74
|
]}
|
|
96
75
|
hintLayout="inline"
|
|
@@ -104,18 +83,18 @@ export const StorageCredentialScreen: React.FC<StorageCredentialScreenProps> = (
|
|
|
104
83
|
|
|
105
84
|
return (
|
|
106
85
|
<Surface
|
|
107
|
-
title="IPFS Storage
|
|
108
|
-
subtitle="
|
|
86
|
+
title="IPFS Storage"
|
|
87
|
+
subtitle="Manage the credential used to pin encrypted snapshots from this machine."
|
|
109
88
|
footer={footer}
|
|
110
89
|
>
|
|
111
90
|
<Box marginTop={1}>
|
|
112
91
|
<Select<StorageCredentialAction>
|
|
113
92
|
options={[
|
|
114
|
-
{ value: 'edit', role: 'section',
|
|
93
|
+
{ value: 'edit', role: 'section', label: 'Credential' },
|
|
115
94
|
{ value: 'edit', label: hasCredential ? 'Replace Credential' : 'Save Credential', hint: 'Store Pinata JWT for IPFS pinning' },
|
|
116
|
-
{ value: 'forget', label: 'Forget Credential', hint: 'Remove the local pinning token
|
|
117
|
-
{ value: 'back', role: 'section',
|
|
118
|
-
{ value: 'back', label: 'Back
|
|
95
|
+
{ value: 'forget', label: 'Forget Credential', hint: 'Remove the local pinning token. Existing pins remain', disabled: !hasCredential },
|
|
96
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
97
|
+
{ value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
|
|
119
98
|
]}
|
|
120
99
|
hintLayout="inline"
|
|
121
100
|
onSubmit={choice => {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
|
|
3
|
+
import { supportedErc8004ChainForId } from '../../../registry/erc8004.js'
|
|
4
|
+
import {
|
|
5
|
+
runTokenTransferStorageSubmit,
|
|
6
|
+
runTokenTransferTargetSubmit,
|
|
7
|
+
} from '../../effects/token-transfer/runTokenTransfer.js'
|
|
8
|
+
import type {
|
|
9
|
+
EffectCallbacks,
|
|
10
|
+
TokenTransferProgress,
|
|
11
|
+
} from '../../effects/types.js'
|
|
12
|
+
import type { Step } from '../../identityHubReducer.js'
|
|
13
|
+
import { BusyScreen } from '../../components/BusyScreen.js'
|
|
14
|
+
import { RebackupStorageScreen } from '../continuity/RebackupStorageScreen.js'
|
|
15
|
+
import {
|
|
16
|
+
TokenTransferReadyScreen,
|
|
17
|
+
TokenTransferSigningScreen,
|
|
18
|
+
TokenTransferTargetScreen,
|
|
19
|
+
} from './TokenTransferScreens.js'
|
|
20
|
+
|
|
21
|
+
type TokenTransferStep = Extract<Step, {
|
|
22
|
+
kind:
|
|
23
|
+
| 'token-transfer-target'
|
|
24
|
+
| 'token-transfer-resolving'
|
|
25
|
+
| 'token-transfer-storage'
|
|
26
|
+
| 'token-transfer-signing'
|
|
27
|
+
| 'token-transfer-ready'
|
|
28
|
+
}>
|
|
29
|
+
|
|
30
|
+
type IdentityHubTokenTransferFlowProps = {
|
|
31
|
+
step: TokenTransferStep
|
|
32
|
+
callbacks: EffectCallbacks
|
|
33
|
+
footer: React.ReactNode
|
|
34
|
+
progress: TokenTransferProgress | null
|
|
35
|
+
walletSession: BrowserWalletReady | null
|
|
36
|
+
onSetStep: (step: Step) => void
|
|
37
|
+
onBack: () => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isTokenTransferStep(step: Step): step is TokenTransferStep {
|
|
41
|
+
return step.kind === 'token-transfer-target'
|
|
42
|
+
|| step.kind === 'token-transfer-resolving'
|
|
43
|
+
|| step.kind === 'token-transfer-storage'
|
|
44
|
+
|| step.kind === 'token-transfer-signing'
|
|
45
|
+
|| step.kind === 'token-transfer-ready'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const IdentityHubTokenTransferFlow: React.FC<IdentityHubTokenTransferFlowProps> = ({
|
|
49
|
+
step,
|
|
50
|
+
callbacks,
|
|
51
|
+
footer,
|
|
52
|
+
progress,
|
|
53
|
+
walletSession,
|
|
54
|
+
onSetStep,
|
|
55
|
+
onBack,
|
|
56
|
+
}) => {
|
|
57
|
+
const resolveAbortRef = React.useRef<AbortController | null>(null)
|
|
58
|
+
const tokenNetworkLabel = networkLabelForChainId(step.registry.chainId)
|
|
59
|
+
const readyBackHint = tokenTransferBackHint(step.returnTo)
|
|
60
|
+
|
|
61
|
+
if (step.kind === 'token-transfer-target') {
|
|
62
|
+
return (
|
|
63
|
+
<TokenTransferTargetScreen
|
|
64
|
+
identity={step.identity}
|
|
65
|
+
tokenNetworkLabel={tokenNetworkLabel}
|
|
66
|
+
error={step.error}
|
|
67
|
+
initialValue={step.previousTarget}
|
|
68
|
+
onSubmit={async value => {
|
|
69
|
+
resolveAbortRef.current?.abort()
|
|
70
|
+
const controller = new AbortController()
|
|
71
|
+
resolveAbortRef.current = controller
|
|
72
|
+
try {
|
|
73
|
+
await runTokenTransferTargetSubmit(value, step, callbacks, { signal: controller.signal })
|
|
74
|
+
} catch (err: unknown) {
|
|
75
|
+
if (controller.signal.aborted) return
|
|
76
|
+
onSetStep({ ...step, error: (err as Error).message })
|
|
77
|
+
} finally {
|
|
78
|
+
if (resolveAbortRef.current === controller) resolveAbortRef.current = null
|
|
79
|
+
}
|
|
80
|
+
}}
|
|
81
|
+
onBack={onBack}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (step.kind === 'token-transfer-resolving') {
|
|
87
|
+
return (
|
|
88
|
+
<BusyScreen
|
|
89
|
+
title="Resolve Receiver Wallet"
|
|
90
|
+
subtitle={`Resolving ${step.targetHandle} before preparing the transfer snapshot.`}
|
|
91
|
+
label="checking ens or address..."
|
|
92
|
+
onCancel={() => {
|
|
93
|
+
resolveAbortRef.current?.abort()
|
|
94
|
+
resolveAbortRef.current = null
|
|
95
|
+
onBack()
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (step.kind === 'token-transfer-storage') {
|
|
102
|
+
return (
|
|
103
|
+
<RebackupStorageScreen
|
|
104
|
+
step={{
|
|
105
|
+
kind: 'rebackup-storage',
|
|
106
|
+
identity: step.identity,
|
|
107
|
+
registry: step.registry,
|
|
108
|
+
error: step.error,
|
|
109
|
+
pinataJwt: step.pinataJwt,
|
|
110
|
+
}}
|
|
111
|
+
footer={footer}
|
|
112
|
+
title="Connect IPFS Storage"
|
|
113
|
+
subtitle="Save a Pinata JWT so ethagent can pin the transfer snapshot to IPFS."
|
|
114
|
+
onSubmit={async input => {
|
|
115
|
+
try {
|
|
116
|
+
await runTokenTransferStorageSubmit(input, step, callbacks)
|
|
117
|
+
} catch (err: unknown) {
|
|
118
|
+
onSetStep({ ...step, error: (err as Error).message })
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
onCancel={onBack}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (step.kind === 'token-transfer-signing') {
|
|
127
|
+
return (
|
|
128
|
+
<TokenTransferSigningScreen
|
|
129
|
+
identity={step.identity}
|
|
130
|
+
tokenNetworkLabel={tokenNetworkLabel}
|
|
131
|
+
targetHandle={step.targetHandle}
|
|
132
|
+
targetAddress={step.targetAddress}
|
|
133
|
+
progress={progress}
|
|
134
|
+
walletSession={walletSession}
|
|
135
|
+
onCancel={onBack}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<TokenTransferReadyScreen
|
|
142
|
+
identity={step.identity}
|
|
143
|
+
tokenNetworkLabel={tokenNetworkLabel}
|
|
144
|
+
targetHandle={step.targetHandle}
|
|
145
|
+
targetAddress={step.targetAddress}
|
|
146
|
+
snapshotCid={step.snapshotCid}
|
|
147
|
+
txHash={step.txHash}
|
|
148
|
+
footer={footer}
|
|
149
|
+
backHint={readyBackHint}
|
|
150
|
+
onBack={onBack}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function networkLabelForChainId(chainId: number): string {
|
|
156
|
+
return supportedErc8004ChainForId(chainId)?.name ?? `Chain ${chainId}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function tokenTransferBackHint(returnTo: Step | undefined): string {
|
|
160
|
+
if (returnTo?.kind === 'edit-profile-ens') return 'Return to ENS setup'
|
|
161
|
+
return 'Return to Identity Hub menu'
|
|
162
|
+
}
|