ethagent 1.1.1 → 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 +127 -29
- package/package.json +16 -9
- package/src/app/FirstRun.tsx +192 -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 +43 -18
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +11 -17
- package/src/chat/ConversationStack.tsx +3 -0
- 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/TranscriptView.tsx +6 -0
- 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 +5 -3
- 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 -815
- 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/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
- 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 +25 -43
- 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 +166 -101
- 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 +21 -9
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +3 -3
- package/src/ui/TextInput.tsx +191 -29
- 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 -291
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
- 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
|
@@ -1,937 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import type { Address, Hex } from 'viem'
|
|
5
|
-
import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../storage/config.js'
|
|
6
|
-
import { saveConfig } from '../../storage/config.js'
|
|
7
|
-
import {
|
|
8
|
-
assertAgentStateBackupOwner,
|
|
9
|
-
parseAgentStateBackupEnvelope,
|
|
10
|
-
restoreAgentStateBackupEnvelope,
|
|
11
|
-
} from '../crypto/backupEnvelope.js'
|
|
12
|
-
import {
|
|
13
|
-
CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
|
|
14
|
-
assertContinuitySnapshotOwner,
|
|
15
|
-
createContinuitySnapshotChallenge,
|
|
16
|
-
createContinuitySnapshotEnvelope,
|
|
17
|
-
parseContinuitySnapshotEnvelope,
|
|
18
|
-
restoreContinuitySnapshotEnvelope,
|
|
19
|
-
serializeContinuitySnapshotEnvelope,
|
|
20
|
-
type ContinuitySnapshotEnvelope,
|
|
21
|
-
} from '../continuity/envelope.js'
|
|
22
|
-
import {
|
|
23
|
-
continuityAgentSnapshot,
|
|
24
|
-
continuityVaultStatus,
|
|
25
|
-
defaultContinuityFiles,
|
|
26
|
-
ensurePublicSkillsFile,
|
|
27
|
-
ensureIdentityMarkdownScaffold,
|
|
28
|
-
ensureContinuityFiles,
|
|
29
|
-
continuitySnapshotContentHashes,
|
|
30
|
-
equalContinuitySnapshotHashes,
|
|
31
|
-
localContinuitySnapshotContentHashes,
|
|
32
|
-
prepareSyncedIdentityMarkdownScaffold,
|
|
33
|
-
prepareSyncedPublicSkillsJson,
|
|
34
|
-
readContinuityFiles,
|
|
35
|
-
readPublicSkillsFile,
|
|
36
|
-
writeContinuityFiles,
|
|
37
|
-
writeIdentityMarkdownScaffold,
|
|
38
|
-
writePublicSkillsFile,
|
|
39
|
-
type IdentityMarkdownScaffold,
|
|
40
|
-
} from '../continuity/storage.js'
|
|
41
|
-
import {
|
|
42
|
-
createAgentCard,
|
|
43
|
-
defaultPublicSkillsProfile,
|
|
44
|
-
renderPublicSkillsJson,
|
|
45
|
-
serializeAgentCard,
|
|
46
|
-
} from '../continuity/publicSkills.js'
|
|
47
|
-
import {
|
|
48
|
-
recordPublishedContinuitySnapshot,
|
|
49
|
-
updatePublishedContinuitySnapshotContentHashes,
|
|
50
|
-
} from '../continuity/snapshots.js'
|
|
51
|
-
import { addFileToIpfs, addToIpfs, catFromIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl, type IpfsAddResult } from '../storage/ipfs.js'
|
|
52
|
-
import {
|
|
53
|
-
AgentTokenIdRequiredError,
|
|
54
|
-
chainIdForNetwork,
|
|
55
|
-
createErc8004PublicClient,
|
|
56
|
-
discoverOwnedAgentBackups,
|
|
57
|
-
discoverOwnedAgentBackupByTokenId,
|
|
58
|
-
encodeRegisterAgent,
|
|
59
|
-
encodeSetAgentUri,
|
|
60
|
-
erc8004ConfigForSupportedChain,
|
|
61
|
-
normalizeErc8004RegistryConfig,
|
|
62
|
-
preflightRegisterAgent,
|
|
63
|
-
preflightSetAgentUri,
|
|
64
|
-
registeredAgentFromReceipt,
|
|
65
|
-
withEthagentBackupPointer,
|
|
66
|
-
withEthagentPointers,
|
|
67
|
-
type Erc8004AgentCandidate,
|
|
68
|
-
type Erc8004RegistryConfig,
|
|
69
|
-
} from '../registry/erc8004.js'
|
|
70
|
-
import { getAddress } from 'viem'
|
|
71
|
-
import { registryConfigFromConfig, type RegistryResolution } from '../registry/registryConfig.js'
|
|
72
|
-
import { resolveValidatedPinataJwt, savePinataJwt } from '../storage/pinataJwt.js'
|
|
73
|
-
import {
|
|
74
|
-
requestBrowserWalletAccount,
|
|
75
|
-
requestBrowserWalletSignature,
|
|
76
|
-
requestBrowserWalletSignatureAndTransaction,
|
|
77
|
-
sendBrowserWalletTransaction,
|
|
78
|
-
type BrowserWalletReady,
|
|
79
|
-
} from '../wallet/browserWallet.js'
|
|
80
|
-
import { initialAgentState, PREFLIGHT_AGENT_URI } from './identityHubModel.js'
|
|
81
|
-
import type { Step, ProfileUpdates, RestorePurpose } from './identityHubReducer.js'
|
|
82
|
-
|
|
83
|
-
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
84
|
-
type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
|
|
85
|
-
|
|
86
|
-
type CreatePreparedTransaction = {
|
|
87
|
-
ownerAddress: Address
|
|
88
|
-
agentUri: string
|
|
89
|
-
metadataCid: string
|
|
90
|
-
backup: BackupMetadata
|
|
91
|
-
publicSkills: PublicSkillsMetadata
|
|
92
|
-
state: Record<string, unknown>
|
|
93
|
-
continuityFiles: ReturnType<typeof defaultContinuityFiles>
|
|
94
|
-
publicSkillsJson: string
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type RebackupPreparedTransaction = {
|
|
98
|
-
ownerAddress: Address
|
|
99
|
-
agentUri: string
|
|
100
|
-
metadataCid: string
|
|
101
|
-
backup: BackupMetadata
|
|
102
|
-
publicSkills: PublicSkillsMetadata
|
|
103
|
-
identity: EthagentIdentity
|
|
104
|
-
markdownScaffold?: IdentityMarkdownScaffold
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
type PublicProfilePreparedTransaction = {
|
|
108
|
-
ownerAddress: Address
|
|
109
|
-
agentUri: string
|
|
110
|
-
metadataCid: string
|
|
111
|
-
publicSkills: PublicSkillsMetadata
|
|
112
|
-
identity: EthagentIdentity
|
|
113
|
-
publicSkillsJson: string
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export type EffectCallbacks = {
|
|
117
|
-
onStep: (step: Step) => void
|
|
118
|
-
onWalletReady: (session: BrowserWalletReady | null) => void
|
|
119
|
-
onIdentityComplete: (identity: EthagentIdentity, message: string) => Promise<void>
|
|
120
|
-
onRestoreProgress?: (progress: RestoreProgress | null) => void
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export type RestoreProgress = {
|
|
124
|
-
phase: 'decrypting' | 'writing' | 'finishing'
|
|
125
|
-
label: string
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export async function runCreatePreflight(
|
|
129
|
-
step: Extract<Step, { kind: 'create-preflight' }>,
|
|
130
|
-
config: EthagentConfig | undefined,
|
|
131
|
-
callbacks: EffectCallbacks,
|
|
132
|
-
): Promise<void> {
|
|
133
|
-
const resolution = step.network
|
|
134
|
-
? registryResolutionForNetwork(step.network)
|
|
135
|
-
: registryConfigFromConfig(config)
|
|
136
|
-
if (!resolution.config) {
|
|
137
|
-
callbacks.onStep({ kind: 'create-registry', name: step.name, description: step.description, resolution })
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
const apiUrl = DEFAULT_IPFS_API_URL
|
|
141
|
-
let jwt: string | undefined
|
|
142
|
-
try {
|
|
143
|
-
jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
|
|
144
|
-
} catch (err: unknown) {
|
|
145
|
-
callbacks.onStep({
|
|
146
|
-
kind: 'create-storage',
|
|
147
|
-
name: step.name,
|
|
148
|
-
description: step.description,
|
|
149
|
-
registry: resolution.config,
|
|
150
|
-
error: (err as Error).message,
|
|
151
|
-
})
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
if (isPinataUploadUrl(apiUrl) && !jwt) {
|
|
155
|
-
callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry: resolution.config })
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: resolution.config, pinataJwt: jwt })
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function registryResolutionForNetwork(network: SelectableNetwork): RegistryResolution {
|
|
162
|
-
const chainId = chainIdForNetwork(network)
|
|
163
|
-
try {
|
|
164
|
-
const registry = erc8004ConfigForSupportedChain(chainId)
|
|
165
|
-
return {
|
|
166
|
-
config: registry,
|
|
167
|
-
network,
|
|
168
|
-
chainId,
|
|
169
|
-
needsRegistryAddress: false,
|
|
170
|
-
defaultRpcUrl: registry.rpcUrl,
|
|
171
|
-
}
|
|
172
|
-
} catch {
|
|
173
|
-
return {
|
|
174
|
-
config: null,
|
|
175
|
-
network,
|
|
176
|
-
chainId,
|
|
177
|
-
needsRegistryAddress: true,
|
|
178
|
-
defaultRpcUrl: '',
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export async function runCreateSigning(
|
|
184
|
-
step: Extract<Step, { kind: 'create-signing' }>,
|
|
185
|
-
callbacks: EffectCallbacks,
|
|
186
|
-
): Promise<void> {
|
|
187
|
-
const result = await requestBrowserWalletSignatureAndTransaction<CreatePreparedTransaction>({
|
|
188
|
-
chainId: step.registry.chainId,
|
|
189
|
-
messageForAccount: account => createContinuitySnapshotChallenge(account),
|
|
190
|
-
onReady: callbacks.onWalletReady,
|
|
191
|
-
prepareTransaction: async wallet => {
|
|
192
|
-
await preflightRegisterAgent({
|
|
193
|
-
...step.registry,
|
|
194
|
-
ownerAddress: wallet.account,
|
|
195
|
-
agentURI: PREFLIGHT_AGENT_URI,
|
|
196
|
-
})
|
|
197
|
-
const state = initialAgentState(step.name, step.description, wallet.account)
|
|
198
|
-
const draftIdentity = identityDraftForBackup({
|
|
199
|
-
ownerAddress: wallet.account,
|
|
200
|
-
registry: step.registry,
|
|
201
|
-
state,
|
|
202
|
-
})
|
|
203
|
-
const continuityFiles = defaultContinuityFiles(draftIdentity)
|
|
204
|
-
const publicProfile = defaultPublicSkillsProfile(draftIdentity)
|
|
205
|
-
const publicSkillsJson = renderPublicSkillsJson(publicProfile)
|
|
206
|
-
const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
|
|
207
|
-
assertVerifiedPin(publicSkillsPin)
|
|
208
|
-
const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeAgentCard(createAgentCard(publicProfile)), fetch, { pinataJwt: step.pinataJwt })
|
|
209
|
-
assertVerifiedPin(agentCardPin)
|
|
210
|
-
const envelope = createContinuitySnapshotEnvelope({
|
|
211
|
-
ownerAddress: wallet.account,
|
|
212
|
-
walletSignature: wallet.signature,
|
|
213
|
-
payload: {
|
|
214
|
-
agent: continuityAgentSnapshot(draftIdentity),
|
|
215
|
-
files: continuityFiles,
|
|
216
|
-
transcript: [],
|
|
217
|
-
state,
|
|
218
|
-
},
|
|
219
|
-
})
|
|
220
|
-
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
221
|
-
assertVerifiedPin(statePin)
|
|
222
|
-
const cid = statePin.cid
|
|
223
|
-
const backup: BackupMetadata = {
|
|
224
|
-
cid,
|
|
225
|
-
createdAt: envelope.createdAt,
|
|
226
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
227
|
-
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
228
|
-
status: 'pinned',
|
|
229
|
-
ownerAddress: wallet.account,
|
|
230
|
-
chainId: step.registry.chainId,
|
|
231
|
-
rpcUrl: step.registry.rpcUrl,
|
|
232
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
233
|
-
}
|
|
234
|
-
const publicSkills: PublicSkillsMetadata = {
|
|
235
|
-
cid: publicSkillsPin.cid,
|
|
236
|
-
agentCardCid: agentCardPin.cid,
|
|
237
|
-
updatedAt: envelope.createdAt,
|
|
238
|
-
status: 'pinned',
|
|
239
|
-
}
|
|
240
|
-
const registration = withEthagentBackupPointer({
|
|
241
|
-
type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
|
|
242
|
-
name: step.name,
|
|
243
|
-
...(step.description ? { description: step.description } : {}),
|
|
244
|
-
...(typeof state.imageUrl === 'string' ? { image: state.imageUrl } : {}),
|
|
245
|
-
}, {
|
|
246
|
-
cid,
|
|
247
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
248
|
-
createdAt: envelope.createdAt,
|
|
249
|
-
}, {
|
|
250
|
-
skillsCid: publicSkills.cid,
|
|
251
|
-
agentCardCid: publicSkills.agentCardCid,
|
|
252
|
-
updatedAt: publicSkills.updatedAt,
|
|
253
|
-
}, {
|
|
254
|
-
chainId: step.registry.chainId,
|
|
255
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
256
|
-
})
|
|
257
|
-
const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
|
|
258
|
-
assertVerifiedPin(metadataPin)
|
|
259
|
-
const metadataCid = metadataPin.cid
|
|
260
|
-
const agentUri = `ipfs://${metadataCid}`
|
|
261
|
-
return {
|
|
262
|
-
to: step.registry.identityRegistryAddress,
|
|
263
|
-
data: encodeRegisterAgent({ agentURI: agentUri }),
|
|
264
|
-
prepared: {
|
|
265
|
-
ownerAddress: wallet.account,
|
|
266
|
-
agentUri,
|
|
267
|
-
metadataCid,
|
|
268
|
-
backup: { ...backup, metadataCid, agentUri },
|
|
269
|
-
publicSkills,
|
|
270
|
-
state,
|
|
271
|
-
continuityFiles,
|
|
272
|
-
publicSkillsJson,
|
|
273
|
-
},
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
})
|
|
277
|
-
const client = createErc8004PublicClient(step.registry)
|
|
278
|
-
const receipt = await client.waitForTransactionReceipt({ hash: result.txHash })
|
|
279
|
-
const registered = registeredAgentFromReceipt({
|
|
280
|
-
logs: receipt.logs.map(log => ({ address: log.address, topics: [...log.topics] as Hex[], data: log.data })),
|
|
281
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
282
|
-
ownerAddress: result.prepared.ownerAddress,
|
|
283
|
-
})
|
|
284
|
-
const backup: BackupMetadata = {
|
|
285
|
-
...result.prepared.backup,
|
|
286
|
-
agentId: registered.agentId.toString(),
|
|
287
|
-
agentUri: registered.agentURI,
|
|
288
|
-
txHash: result.txHash,
|
|
289
|
-
}
|
|
290
|
-
const nextIdentity: EthagentIdentity = {
|
|
291
|
-
source: 'erc8004',
|
|
292
|
-
address: result.prepared.ownerAddress,
|
|
293
|
-
ownerAddress: result.prepared.ownerAddress,
|
|
294
|
-
createdAt: result.prepared.backup.createdAt,
|
|
295
|
-
chainId: step.registry.chainId,
|
|
296
|
-
rpcUrl: step.registry.rpcUrl,
|
|
297
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
298
|
-
agentId: registered.agentId.toString(),
|
|
299
|
-
agentUri: registered.agentURI,
|
|
300
|
-
metadataCid: result.prepared.metadataCid,
|
|
301
|
-
state: result.prepared.state,
|
|
302
|
-
backup,
|
|
303
|
-
publicSkills: result.prepared.publicSkills,
|
|
304
|
-
}
|
|
305
|
-
await writeIdentityMarkdownScaffold(nextIdentity, {
|
|
306
|
-
...defaultContinuityFiles(nextIdentity),
|
|
307
|
-
'skills.json': result.prepared.publicSkillsJson,
|
|
308
|
-
})
|
|
309
|
-
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'initial published snapshot' }).catch(() => null)
|
|
310
|
-
await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent registered · #${registered.agentId.toString()}`)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export async function runRestoreDiscover(
|
|
314
|
-
step: Extract<Step, { kind: 'restore-discovering' }>,
|
|
315
|
-
_config: EthagentConfig | undefined,
|
|
316
|
-
callbacks: EffectCallbacks,
|
|
317
|
-
): Promise<void> {
|
|
318
|
-
const candidates = await discoverOwnedAgentBackups({
|
|
319
|
-
...step.registry,
|
|
320
|
-
ownerHandle: step.ownerHandle,
|
|
321
|
-
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
322
|
-
})
|
|
323
|
-
callbacks.onStep(restoreTokenSelectionStep({
|
|
324
|
-
ownerHandle: step.ownerHandle,
|
|
325
|
-
registry: step.registry,
|
|
326
|
-
candidates,
|
|
327
|
-
purpose: step.purpose,
|
|
328
|
-
}))
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export async function runRestoreConnectWallet(
|
|
332
|
-
step: Extract<Step, { kind: 'restore-wallet' }>,
|
|
333
|
-
callbacks: EffectCallbacks,
|
|
334
|
-
): Promise<void> {
|
|
335
|
-
const wallet = await requestBrowserWalletAccount({
|
|
336
|
-
onReady: callbacks.onWalletReady,
|
|
337
|
-
})
|
|
338
|
-
callbacks.onStep({ kind: 'restore-network', ownerHandle: wallet.account, purpose: step.purpose })
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
export function restoreTokenSelectionStep(args: {
|
|
342
|
-
ownerHandle: string
|
|
343
|
-
registry: Erc8004RegistryConfig
|
|
344
|
-
candidates: Erc8004AgentCandidate[]
|
|
345
|
-
purpose?: RestorePurpose
|
|
346
|
-
}): Extract<Step, { kind: 'restore-select-token' }> {
|
|
347
|
-
const restorable = args.candidates.filter(candidate => candidate.backup?.cid)
|
|
348
|
-
if (restorable.length === 0) {
|
|
349
|
-
throw new Error(args.candidates.length === 0
|
|
350
|
-
? 'no agent identities owned by that wallet on this network'
|
|
351
|
-
: 'no owned agent identity has recoverable ethagent state on this network')
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
kind: 'restore-select-token',
|
|
355
|
-
ownerHandle: args.ownerHandle,
|
|
356
|
-
registry: args.registry,
|
|
357
|
-
candidates: restorable,
|
|
358
|
-
purpose: args.purpose,
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export function isAgentTokenIdRequiredError(err: unknown): err is AgentTokenIdRequiredError {
|
|
363
|
-
return err instanceof AgentTokenIdRequiredError
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export async function runRestoreTokenIdSubmit(
|
|
367
|
-
value: string,
|
|
368
|
-
step: Extract<Step, { kind: 'restore-token-id' }>,
|
|
369
|
-
callbacks: EffectCallbacks,
|
|
370
|
-
): Promise<void> {
|
|
371
|
-
const tokenId = parseTokenId(value)
|
|
372
|
-
const candidate = await discoverOwnedAgentBackupByTokenId({
|
|
373
|
-
...step.registry,
|
|
374
|
-
ownerHandle: step.ownerHandle,
|
|
375
|
-
tokenId,
|
|
376
|
-
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
377
|
-
})
|
|
378
|
-
if (!candidate.backup?.cid) {
|
|
379
|
-
throw new Error('that agent token does not have recoverable ethagent state')
|
|
380
|
-
}
|
|
381
|
-
callbacks.onStep({
|
|
382
|
-
kind: 'restore-fetching',
|
|
383
|
-
cid: candidate.backup.cid,
|
|
384
|
-
apiUrl: DEFAULT_IPFS_API_URL,
|
|
385
|
-
candidate,
|
|
386
|
-
purpose: step.purpose,
|
|
387
|
-
})
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function parseTokenId(value: string): bigint {
|
|
391
|
-
const normalized = value.trim().replace(/^#/, '')
|
|
392
|
-
if (!/^\d+$/.test(normalized)) throw new Error('enter a token id')
|
|
393
|
-
return BigInt(normalized)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export async function runRestoreFetch(
|
|
397
|
-
step: Extract<Step, { kind: 'restore-fetching' }>,
|
|
398
|
-
callbacks: EffectCallbacks,
|
|
399
|
-
): Promise<void> {
|
|
400
|
-
const raw = await catFromIpfs(step.apiUrl, step.cid)
|
|
401
|
-
const envelope = parseRestorableEnvelope(raw)
|
|
402
|
-
if (isContinuitySnapshotEnvelope(envelope)) {
|
|
403
|
-
assertContinuitySnapshotOwner(envelope, step.candidate.ownerAddress)
|
|
404
|
-
} else {
|
|
405
|
-
assertAgentStateBackupOwner(envelope, step.candidate.ownerAddress)
|
|
406
|
-
}
|
|
407
|
-
callbacks.onStep({ kind: 'restore-authorizing', cid: step.cid, apiUrl: step.apiUrl, envelope, candidate: step.candidate, purpose: step.purpose })
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
export async function runRestoreAuthorize(
|
|
411
|
-
step: Extract<Step, { kind: 'restore-authorizing' }>,
|
|
412
|
-
callbacks: EffectCallbacks,
|
|
413
|
-
): Promise<void> {
|
|
414
|
-
const wallet = await requestBrowserWalletSignature({
|
|
415
|
-
chainId: step.candidate.chainId,
|
|
416
|
-
expectedAccount: step.candidate.ownerAddress,
|
|
417
|
-
message: step.envelope.challenge,
|
|
418
|
-
onReady: callbacks.onWalletReady,
|
|
419
|
-
})
|
|
420
|
-
callbacks.onWalletReady(null)
|
|
421
|
-
callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting encrypted snapshot...' })
|
|
422
|
-
let restored: ReturnType<typeof restoreAgentStateBackupEnvelope> | ReturnType<typeof restoreContinuitySnapshotEnvelope>
|
|
423
|
-
let continuityFiles: ReturnType<typeof restoreContinuitySnapshotEnvelope>['files'] | undefined
|
|
424
|
-
if (isContinuitySnapshotEnvelope(step.envelope)) {
|
|
425
|
-
const payload = restoreContinuitySnapshotEnvelope({
|
|
426
|
-
envelope: step.envelope,
|
|
427
|
-
walletSignature: wallet.signature,
|
|
428
|
-
})
|
|
429
|
-
restored = payload
|
|
430
|
-
continuityFiles = payload.files
|
|
431
|
-
} else {
|
|
432
|
-
restored = restoreAgentStateBackupEnvelope({
|
|
433
|
-
envelope: step.envelope,
|
|
434
|
-
walletSignature: wallet.signature,
|
|
435
|
-
})
|
|
436
|
-
}
|
|
437
|
-
callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring local agent files...' })
|
|
438
|
-
const backup: BackupMetadata = {
|
|
439
|
-
cid: step.cid,
|
|
440
|
-
createdAt: step.envelope.createdAt,
|
|
441
|
-
envelopeVersion: step.envelope.envelopeVersion,
|
|
442
|
-
ipfsApiUrl: step.apiUrl,
|
|
443
|
-
status: 'restored',
|
|
444
|
-
ownerAddress: step.candidate.ownerAddress,
|
|
445
|
-
chainId: step.candidate.chainId,
|
|
446
|
-
rpcUrl: step.candidate.rpcUrl,
|
|
447
|
-
identityRegistryAddress: step.candidate.identityRegistryAddress,
|
|
448
|
-
agentId: step.candidate.agentId.toString(),
|
|
449
|
-
agentUri: step.candidate.agentUri,
|
|
450
|
-
metadataCid: step.candidate.metadataCid,
|
|
451
|
-
}
|
|
452
|
-
const nextIdentity: EthagentIdentity = {
|
|
453
|
-
source: 'erc8004',
|
|
454
|
-
address: step.candidate.ownerAddress,
|
|
455
|
-
ownerAddress: step.candidate.ownerAddress,
|
|
456
|
-
createdAt: restored.createdAt,
|
|
457
|
-
chainId: step.candidate.chainId,
|
|
458
|
-
rpcUrl: step.candidate.rpcUrl,
|
|
459
|
-
identityRegistryAddress: step.candidate.identityRegistryAddress,
|
|
460
|
-
agentId: step.candidate.agentId.toString(),
|
|
461
|
-
agentUri: step.candidate.agentUri,
|
|
462
|
-
metadataCid: step.candidate.metadataCid,
|
|
463
|
-
state: {
|
|
464
|
-
...restored.state,
|
|
465
|
-
...(step.candidate.name ? { name: step.candidate.name } : {}),
|
|
466
|
-
...(step.candidate.description ? { description: step.candidate.description } : {}),
|
|
467
|
-
...(step.candidate.imageUrl ? { imageUrl: step.candidate.imageUrl } : {}),
|
|
468
|
-
},
|
|
469
|
-
backup,
|
|
470
|
-
...(step.candidate.publicDiscovery ? {
|
|
471
|
-
publicSkills: {
|
|
472
|
-
...(step.candidate.publicDiscovery.skillsCid ? { cid: step.candidate.publicDiscovery.skillsCid } : {}),
|
|
473
|
-
...(step.candidate.publicDiscovery.agentCardCid ? { agentCardCid: step.candidate.publicDiscovery.agentCardCid } : {}),
|
|
474
|
-
...(step.candidate.publicDiscovery.updatedAt ? { updatedAt: step.candidate.publicDiscovery.updatedAt } : {}),
|
|
475
|
-
status: 'pinned',
|
|
476
|
-
},
|
|
477
|
-
} : {}),
|
|
478
|
-
}
|
|
479
|
-
if (continuityFiles) {
|
|
480
|
-
await writeContinuityFiles(nextIdentity, continuityFiles)
|
|
481
|
-
}
|
|
482
|
-
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
|
|
483
|
-
await restorePublishedPublicSkills(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.skillsCid)
|
|
484
|
-
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
485
|
-
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
|
|
486
|
-
await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
export async function runRegistrySubmit(
|
|
490
|
-
value: string,
|
|
491
|
-
step: Extract<Step, { kind: 'create-registry' }>,
|
|
492
|
-
config: EthagentConfig | undefined,
|
|
493
|
-
onConfigChange: ((config: EthagentConfig) => void) | undefined,
|
|
494
|
-
callbacks: EffectCallbacks,
|
|
495
|
-
): Promise<void> {
|
|
496
|
-
const registry = normalizeErc8004RegistryConfig({
|
|
497
|
-
chainId: step.resolution.chainId,
|
|
498
|
-
rpcUrl: step.resolution.defaultRpcUrl,
|
|
499
|
-
identityRegistryAddress: value.trim(),
|
|
500
|
-
})
|
|
501
|
-
if (config && onConfigChange) {
|
|
502
|
-
const next: EthagentConfig = {
|
|
503
|
-
...config,
|
|
504
|
-
erc8004: {
|
|
505
|
-
chainId: registry.chainId,
|
|
506
|
-
rpcUrl: registry.rpcUrl,
|
|
507
|
-
identityRegistryAddress: registry.identityRegistryAddress,
|
|
508
|
-
},
|
|
509
|
-
}
|
|
510
|
-
await saveConfig(next)
|
|
511
|
-
onConfigChange(next)
|
|
512
|
-
}
|
|
513
|
-
const apiUrl = DEFAULT_IPFS_API_URL
|
|
514
|
-
let jwt: string | undefined
|
|
515
|
-
try {
|
|
516
|
-
jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
|
|
517
|
-
} catch (err: unknown) {
|
|
518
|
-
callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry, error: (err as Error).message })
|
|
519
|
-
return
|
|
520
|
-
}
|
|
521
|
-
if (isPinataUploadUrl(apiUrl) && !jwt) {
|
|
522
|
-
callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry })
|
|
523
|
-
return
|
|
524
|
-
}
|
|
525
|
-
callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry, pinataJwt: jwt })
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
export async function runRestoreRegistrySubmit(
|
|
529
|
-
value: string,
|
|
530
|
-
step: Extract<Step, { kind: 'restore-registry' }>,
|
|
531
|
-
config: EthagentConfig | undefined,
|
|
532
|
-
onConfigChange: ((config: EthagentConfig) => void) | undefined,
|
|
533
|
-
callbacks: EffectCallbacks,
|
|
534
|
-
): Promise<void> {
|
|
535
|
-
const resolution = registryConfigFromConfig(config)
|
|
536
|
-
const registry = normalizeErc8004RegistryConfig({
|
|
537
|
-
chainId: resolution.chainId,
|
|
538
|
-
rpcUrl: resolution.config?.rpcUrl ?? resolution.defaultRpcUrl,
|
|
539
|
-
identityRegistryAddress: value.trim(),
|
|
540
|
-
})
|
|
541
|
-
if (config && onConfigChange) {
|
|
542
|
-
const next: EthagentConfig = {
|
|
543
|
-
...config,
|
|
544
|
-
erc8004: {
|
|
545
|
-
chainId: registry.chainId,
|
|
546
|
-
rpcUrl: registry.rpcUrl,
|
|
547
|
-
identityRegistryAddress: registry.identityRegistryAddress,
|
|
548
|
-
},
|
|
549
|
-
}
|
|
550
|
-
await saveConfig(next)
|
|
551
|
-
onConfigChange(next)
|
|
552
|
-
}
|
|
553
|
-
callbacks.onStep({ kind: 'restore-discovering', ownerHandle: step.ownerHandle, registry, purpose: step.purpose })
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
export async function runStorageSubmit(
|
|
557
|
-
input: string,
|
|
558
|
-
step: Extract<Step, { kind: 'create-storage' }>,
|
|
559
|
-
callbacks: EffectCallbacks,
|
|
560
|
-
): Promise<void> {
|
|
561
|
-
const { jwt: pinataJwt } = await savePinataJwt(input)
|
|
562
|
-
callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: step.registry, pinataJwt })
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
export async function runRebackupPreflight(
|
|
566
|
-
identity: EthagentIdentity,
|
|
567
|
-
registry: Erc8004RegistryConfig,
|
|
568
|
-
callbacks: EffectCallbacks,
|
|
569
|
-
profileUpdates?: ProfileUpdates,
|
|
570
|
-
returnTo: Step = { kind: 'menu' },
|
|
571
|
-
): Promise<void> {
|
|
572
|
-
const status = await continuityVaultStatus(identity)
|
|
573
|
-
if (!status.ready) {
|
|
574
|
-
throw new Error('restore local SOUL.md and MEMORY.md working files before saving an encrypted snapshot')
|
|
575
|
-
}
|
|
576
|
-
const apiUrl = DEFAULT_IPFS_API_URL
|
|
577
|
-
let jwt: string | undefined
|
|
578
|
-
try {
|
|
579
|
-
jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
|
|
580
|
-
} catch (err: unknown) {
|
|
581
|
-
callbacks.onStep({ kind: 'rebackup-storage', identity, registry, error: (err as Error).message, profileUpdates, returnTo })
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
if (isPinataUploadUrl(apiUrl) && !jwt) {
|
|
585
|
-
callbacks.onStep({ kind: 'rebackup-storage', identity, registry, profileUpdates, returnTo })
|
|
586
|
-
return
|
|
587
|
-
}
|
|
588
|
-
callbacks.onStep({ kind: 'rebackup-signing', identity, registry, pinataJwt: jwt, profileUpdates, returnTo })
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
export async function runRebackupSigning(
|
|
592
|
-
step: Extract<Step, { kind: 'rebackup-signing' }>,
|
|
593
|
-
callbacks: EffectCallbacks,
|
|
594
|
-
): Promise<void> {
|
|
595
|
-
const expectedOwner = step.identity.ownerAddress ?? step.identity.address
|
|
596
|
-
const result = await requestBrowserWalletSignatureAndTransaction<RebackupPreparedTransaction>({
|
|
597
|
-
chainId: step.registry.chainId,
|
|
598
|
-
messageForAccount: account => createContinuitySnapshotChallenge(account),
|
|
599
|
-
onReady: callbacks.onWalletReady,
|
|
600
|
-
...(expectedOwner ? { expectedAccount: getAddress(expectedOwner) } : {}),
|
|
601
|
-
prepareTransaction: async wallet => {
|
|
602
|
-
if (!step.identity.agentId) throw new Error('cannot back up: identity is missing an agent token id')
|
|
603
|
-
if (expectedOwner && wallet.account.toLowerCase() !== expectedOwner.toLowerCase()) {
|
|
604
|
-
throw new Error(`connect the wallet that owns this agent (${expectedOwner}) and try again`)
|
|
605
|
-
}
|
|
606
|
-
const baseState = (step.identity.state ?? {}) as Record<string, unknown>
|
|
607
|
-
const profile = step.profileUpdates ?? {}
|
|
608
|
-
const nextName = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : (typeof baseState.name === 'string' ? baseState.name : undefined)
|
|
609
|
-
const nextDescription = profile.description !== undefined ? profile.description.trim() : (typeof baseState.description === 'string' ? baseState.description : '')
|
|
610
|
-
const uploadedImageUri = profile.imagePath === 'delete'
|
|
611
|
-
? ''
|
|
612
|
-
: profile.imagePath
|
|
613
|
-
? await uploadAgentImage(profile.imagePath, step.pinataJwt)
|
|
614
|
-
: typeof baseState.imageUrl === 'string' && baseState.imageUrl.trim()
|
|
615
|
-
? baseState.imageUrl.trim()
|
|
616
|
-
: undefined
|
|
617
|
-
const state: Record<string, unknown> = {
|
|
618
|
-
...baseState,
|
|
619
|
-
...(nextName !== undefined ? { name: nextName } : {}),
|
|
620
|
-
description: nextDescription,
|
|
621
|
-
lastBackedUpAt: new Date().toISOString(),
|
|
622
|
-
}
|
|
623
|
-
if (uploadedImageUri === '') {
|
|
624
|
-
delete state.imageUrl
|
|
625
|
-
} else if (uploadedImageUri) {
|
|
626
|
-
state.imageUrl = uploadedImageUri
|
|
627
|
-
}
|
|
628
|
-
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
629
|
-
const markdownScaffold = step.profileUpdates
|
|
630
|
-
? await prepareSyncedIdentityMarkdownScaffold(nextIdentityForFiles)
|
|
631
|
-
: undefined
|
|
632
|
-
const continuityFiles = markdownScaffold
|
|
633
|
-
? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
|
|
634
|
-
: await readContinuityFiles(nextIdentityForFiles)
|
|
635
|
-
const publicSkillsJson = markdownScaffold
|
|
636
|
-
? markdownScaffold['skills.json']
|
|
637
|
-
: await readPublicSkillsFile(nextIdentityForFiles)
|
|
638
|
-
const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
|
|
639
|
-
assertVerifiedPin(publicSkillsPin)
|
|
640
|
-
const agentCardPin = await addToIpfs(
|
|
641
|
-
DEFAULT_IPFS_API_URL,
|
|
642
|
-
serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
|
|
643
|
-
fetch,
|
|
644
|
-
{ pinataJwt: step.pinataJwt },
|
|
645
|
-
)
|
|
646
|
-
assertVerifiedPin(agentCardPin)
|
|
647
|
-
const envelope = createContinuitySnapshotEnvelope({
|
|
648
|
-
ownerAddress: wallet.account,
|
|
649
|
-
walletSignature: wallet.signature,
|
|
650
|
-
payload: {
|
|
651
|
-
agent: continuityAgentSnapshot(nextIdentityForFiles),
|
|
652
|
-
files: continuityFiles,
|
|
653
|
-
transcript: [],
|
|
654
|
-
state,
|
|
655
|
-
},
|
|
656
|
-
})
|
|
657
|
-
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
658
|
-
assertVerifiedPin(statePin)
|
|
659
|
-
const cid = statePin.cid
|
|
660
|
-
const backup: BackupMetadata = {
|
|
661
|
-
cid,
|
|
662
|
-
createdAt: envelope.createdAt,
|
|
663
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
664
|
-
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
665
|
-
status: 'pinned',
|
|
666
|
-
ownerAddress: wallet.account,
|
|
667
|
-
chainId: step.registry.chainId,
|
|
668
|
-
rpcUrl: step.registry.rpcUrl,
|
|
669
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
670
|
-
agentId: step.identity.agentId,
|
|
671
|
-
}
|
|
672
|
-
const publicSkills: PublicSkillsMetadata = {
|
|
673
|
-
cid: publicSkillsPin.cid,
|
|
674
|
-
agentCardCid: agentCardPin.cid,
|
|
675
|
-
updatedAt: envelope.createdAt,
|
|
676
|
-
status: 'pinned',
|
|
677
|
-
}
|
|
678
|
-
const registration = withEthagentBackupPointer({
|
|
679
|
-
type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
|
|
680
|
-
name: nextName ?? deriveAgentName(step.identity),
|
|
681
|
-
...(nextDescription ? { description: nextDescription } : {}),
|
|
682
|
-
...(uploadedImageUri ? { image: uploadedImageUri } : {}),
|
|
683
|
-
}, {
|
|
684
|
-
cid,
|
|
685
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
686
|
-
createdAt: envelope.createdAt,
|
|
687
|
-
}, {
|
|
688
|
-
skillsCid: publicSkills.cid,
|
|
689
|
-
agentCardCid: publicSkills.agentCardCid,
|
|
690
|
-
updatedAt: publicSkills.updatedAt,
|
|
691
|
-
}, {
|
|
692
|
-
chainId: step.registry.chainId,
|
|
693
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
694
|
-
agentId: step.identity.agentId,
|
|
695
|
-
})
|
|
696
|
-
const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
|
|
697
|
-
assertVerifiedPin(metadataPin)
|
|
698
|
-
const metadataCid = metadataPin.cid
|
|
699
|
-
const agentUri = `ipfs://${metadataCid}`
|
|
700
|
-
const agentId = BigInt(step.identity.agentId)
|
|
701
|
-
await preflightSetAgentUri({
|
|
702
|
-
...step.registry,
|
|
703
|
-
account: wallet.account,
|
|
704
|
-
agentId,
|
|
705
|
-
newUri: agentUri,
|
|
706
|
-
})
|
|
707
|
-
return {
|
|
708
|
-
to: step.registry.identityRegistryAddress,
|
|
709
|
-
data: encodeSetAgentUri({ agentId, newUri: agentUri }),
|
|
710
|
-
prepared: {
|
|
711
|
-
ownerAddress: wallet.account,
|
|
712
|
-
agentUri,
|
|
713
|
-
metadataCid,
|
|
714
|
-
backup: { ...backup, metadataCid, agentUri },
|
|
715
|
-
publicSkills,
|
|
716
|
-
identity: { ...step.identity, state },
|
|
717
|
-
...(markdownScaffold ? { markdownScaffold } : {}),
|
|
718
|
-
},
|
|
719
|
-
}
|
|
720
|
-
},
|
|
721
|
-
})
|
|
722
|
-
const client = createErc8004PublicClient(step.registry)
|
|
723
|
-
await client.waitForTransactionReceipt({ hash: result.txHash })
|
|
724
|
-
const nextIdentity: EthagentIdentity = {
|
|
725
|
-
...result.prepared.identity,
|
|
726
|
-
source: 'erc8004',
|
|
727
|
-
address: getAddress(result.prepared.ownerAddress),
|
|
728
|
-
ownerAddress: getAddress(result.prepared.ownerAddress),
|
|
729
|
-
chainId: step.registry.chainId,
|
|
730
|
-
rpcUrl: step.registry.rpcUrl,
|
|
731
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
732
|
-
agentUri: result.prepared.agentUri,
|
|
733
|
-
metadataCid: result.prepared.metadataCid,
|
|
734
|
-
backup: { ...result.prepared.backup, txHash: result.txHash },
|
|
735
|
-
publicSkills: result.prepared.publicSkills,
|
|
736
|
-
}
|
|
737
|
-
if (result.prepared.markdownScaffold) {
|
|
738
|
-
await writeIdentityMarkdownScaffold(nextIdentity, result.prepared.markdownScaffold)
|
|
739
|
-
}
|
|
740
|
-
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published encrypted snapshot' }).catch(() => null)
|
|
741
|
-
const completionMessage = step.profileUpdates ? 'profile updated and backup saved' : 'agent backup saved'
|
|
742
|
-
await callbacks.onIdentityComplete(nextIdentity, completionMessage)
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
export async function runRebackupStorageSubmit(
|
|
746
|
-
input: string,
|
|
747
|
-
step: Extract<Step, { kind: 'rebackup-storage' }>,
|
|
748
|
-
callbacks: EffectCallbacks,
|
|
749
|
-
): Promise<void> {
|
|
750
|
-
const { jwt: pinataJwt } = await savePinataJwt(input)
|
|
751
|
-
callbacks.onStep({ kind: 'rebackup-signing', identity: step.identity, registry: step.registry, pinataJwt, profileUpdates: step.profileUpdates, returnTo: step.returnTo })
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
export async function runRecoveryRefetch(
|
|
758
|
-
identity: EthagentIdentity,
|
|
759
|
-
registry: Erc8004RegistryConfig,
|
|
760
|
-
callbacks: EffectCallbacks,
|
|
761
|
-
): Promise<void> {
|
|
762
|
-
if (!identity.agentId) throw new Error('cannot refetch: identity is missing an agent token id')
|
|
763
|
-
const ownerAddress = getAddress(identity.ownerAddress ?? identity.address)
|
|
764
|
-
const candidate = await discoverOwnedAgentBackupByTokenId({
|
|
765
|
-
...registry,
|
|
766
|
-
ownerHandle: ownerAddress,
|
|
767
|
-
tokenId: BigInt(identity.agentId),
|
|
768
|
-
ipfsApiUrl: identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
|
|
769
|
-
})
|
|
770
|
-
if (!candidate.backup?.cid) {
|
|
771
|
-
throw new Error('the published agent does not have a recoverable encrypted snapshot')
|
|
772
|
-
}
|
|
773
|
-
const apiUrl = identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL
|
|
774
|
-
const raw = await catFromIpfs(apiUrl, candidate.backup.cid)
|
|
775
|
-
const envelope = parseRestorableEnvelope(raw)
|
|
776
|
-
if (!isContinuitySnapshotEnvelope(envelope)) {
|
|
777
|
-
throw new Error('on-chain backup is in a legacy format and cannot be refetched here; use switch agent')
|
|
778
|
-
}
|
|
779
|
-
assertContinuitySnapshotOwner(envelope, ownerAddress)
|
|
780
|
-
const wallet = await requestBrowserWalletSignature({
|
|
781
|
-
chainId: candidate.chainId,
|
|
782
|
-
expectedAccount: ownerAddress,
|
|
783
|
-
message: envelope.challenge,
|
|
784
|
-
onReady: callbacks.onWalletReady,
|
|
785
|
-
})
|
|
786
|
-
callbacks.onWalletReady(null)
|
|
787
|
-
callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting on-chain snapshot...' })
|
|
788
|
-
const payload = restoreContinuitySnapshotEnvelope({ envelope, walletSignature: wallet.signature })
|
|
789
|
-
callbacks.onRestoreProgress?.({ phase: 'writing', label: 'overwriting local SOUL.md, MEMORY.md, skills.json...' })
|
|
790
|
-
const refreshedBackup: BackupMetadata = {
|
|
791
|
-
cid: candidate.backup.cid,
|
|
792
|
-
createdAt: envelope.createdAt,
|
|
793
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
794
|
-
ipfsApiUrl: apiUrl,
|
|
795
|
-
status: 'restored',
|
|
796
|
-
ownerAddress,
|
|
797
|
-
chainId: candidate.chainId,
|
|
798
|
-
rpcUrl: candidate.rpcUrl,
|
|
799
|
-
identityRegistryAddress: candidate.identityRegistryAddress,
|
|
800
|
-
agentId: candidate.agentId.toString(),
|
|
801
|
-
agentUri: candidate.agentUri,
|
|
802
|
-
metadataCid: candidate.metadataCid,
|
|
803
|
-
}
|
|
804
|
-
const nextIdentity: EthagentIdentity = {
|
|
805
|
-
...identity,
|
|
806
|
-
source: 'erc8004',
|
|
807
|
-
address: ownerAddress,
|
|
808
|
-
ownerAddress,
|
|
809
|
-
chainId: candidate.chainId,
|
|
810
|
-
rpcUrl: candidate.rpcUrl,
|
|
811
|
-
identityRegistryAddress: candidate.identityRegistryAddress,
|
|
812
|
-
agentId: candidate.agentId.toString(),
|
|
813
|
-
agentUri: candidate.agentUri,
|
|
814
|
-
metadataCid: candidate.metadataCid,
|
|
815
|
-
state: {
|
|
816
|
-
...payload.state,
|
|
817
|
-
...(candidate.name ? { name: candidate.name } : {}),
|
|
818
|
-
...(candidate.description ? { description: candidate.description } : {}),
|
|
819
|
-
...(candidate.imageUrl ? { imageUrl: candidate.imageUrl } : {}),
|
|
820
|
-
},
|
|
821
|
-
backup: refreshedBackup,
|
|
822
|
-
...(candidate.publicDiscovery ? {
|
|
823
|
-
publicSkills: {
|
|
824
|
-
...(candidate.publicDiscovery.skillsCid ? { cid: candidate.publicDiscovery.skillsCid } : {}),
|
|
825
|
-
...(candidate.publicDiscovery.agentCardCid ? { agentCardCid: candidate.publicDiscovery.agentCardCid } : {}),
|
|
826
|
-
...(candidate.publicDiscovery.updatedAt ? { updatedAt: candidate.publicDiscovery.updatedAt } : {}),
|
|
827
|
-
status: 'pinned',
|
|
828
|
-
},
|
|
829
|
-
} : {}),
|
|
830
|
-
}
|
|
831
|
-
await writeContinuityFiles(nextIdentity, payload.files)
|
|
832
|
-
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing refreshed identity...' })
|
|
833
|
-
await restorePublishedPublicSkills(nextIdentity, apiUrl, candidate.publicDiscovery?.skillsCid)
|
|
834
|
-
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
835
|
-
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'refetched latest snapshot from chain' }).catch(() => null)
|
|
836
|
-
await callbacks.onIdentityComplete(nextIdentity, 'latest published snapshot restored from chain')
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
function deriveAgentName(identity: EthagentIdentity): string {
|
|
843
|
-
const state = (identity.state ?? {}) as Record<string, unknown>
|
|
844
|
-
const name = typeof state.name === 'string' ? state.name.trim() : ''
|
|
845
|
-
if (name) return name
|
|
846
|
-
return identity.agentId ? `agent #${identity.agentId}` : 'unnamed agent'
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
async function uploadAgentImage(imagePath: string, pinataJwt: string | undefined): Promise<string> {
|
|
850
|
-
const file = resolveImagePath(imagePath)
|
|
851
|
-
const data = await fs.readFile(file)
|
|
852
|
-
const contentType = imageContentType(file)
|
|
853
|
-
const pin = await addFileToIpfs(DEFAULT_IPFS_API_URL, data, path.basename(file), contentType, fetch, { pinataJwt })
|
|
854
|
-
assertVerifiedPin(pin)
|
|
855
|
-
return `ipfs://${pin.cid}`
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function resolveImagePath(input: string): string {
|
|
859
|
-
const trimmed = input.trim()
|
|
860
|
-
if (!trimmed) throw new Error('image file path is empty')
|
|
861
|
-
if (/^https?:\/\//i.test(trimmed) || /^ipfs:\/\//i.test(trimmed)) {
|
|
862
|
-
throw new Error('enter a local image file path; ethagent uploads it to IPFS')
|
|
863
|
-
}
|
|
864
|
-
if (!/\.(png|jpe?g|gif|webp|svg)$/i.test(trimmed)) {
|
|
865
|
-
throw new Error('agent image must be png, jpg, gif, webp, or svg')
|
|
866
|
-
}
|
|
867
|
-
return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, os.homedir()))
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
function imageContentType(file: string): string {
|
|
871
|
-
const ext = path.extname(file).toLowerCase()
|
|
872
|
-
switch (ext) {
|
|
873
|
-
case '.png':
|
|
874
|
-
return 'image/png'
|
|
875
|
-
case '.jpg':
|
|
876
|
-
case '.jpeg':
|
|
877
|
-
return 'image/jpeg'
|
|
878
|
-
case '.gif':
|
|
879
|
-
return 'image/gif'
|
|
880
|
-
case '.webp':
|
|
881
|
-
return 'image/webp'
|
|
882
|
-
case '.svg':
|
|
883
|
-
return 'image/svg+xml'
|
|
884
|
-
default:
|
|
885
|
-
throw new Error('agent image must be png, jpg, gif, webp, or svg')
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function assertVerifiedPin(pin: IpfsAddResult, expectedCid?: string): void {
|
|
890
|
-
if (expectedCid && pin.cid !== expectedCid) throw new Error('IPFS pin verification did not match the published CID')
|
|
891
|
-
if (!pin.pinVerified) throw new Error(`IPFS pin was not verified for ${pin.cid}`)
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function parseRestorableEnvelope(raw: string | Uint8Array): ReturnType<typeof parseAgentStateBackupEnvelope> | ContinuitySnapshotEnvelope {
|
|
895
|
-
const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw)
|
|
896
|
-
const parsed = JSON.parse(text) as { envelopeVersion?: unknown }
|
|
897
|
-
if (parsed.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION) {
|
|
898
|
-
return parseContinuitySnapshotEnvelope(text)
|
|
899
|
-
}
|
|
900
|
-
return parseAgentStateBackupEnvelope(text)
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
function isContinuitySnapshotEnvelope(envelope: ReturnType<typeof parseRestorableEnvelope>): envelope is ContinuitySnapshotEnvelope {
|
|
904
|
-
return envelope.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
function identityDraftForBackup(args: {
|
|
908
|
-
ownerAddress: Address
|
|
909
|
-
registry: Erc8004RegistryConfig
|
|
910
|
-
state: Record<string, unknown>
|
|
911
|
-
}): EthagentIdentity {
|
|
912
|
-
return {
|
|
913
|
-
source: 'erc8004',
|
|
914
|
-
address: args.ownerAddress,
|
|
915
|
-
ownerAddress: args.ownerAddress,
|
|
916
|
-
createdAt: typeof args.state.createdAt === 'string' ? args.state.createdAt : new Date().toISOString(),
|
|
917
|
-
chainId: args.registry.chainId,
|
|
918
|
-
rpcUrl: args.registry.rpcUrl,
|
|
919
|
-
identityRegistryAddress: args.registry.identityRegistryAddress,
|
|
920
|
-
agentUri: PREFLIGHT_AGENT_URI,
|
|
921
|
-
state: args.state,
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
async function restorePublishedPublicSkills(
|
|
926
|
-
identity: EthagentIdentity,
|
|
927
|
-
apiUrl: string,
|
|
928
|
-
cid: string | undefined,
|
|
929
|
-
): Promise<void> {
|
|
930
|
-
if (!cid) return
|
|
931
|
-
try {
|
|
932
|
-
const raw = await catFromIpfs(apiUrl, cid)
|
|
933
|
-
await writePublicSkillsFile(identity, new TextDecoder().decode(raw))
|
|
934
|
-
} catch {
|
|
935
|
-
// Public skills are recoverable from IPFS later and must not block private restore.
|
|
936
|
-
}
|
|
937
|
-
}
|