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,94 @@
|
|
|
1
|
+
import type { ContinuityWorkingTreeStatus } from '../../continuity/storage.js'
|
|
2
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
+
|
|
4
|
+
export function hasPendingPublish(identity?: EthagentIdentity): boolean {
|
|
5
|
+
if (!identity?.backup?.cid) return false
|
|
6
|
+
if (!identity.metadataCid) return false
|
|
7
|
+
if (!identity.backup.metadataCid) return true
|
|
8
|
+
return identity.backup.metadataCid !== identity.metadataCid
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LocalChangeStatusView = {
|
|
12
|
+
label: string
|
|
13
|
+
detail: string
|
|
14
|
+
tone: 'ok' | 'warn' | 'dim'
|
|
15
|
+
files: string[]
|
|
16
|
+
hasLocalChanges: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function changedContinuitySnapshotFiles(
|
|
20
|
+
workingStatus?: ContinuityWorkingTreeStatus | null,
|
|
21
|
+
): string[] {
|
|
22
|
+
if (!workingStatus?.localContentHashes || !workingStatus.publishedContentHashes) return []
|
|
23
|
+
const files: Array<keyof typeof workingStatus.localContentHashes> = ['SOUL.md', 'MEMORY.md', 'skills.json']
|
|
24
|
+
return files
|
|
25
|
+
.filter(file => workingStatus.localContentHashes?.[file] !== workingStatus.publishedContentHashes?.[file])
|
|
26
|
+
.map(displayContinuitySnapshotFile)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function displayContinuitySnapshotFile(file: keyof NonNullable<ContinuityWorkingTreeStatus['localContentHashes']>): string {
|
|
30
|
+
return file
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function localChangeStatusView(
|
|
34
|
+
workingStatus?: ContinuityWorkingTreeStatus | null,
|
|
35
|
+
): LocalChangeStatusView {
|
|
36
|
+
if (!workingStatus) {
|
|
37
|
+
return {
|
|
38
|
+
label: 'Local Changes',
|
|
39
|
+
detail: '',
|
|
40
|
+
tone: 'dim',
|
|
41
|
+
files: [],
|
|
42
|
+
hasLocalChanges: false,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (workingStatus.publishState === 'published') {
|
|
47
|
+
return {
|
|
48
|
+
label: 'Local Changes',
|
|
49
|
+
detail: 'None detected',
|
|
50
|
+
tone: 'ok',
|
|
51
|
+
files: [],
|
|
52
|
+
hasLocalChanges: false,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (workingStatus.publishState === 'local-changes') {
|
|
57
|
+
const files = changedContinuitySnapshotFiles(workingStatus)
|
|
58
|
+
return {
|
|
59
|
+
label: 'Local Changes',
|
|
60
|
+
detail: files.length > 0 ? `Detected: ${files.join(', ')}` : 'Detected: local files differ from saved snapshot',
|
|
61
|
+
tone: 'warn',
|
|
62
|
+
files,
|
|
63
|
+
hasLocalChanges: true,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (workingStatus.publishState === 'not-published') {
|
|
68
|
+
return {
|
|
69
|
+
label: 'Local Changes',
|
|
70
|
+
detail: 'Snapshot not saved yet',
|
|
71
|
+
tone: 'warn',
|
|
72
|
+
files: [],
|
|
73
|
+
hasLocalChanges: false,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (workingStatus.publishState === 'verify-needed') {
|
|
78
|
+
return {
|
|
79
|
+
label: 'Local Changes',
|
|
80
|
+
detail: 'Unable to verify saved snapshot',
|
|
81
|
+
tone: 'warn',
|
|
82
|
+
files: [],
|
|
83
|
+
hasLocalChanges: false,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
label: 'Local Changes',
|
|
89
|
+
detail: 'Local files not restored',
|
|
90
|
+
tone: 'warn',
|
|
91
|
+
files: [],
|
|
92
|
+
hasLocalChanges: false,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import { readCustodyMode, readIdentityStateString } from './custody.js'
|
|
3
|
+
|
|
4
|
+
type CopyableField = {
|
|
5
|
+
label: string
|
|
6
|
+
value: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function copyableIdentityFields(identity?: EthagentIdentity, config?: EthagentConfig): CopyableField[] {
|
|
10
|
+
if (!identity) return []
|
|
11
|
+
const fields: CopyableField[] = []
|
|
12
|
+
if (identity.agentId) fields.push({ label: 'Token ID', value: identity.agentId })
|
|
13
|
+
const ensName = readIdentityStateString(identity.state, 'ensName')
|
|
14
|
+
if (ensName) fields.push({ label: 'ENS Name', value: ensName })
|
|
15
|
+
const agentUri = identity.agentUri ?? (identity.metadataCid ? `ipfs://${identity.metadataCid}` : undefined)
|
|
16
|
+
if (agentUri) fields.push({ label: 'Agent URI', value: agentUri })
|
|
17
|
+
const ownerAddress = readIdentityStateString(identity.state, 'ownerAddress')
|
|
18
|
+
const owner = identity.ownerAddress ?? identity.address
|
|
19
|
+
const ownerWallet = ownerAddress || owner
|
|
20
|
+
if (ownerWallet) fields.push({ label: 'Owner Wallet', value: ownerWallet })
|
|
21
|
+
const custodyMode = readCustodyMode(identity.state)
|
|
22
|
+
if (custodyMode === 'advanced') {
|
|
23
|
+
const vaultAddress = readIdentityStateString(identity.state, 'operatorVaultAddress')
|
|
24
|
+
if (vaultAddress) fields.push({ label: 'Agent Vault', value: vaultAddress })
|
|
25
|
+
}
|
|
26
|
+
const activeOperator = readIdentityStateString(identity.state, 'activeOperatorAddress')
|
|
27
|
+
if (activeOperator) fields.push({ label: 'Operator Wallet', value: activeOperator })
|
|
28
|
+
return fields
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function identityValuesCopyHint(identity?: EthagentIdentity): string {
|
|
32
|
+
return readIdentityStateString(identity?.state, 'ensName')
|
|
33
|
+
? 'Copy token, ENS, and token URI pointers'
|
|
34
|
+
: 'Copy token and token URI pointers'
|
|
35
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import { readOwnerAddressField } from '../../identityCompat.js'
|
|
3
|
+
|
|
4
|
+
export type CustodyMode = 'simple' | 'advanced'
|
|
5
|
+
|
|
6
|
+
export function readCustodyMode(state: Record<string, unknown> | undefined): CustodyMode | undefined {
|
|
7
|
+
const s = (state ?? {}) as Record<string, unknown>
|
|
8
|
+
const current = s.custodyMode
|
|
9
|
+
if (current === 'simple' || current === 'advanced') return current
|
|
10
|
+
if (current === 'single') return 'simple'
|
|
11
|
+
if (current === 'multi') return 'advanced'
|
|
12
|
+
const legacy = s.ensMode
|
|
13
|
+
if (legacy === 'advanced') return 'advanced'
|
|
14
|
+
if (legacy === 'simple') return 'simple'
|
|
15
|
+
return undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function displayCustodyMode(mode: CustodyMode | undefined): string {
|
|
19
|
+
if (mode === 'advanced') return 'Advanced'
|
|
20
|
+
if (mode === 'simple') return 'Simple'
|
|
21
|
+
return 'Not set'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function identityOwnerAddress(identity?: EthagentIdentity, verifiedOnchainOwner?: string): string {
|
|
25
|
+
const onchainOwner = typeof verifiedOnchainOwner === 'string' ? verifiedOnchainOwner.trim() : ''
|
|
26
|
+
if (onchainOwner) return onchainOwner
|
|
27
|
+
if (!identity) return ''
|
|
28
|
+
return readOwnerAddressField(identity.state as Record<string, unknown> | undefined)
|
|
29
|
+
?? identity.ownerAddress
|
|
30
|
+
?? identity.address
|
|
31
|
+
?? ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type IdentityPerspective = 'owner' | 'operator' | 'unknown'
|
|
35
|
+
|
|
36
|
+
export function identityPerspective(identity?: EthagentIdentity): IdentityPerspective {
|
|
37
|
+
if (!identity) return 'unknown'
|
|
38
|
+
const connected = identity.connectedWallet?.toLowerCase()
|
|
39
|
+
if (!connected) return 'unknown'
|
|
40
|
+
const ownerAddress = (readOwnerAddressField(identity.state as Record<string, unknown> | undefined) ?? identity.ownerAddress ?? identity.address)?.toLowerCase()
|
|
41
|
+
if (ownerAddress && connected === ownerAddress) return 'owner'
|
|
42
|
+
const activeOp = readIdentityStateString(identity.state as Record<string, unknown> | undefined, 'activeOperatorAddress')?.toLowerCase()
|
|
43
|
+
if (activeOp && connected === activeOp) return 'operator'
|
|
44
|
+
const approvedRaw = (identity.state as Record<string, unknown> | undefined)?.approvedOperatorWallets
|
|
45
|
+
const approved = Array.isArray(approvedRaw) ? approvedRaw as Array<{ address?: unknown }> : []
|
|
46
|
+
if (approved.some(r => typeof r?.address === 'string' && r.address.toLowerCase() === connected)) return 'operator'
|
|
47
|
+
return 'unknown'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function readIdentityStateString(state: Record<string, unknown> | undefined, key: string): string {
|
|
51
|
+
if (key === 'ownerAddress') return readOwnerAddressField(state) ?? ''
|
|
52
|
+
const value = state?.[key]
|
|
53
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
54
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import { readIdentityStateString } from './custody.js'
|
|
3
|
+
|
|
4
|
+
export type EnsStatusView =
|
|
5
|
+
| { kind: 'none' }
|
|
6
|
+
| { kind: 'linked'; name: string }
|
|
7
|
+
| { kind: 'issue'; name: string; reason: string }
|
|
8
|
+
|
|
9
|
+
type EnsValidationRecord = {
|
|
10
|
+
ok: boolean
|
|
11
|
+
reason?: string
|
|
12
|
+
resolvedAddress?: string
|
|
13
|
+
checkedAt?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readEnsValidation(state: Record<string, unknown> | undefined): EnsValidationRecord | null {
|
|
17
|
+
const raw = state?.ensValidation
|
|
18
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
|
|
19
|
+
const obj = raw as Record<string, unknown>
|
|
20
|
+
if (typeof obj.ok !== 'boolean') return null
|
|
21
|
+
return {
|
|
22
|
+
ok: obj.ok,
|
|
23
|
+
...(typeof obj.reason === 'string' ? { reason: obj.reason } : {}),
|
|
24
|
+
...(typeof obj.resolvedAddress === 'string' ? { resolvedAddress: obj.resolvedAddress } : {}),
|
|
25
|
+
...(typeof obj.checkedAt === 'string' ? { checkedAt: obj.checkedAt } : {}),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function selectEnsStatus(identity: EthagentIdentity | undefined | null): EnsStatusView {
|
|
30
|
+
if (!identity) return { kind: 'none' }
|
|
31
|
+
const name = readIdentityStateString(identity.state, 'ensName')
|
|
32
|
+
if (!name) return { kind: 'none' }
|
|
33
|
+
const validation = readEnsValidation(identity.state)
|
|
34
|
+
if (validation?.ok) return { kind: 'linked', name }
|
|
35
|
+
return { kind: 'issue', name, reason: validation?.reason ?? 'not yet verified' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ensValidationReasonText(reason: string | undefined): string {
|
|
39
|
+
switch (reason) {
|
|
40
|
+
case 'no-owner': return 'Name does not exist on ENS'
|
|
41
|
+
case 'no-resolver': return 'Name has no resolver set'
|
|
42
|
+
case 'address-mismatch': return 'ENS name is not resolving to the expected wallet'
|
|
43
|
+
case 'lookup-failed': return 'Could not reach Ethereum mainnet'
|
|
44
|
+
case 'token-owner-mismatch': return 'Token not held by owner wallet'
|
|
45
|
+
case 'token-owner-lookup-failed': return 'Could not verify ERC-8004 token owner'
|
|
46
|
+
case undefined: return 'Not yet verified'
|
|
47
|
+
default: return reason
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ZodError } from 'zod'
|
|
2
|
+
import { RegisterAgentPreflightError } from '../../registry/erc8004.js'
|
|
3
|
+
import { AgentStateOwnerMismatchError } from '../../crypto/backupEnvelope.js'
|
|
4
|
+
import {
|
|
5
|
+
ContinuitySnapshotOwnerMismatchError,
|
|
6
|
+
ContinuityTransferSnapshotTargetMismatchError,
|
|
7
|
+
} from '../../continuity/envelope.js'
|
|
8
|
+
import {
|
|
9
|
+
OperatorVaultBytecodeMismatchError,
|
|
10
|
+
formatOperatorVaultBytecodeMismatchDetail,
|
|
11
|
+
} from '../../registry/operatorVault.js'
|
|
12
|
+
import { BrowserWalletError } from '../../wallet/browserWallet.js'
|
|
13
|
+
import { TxGuardBusyError } from '../txGuard.js'
|
|
14
|
+
import { shortAddress } from './format.js'
|
|
15
|
+
|
|
16
|
+
export type IdentityHubErrorView = {
|
|
17
|
+
title: string
|
|
18
|
+
detail?: string
|
|
19
|
+
hint?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function identityHubErrorView(err: unknown): IdentityHubErrorView {
|
|
23
|
+
if (err instanceof ZodError) {
|
|
24
|
+
const issue = err.issues[0]
|
|
25
|
+
const path = issue?.path.join('.') ?? ''
|
|
26
|
+
const message = issue?.message ?? 'Schema validation failed.'
|
|
27
|
+
return {
|
|
28
|
+
title: 'Config Validation Failed',
|
|
29
|
+
detail: path ? `${path}: ${message}` : message,
|
|
30
|
+
hint: 'Internal: a config write was attempted before required fields were set. Restart First Run if this persists.',
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (err instanceof TxGuardBusyError) {
|
|
34
|
+
return {
|
|
35
|
+
title: 'Already In Progress',
|
|
36
|
+
detail: err.message,
|
|
37
|
+
hint: 'Approve or cancel the open wallet popup, then retry.',
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (err instanceof RegisterAgentPreflightError) {
|
|
41
|
+
return {
|
|
42
|
+
title: err.title,
|
|
43
|
+
detail: err.detail,
|
|
44
|
+
hint: err.hint,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (err instanceof AgentStateOwnerMismatchError) {
|
|
48
|
+
return {
|
|
49
|
+
title: 'Backup Locked to Another Wallet',
|
|
50
|
+
detail: `Wallet ${shortAddress(err.currentOwner)} cannot read state encrypted for ${shortAddress(err.backupOwner)}.`,
|
|
51
|
+
hint: 'Use the wallet that created this backup.',
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (err instanceof ContinuitySnapshotOwnerMismatchError) {
|
|
55
|
+
return {
|
|
56
|
+
title: 'Snapshot Locked to Previous Wallet',
|
|
57
|
+
detail: 'This token points at an owner-only snapshot. Ask the previous owner to prepare a transfer snapshot before transferring.',
|
|
58
|
+
hint: `Current wallet ${shortAddress(err.currentOwner)} cannot read the snapshot encrypted for ${shortAddress(err.snapshotOwner)}.`,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (err instanceof ContinuityTransferSnapshotTargetMismatchError) {
|
|
62
|
+
return {
|
|
63
|
+
title: 'Transfer Snapshot Receiver Mismatch',
|
|
64
|
+
detail: `This transfer snapshot is for receiver ${shortAddress(err.targetOwner)}, but the current token owner is ${shortAddress(err.currentOwner)}.`,
|
|
65
|
+
hint: `Prepare a new transfer snapshot from ${shortAddress(err.snapshotOwner)} to the intended receiver wallet.`,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (err instanceof OperatorVaultBytecodeMismatchError) {
|
|
69
|
+
return {
|
|
70
|
+
title: 'Vault Bytecode Mismatch',
|
|
71
|
+
detail: formatOperatorVaultBytecodeMismatchDetail(err),
|
|
72
|
+
hint: 'Open the deploy tx on the block explorer and confirm the input data matches the OperatorVault deploy bytecode plus constructor args before retrying. A persistent mismatch usually means the wallet or RPC substituted the create.',
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (err instanceof BrowserWalletError) {
|
|
76
|
+
const lines: string[] = []
|
|
77
|
+
if (err.purpose) lines.push(`Action: ${err.purpose}`)
|
|
78
|
+
if (err.method) lines.push(`Method: ${err.method}`)
|
|
79
|
+
if (err.code) lines.push(`Code: ${err.code}`)
|
|
80
|
+
lines.push(`Wallet: ${err.message}`)
|
|
81
|
+
if (err.causes.length) {
|
|
82
|
+
for (const cause of err.causes) lines.push(`Caused by: ${cause}`)
|
|
83
|
+
}
|
|
84
|
+
if (err.data) lines.push(`Data: ${err.data}`)
|
|
85
|
+
return {
|
|
86
|
+
title: err.title ?? 'Wallet Error',
|
|
87
|
+
detail: lines.join('\n'),
|
|
88
|
+
hint: 'Switch to the expected wallet, confirm you have funds, then retry. If the wallet keeps reporting an internal RPC error, change the RPC endpoint in your wallet settings.',
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
92
|
+
if (/^owner wallet required:/i.test(message)) {
|
|
93
|
+
return {
|
|
94
|
+
title: 'Owner Wallet Required',
|
|
95
|
+
detail: capitalizeErrorText(message.replace(/^owner wallet required:\s*/i, '')),
|
|
96
|
+
hint: 'Switch to the owner wallet, then try again.',
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (/^operator wallet required:/i.test(message)) {
|
|
100
|
+
return {
|
|
101
|
+
title: 'Operator Wallet Required',
|
|
102
|
+
detail: capitalizeErrorText(message.replace(/^operator wallet required:\s*/i, '')),
|
|
103
|
+
hint: 'Switch to the operator wallet, then try again.',
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (/^Restore Slot Missing:/i.test(message)) {
|
|
107
|
+
return {
|
|
108
|
+
title: 'Wallet Not Included In Snapshot',
|
|
109
|
+
detail: 'ERC-8004 metadata authorizes this wallet, but the latest encrypted snapshot was saved before this wallet had restore access.',
|
|
110
|
+
hint: 'Use the owner wallet once to save a fresh snapshot, then restore with this wallet.',
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (message === 'fetch failed') {
|
|
114
|
+
return {
|
|
115
|
+
title: 'Storage Unavailable',
|
|
116
|
+
detail: 'Could not reach storage.',
|
|
117
|
+
hint: 'Check the connection, then try again.',
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
title: 'Identity Error',
|
|
122
|
+
detail: capitalizeErrorText(message),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function pinataErrorText(err: unknown): string {
|
|
127
|
+
const view = identityHubErrorView(err)
|
|
128
|
+
return view.detail ?? view.title
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function isRegistrationPreflightError(err: unknown): boolean {
|
|
132
|
+
return err instanceof RegisterAgentPreflightError
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function capitalizeErrorText(value: string): string {
|
|
136
|
+
const trimmed = value.trim()
|
|
137
|
+
if (/^wallet request timed out$/i.test(trimmed)) return 'Wallet Request Timed Out'
|
|
138
|
+
if (!trimmed) return trimmed
|
|
139
|
+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1)
|
|
140
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function shortCid(cid: string): string {
|
|
2
|
+
if (cid.length <= 18) return cid
|
|
3
|
+
return `${cid.slice(0, 10)}...${cid.slice(-6)}`
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function shortAddress(address: string): string {
|
|
7
|
+
if (address.length <= 14) return address
|
|
8
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatDate(input: string): string {
|
|
12
|
+
const date = new Date(input)
|
|
13
|
+
if (Number.isNaN(date.getTime())) return input
|
|
14
|
+
return date.toISOString().slice(0, 10)
|
|
15
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import { supportedErc8004ChainForId, type Erc8004AgentCandidate } from '../../registry/erc8004.js'
|
|
3
|
+
import { readCustodyMode, type CustodyMode } from './custody.js'
|
|
4
|
+
import { formatDate, shortAddress, shortCid } from './format.js'
|
|
5
|
+
import { chainSummaryRow, networkLabel } from './network.js'
|
|
6
|
+
|
|
7
|
+
export const PREFLIGHT_AGENT_URI = 'ipfs://bafybeigdyrztma2dbfczw7q6ooozbxlqzyw5r7w4f3qw2axvvxqg3w6y7q'
|
|
8
|
+
|
|
9
|
+
export function initialAgentState(name: string, description: string, ownerAddress: string): Record<string, unknown> {
|
|
10
|
+
return {
|
|
11
|
+
version: 1,
|
|
12
|
+
name,
|
|
13
|
+
description,
|
|
14
|
+
ownerAddress,
|
|
15
|
+
custodyMode: 'simple' as CustodyMode,
|
|
16
|
+
createdAt: new Date().toISOString(),
|
|
17
|
+
preferences: {},
|
|
18
|
+
memory: {},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function tokenCandidateLabel(candidate: Erc8004AgentCandidate): string {
|
|
23
|
+
return candidate.name?.trim() || `Agent Token #${candidate.agentId.toString()}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function tokenCandidateSelectLabel(
|
|
27
|
+
candidate: Erc8004AgentCandidate,
|
|
28
|
+
current = false,
|
|
29
|
+
): string {
|
|
30
|
+
return `${tokenCandidateLabel(candidate)}${current ? ' *' : ''}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function tokenCandidateHint(candidate: Erc8004AgentCandidate): string {
|
|
34
|
+
const chain = supportedErc8004ChainForId(candidate.chainId)
|
|
35
|
+
const network = chain?.network ? networkLabel(chain.network) : chain?.name ?? `chain ${candidate.chainId}`
|
|
36
|
+
const parts = [
|
|
37
|
+
candidate.name?.trim() ? `token #${candidate.agentId.toString()}` : null,
|
|
38
|
+
network,
|
|
39
|
+
candidate.backup?.createdAt ? `backup ${formatDate(candidate.backup.createdAt)}` : null,
|
|
40
|
+
].filter((part): part is string => Boolean(part))
|
|
41
|
+
return parts.join(' · ')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isCurrentAgentCandidate(
|
|
45
|
+
identity: EthagentIdentity | undefined,
|
|
46
|
+
candidate: Erc8004AgentCandidate,
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!identity?.agentId) return false
|
|
49
|
+
if (identity.agentId !== candidate.agentId.toString()) return false
|
|
50
|
+
|
|
51
|
+
const owner = identity.ownerAddress ?? identity.address
|
|
52
|
+
if (owner && owner.toLowerCase() !== candidate.ownerAddress.toLowerCase()) return false
|
|
53
|
+
if (identity.chainId !== undefined && identity.chainId !== candidate.chainId) return false
|
|
54
|
+
if (
|
|
55
|
+
identity.identityRegistryAddress
|
|
56
|
+
&& identity.identityRegistryAddress.toLowerCase() !== candidate.identityRegistryAddress.toLowerCase()
|
|
57
|
+
) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function identitySummaryRows(
|
|
64
|
+
identity: EthagentIdentity | undefined,
|
|
65
|
+
config?: EthagentConfig,
|
|
66
|
+
): Array<{
|
|
67
|
+
label: string
|
|
68
|
+
value: string
|
|
69
|
+
tone: 'ok' | 'dim'
|
|
70
|
+
}> {
|
|
71
|
+
const backup = identity?.backup
|
|
72
|
+
const owner = identity?.ownerAddress ?? identity?.address
|
|
73
|
+
const ownerValue = owner ? shortAddress(owner) : 'not connected'
|
|
74
|
+
const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
|
|
75
|
+
const chain = chainSummaryRow(config, identity)
|
|
76
|
+
const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
|
|
77
|
+
const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not saved'
|
|
78
|
+
const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not saved'
|
|
79
|
+
const iconValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
|
|
80
|
+
return [
|
|
81
|
+
{ label: 'owner wallet', value: ownerValue, tone: identity ? 'ok' : 'dim' },
|
|
82
|
+
{ label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' },
|
|
83
|
+
{ label: 'network', value: chain.value, tone: chain.tone },
|
|
84
|
+
{ label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' },
|
|
85
|
+
{ label: 'skills', value: skillsValue, tone: identity?.publicSkills?.cid ? 'ok' : 'dim' },
|
|
86
|
+
{ label: 'card', value: cardValue, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
|
|
87
|
+
{ label: 'icon', value: iconValue, tone: iconValue === 'attached' ? 'ok' : 'dim' },
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function lastBackupLabel(identity?: EthagentIdentity): string {
|
|
92
|
+
const created = identity?.backup?.createdAt
|
|
93
|
+
return created ? formatDate(created) : 'never'
|
|
94
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../../storage/config.js'
|
|
2
|
+
import { supportedErc8004ChainForId } from '../../registry/erc8004.js'
|
|
3
|
+
import { resolveSelectedNetwork } from '../../registry/registryConfig.js'
|
|
4
|
+
|
|
5
|
+
const NETWORK_LABELS: Record<SelectableNetwork, string> = {
|
|
6
|
+
mainnet: 'ethereum mainnet',
|
|
7
|
+
base: 'base',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function networkLabel(network: SelectableNetwork): string {
|
|
11
|
+
return NETWORK_LABELS[network]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NETWORK_SUBTITLES: Record<SelectableNetwork, string> = {
|
|
15
|
+
mainnet: 'Best for high-value identities and highest security',
|
|
16
|
+
base: 'Best for lower-cost setup and routine updates',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function networkSubtitle(network: SelectableNetwork): string {
|
|
20
|
+
return NETWORK_SUBTITLES[network]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function chainSummaryRow(config?: EthagentConfig, identity?: EthagentIdentity): {
|
|
24
|
+
label: string
|
|
25
|
+
value: string
|
|
26
|
+
tone: 'ok' | 'dim'
|
|
27
|
+
} {
|
|
28
|
+
const network = resolveSelectedNetwork(config)
|
|
29
|
+
const fromIdentity = identity?.chainId ? supportedErc8004ChainForId(identity.chainId)?.name.toLowerCase() : undefined
|
|
30
|
+
const value = fromIdentity ?? networkLabel(network)
|
|
31
|
+
return { label: 'chain', value, tone: identity?.chainId ? 'ok' : 'dim' }
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { EthagentIdentity, TransferSnapshotMetadata } from '../../../storage/config.js'
|
|
2
|
+
|
|
3
|
+
export type TransferSnapshotView =
|
|
4
|
+
| {
|
|
5
|
+
kind: 'ready-to-transfer'
|
|
6
|
+
sender: string
|
|
7
|
+
receiver: string
|
|
8
|
+
receiverHandle?: string
|
|
9
|
+
slotCount: number
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
kind: 'received'
|
|
13
|
+
sender: string
|
|
14
|
+
receiver: string
|
|
15
|
+
receiverHandle?: string
|
|
16
|
+
slotCount: number
|
|
17
|
+
}
|
|
18
|
+
| null
|
|
19
|
+
|
|
20
|
+
export function transferSnapshotView(identity?: EthagentIdentity | null): TransferSnapshotView {
|
|
21
|
+
const snapshot = identity?.backup?.transferSnapshot
|
|
22
|
+
if (!identity || !isDualWalletTransferSnapshot(snapshot)) return null
|
|
23
|
+
const owner = identity.ownerAddress ?? identity.address
|
|
24
|
+
if (!owner) return null
|
|
25
|
+
const ownerKey = owner.toLowerCase()
|
|
26
|
+
const senderKey = snapshot.senderAddress.toLowerCase()
|
|
27
|
+
const receiverKey = snapshot.receiverAddress.toLowerCase()
|
|
28
|
+
if (snapshot.slotCount < 2) return null
|
|
29
|
+
if (ownerKey === senderKey) {
|
|
30
|
+
return {
|
|
31
|
+
kind: 'ready-to-transfer',
|
|
32
|
+
sender: snapshot.senderAddress,
|
|
33
|
+
receiver: snapshot.receiverAddress,
|
|
34
|
+
...(snapshot.receiverHandle ? { receiverHandle: snapshot.receiverHandle } : {}),
|
|
35
|
+
slotCount: snapshot.slotCount,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (ownerKey === receiverKey) {
|
|
39
|
+
return {
|
|
40
|
+
kind: 'received',
|
|
41
|
+
sender: snapshot.senderAddress,
|
|
42
|
+
receiver: snapshot.receiverAddress,
|
|
43
|
+
...(snapshot.receiverHandle ? { receiverHandle: snapshot.receiverHandle } : {}),
|
|
44
|
+
slotCount: snapshot.slotCount,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isDualWalletTransferSnapshot(value: unknown): value is TransferSnapshotMetadata {
|
|
51
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
52
|
+
const snapshot = value as Partial<TransferSnapshotMetadata>
|
|
53
|
+
return snapshot.kind === 'dual-wallet'
|
|
54
|
+
&& typeof snapshot.senderAddress === 'string'
|
|
55
|
+
&& typeof snapshot.receiverAddress === 'string'
|
|
56
|
+
&& typeof snapshot.slotCount === 'number'
|
|
57
|
+
}
|