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,347 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Address } from 'viem'
|
|
3
|
+
import { Box, Text } from 'ink'
|
|
4
|
+
import { Surface } from '../../../../ui/Surface.js'
|
|
5
|
+
import { Select } from '../../../../ui/Select.js'
|
|
6
|
+
import { theme } from '../../../../ui/theme.js'
|
|
7
|
+
import type { ProfileUpdates, Step } from '../../identityHubReducer.js'
|
|
8
|
+
import {
|
|
9
|
+
displayCustodyMode,
|
|
10
|
+
identityOwnerAddress,
|
|
11
|
+
readCustodyMode,
|
|
12
|
+
readIdentityStateString,
|
|
13
|
+
} from '../../model/custody.js'
|
|
14
|
+
import { ensValidationReasonText, selectEnsStatus } from '../../model/ens.js'
|
|
15
|
+
import { shortAddress } from '../../model/format.js'
|
|
16
|
+
import { lastBackupLabel } from '../../model/identity.js'
|
|
17
|
+
import {
|
|
18
|
+
describeFixPlanItem,
|
|
19
|
+
fixPlanRequiresOwnerWallet,
|
|
20
|
+
reconcileWalletSetup,
|
|
21
|
+
type AgentReconciliation,
|
|
22
|
+
type RecordsFixPlan,
|
|
23
|
+
} from '../../reconciliation/index.js'
|
|
24
|
+
|
|
25
|
+
const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
|
|
26
|
+
|
|
27
|
+
type CustodyStep = Extract<Step, { kind: 'custody-model' | 'custody-advanced-confirm' | 'custody-simple-confirm' }>
|
|
28
|
+
|
|
29
|
+
interface CustodyEditFlowProps {
|
|
30
|
+
step: CustodyStep
|
|
31
|
+
reconciliation?: AgentReconciliation
|
|
32
|
+
vaultAddress?: Address
|
|
33
|
+
onSetStep: (step: Step) => void
|
|
34
|
+
onSwitchToAdvanced: (returnTo: Step, profileUpdates: ProfileUpdates) => void
|
|
35
|
+
onSwitchToSimple: (returnTo: Step, profileUpdates: ProfileUpdates) => void
|
|
36
|
+
onWithdrawToken: (returnTo: Step) => void
|
|
37
|
+
onReturnToVault: (returnTo: Step, vaultAddress: Address) => void
|
|
38
|
+
onResumeAdvanced: (returnTo: Step) => void
|
|
39
|
+
onManageOperatorWallets: () => void
|
|
40
|
+
onFixRecords: (plan: RecordsFixPlan) => void
|
|
41
|
+
onPrepareTransfer: () => void
|
|
42
|
+
onBack: () => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isCustodyEditStep(step: Step): step is CustodyStep {
|
|
46
|
+
return step.kind === 'custody-model'
|
|
47
|
+
|| step.kind === 'custody-advanced-confirm'
|
|
48
|
+
|| step.kind === 'custody-simple-confirm'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
|
|
52
|
+
step,
|
|
53
|
+
reconciliation,
|
|
54
|
+
vaultAddress,
|
|
55
|
+
onSetStep,
|
|
56
|
+
onSwitchToAdvanced,
|
|
57
|
+
onSwitchToSimple,
|
|
58
|
+
onWithdrawToken,
|
|
59
|
+
onReturnToVault,
|
|
60
|
+
onResumeAdvanced,
|
|
61
|
+
onManageOperatorWallets,
|
|
62
|
+
onFixRecords,
|
|
63
|
+
onPrepareTransfer,
|
|
64
|
+
onBack,
|
|
65
|
+
}) => {
|
|
66
|
+
const identity = step.identity
|
|
67
|
+
const registry = step.registry
|
|
68
|
+
const returnTo = step.returnTo
|
|
69
|
+
const state = (identity.state ?? {}) as Record<string, unknown>
|
|
70
|
+
const custodyMode = readCustodyMode(state)
|
|
71
|
+
const ownerAddress = identityOwnerAddress(identity, reconciliation?.onChainOwner)
|
|
72
|
+
const activeOperator = readIdentityStateString(state, 'activeOperatorAddress')
|
|
73
|
+
const approvedOperatorCount = Array.isArray(state.approvedOperatorWallets)
|
|
74
|
+
? (state.approvedOperatorWallets as unknown[]).length
|
|
75
|
+
: 0
|
|
76
|
+
const agentName = readIdentityStateString(state, 'name')
|
|
77
|
+
const tokenLabel = identity.agentId ? `Token #${identity.agentId}` : 'Token #unknown'
|
|
78
|
+
const tokenOwner = identity.ownerAddress ?? identity.address
|
|
79
|
+
|
|
80
|
+
const [fixPlan, setFixPlan] = React.useState<RecordsFixPlan | null>(null)
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
if (step.kind !== 'custody-model') return
|
|
83
|
+
if (custodyMode !== 'advanced') return
|
|
84
|
+
let cancelled = false
|
|
85
|
+
reconcileWalletSetup({ identity, registry })
|
|
86
|
+
.then(plan => { if (!cancelled) setFixPlan(plan) })
|
|
87
|
+
.catch(() => { if (!cancelled) setFixPlan(null) })
|
|
88
|
+
return () => { cancelled = true }
|
|
89
|
+
}, [identity, registry, step.kind, custodyMode])
|
|
90
|
+
|
|
91
|
+
if (step.kind === 'custody-model') {
|
|
92
|
+
type Action = 'switch-advanced' | 'switch-simple' | 'resume-advanced' | 'cancel-advanced' | 'withdraw-token' | 'return-to-vault' | 'manage-operator-wallets' | 'fix-records' | 'back'
|
|
93
|
+
const onChainCustody = reconciliation?.custody
|
|
94
|
+
const midFlow = onChainCustody === 'mid-flow-uri-pending'
|
|
95
|
+
const isAdvanced = onChainCustody === 'advanced' || midFlow || custodyMode === 'advanced'
|
|
96
|
+
const vaultHolds = onChainCustody === 'advanced' || midFlow
|
|
97
|
+
const subtitle = midFlow
|
|
98
|
+
? 'Advanced setup pending. This agent vault holds your token. Finish by publishing the first onchain update.'
|
|
99
|
+
: isAdvanced
|
|
100
|
+
? 'Advanced is active. Authorized operator wallets publish updates for this agent without an owner signature each time.'
|
|
101
|
+
: 'Simple is active. One wallet owns the token and signs every update.'
|
|
102
|
+
const modeLabel = midFlow ? 'Advanced (setup pending)' : displayCustodyMode(isAdvanced ? 'advanced' : 'simple')
|
|
103
|
+
const options: Array<{ value: Action; role?: 'section' | 'utility'; label: string; hint?: string }> = []
|
|
104
|
+
if (midFlow) {
|
|
105
|
+
options.push({ value: 'resume-advanced', role: 'section', label: 'Resume Setup' })
|
|
106
|
+
options.push({
|
|
107
|
+
value: 'resume-advanced',
|
|
108
|
+
label: 'Resume Advanced Setup',
|
|
109
|
+
hint: 'Sign once to publish onchain and finish the agent-vault switch.',
|
|
110
|
+
})
|
|
111
|
+
options.push({
|
|
112
|
+
value: 'cancel-advanced',
|
|
113
|
+
label: 'Cancel Advanced Setup',
|
|
114
|
+
hint: 'Unwrap the token back to the owner wallet and revert to simple.',
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
options.push({ value: 'switch-advanced', role: 'section', label: 'Custody' })
|
|
118
|
+
if (!isAdvanced) {
|
|
119
|
+
options.push({
|
|
120
|
+
value: 'switch-advanced',
|
|
121
|
+
label: 'Switch to Advanced',
|
|
122
|
+
hint: 'Deposit this token into its own OperatorVault so operator wallets can publish updates onchain.',
|
|
123
|
+
})
|
|
124
|
+
} else {
|
|
125
|
+
if (!midFlow) {
|
|
126
|
+
options.push({
|
|
127
|
+
value: 'switch-simple',
|
|
128
|
+
label: 'Switch to Simple',
|
|
129
|
+
hint: 'Unwrap the token and revoke operator delegations.',
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
if (vaultHolds) {
|
|
133
|
+
options.push({
|
|
134
|
+
value: 'withdraw-token',
|
|
135
|
+
label: 'Withdraw Token',
|
|
136
|
+
hint: 'Unwrap this token to the owner wallet. Vault setup stays for easy redeposit.',
|
|
137
|
+
})
|
|
138
|
+
} else if (vaultAddress) {
|
|
139
|
+
options.push({
|
|
140
|
+
value: 'return-to-vault',
|
|
141
|
+
label: 'Return Token to Vault',
|
|
142
|
+
hint: 'Redeposit this token to its agent vault. No redeploy, no operator re-add.',
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
options.push({ value: 'manage-operator-wallets', role: 'section', label: 'Operators' })
|
|
146
|
+
options.push({
|
|
147
|
+
value: 'manage-operator-wallets',
|
|
148
|
+
label: 'Manage Operators',
|
|
149
|
+
hint: 'Add or revoke wallets that can publish updates onchain.',
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
const hasFixablePlan = fixPlan !== null && fixPlanRequiresOwnerWallet(fixPlan)
|
|
153
|
+
if (hasFixablePlan) {
|
|
154
|
+
options.push({ value: 'fix-records', role: 'section', label: 'Records Out Of Sync' })
|
|
155
|
+
options.push({
|
|
156
|
+
value: 'fix-records',
|
|
157
|
+
label: 'Fix Records (Owner Wallet)',
|
|
158
|
+
hint: 'Sync ENS resolver approvals with the operator wallet list.',
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
options.push({ value: 'back', role: 'section', label: 'Navigation' })
|
|
162
|
+
options.push({ value: 'back', label: 'Back', hint: 'Return to Identity Hub', role: 'utility' })
|
|
163
|
+
const notice = step.kind === 'custody-model' ? step.notice : undefined
|
|
164
|
+
return (
|
|
165
|
+
<Surface title="Custody Mode" subtitle={subtitle} footer={footerHint('enter select · esc back')}>
|
|
166
|
+
{notice ? (
|
|
167
|
+
<Box marginBottom={1}>
|
|
168
|
+
<Text color={theme.accentPeriwinkle}>{notice}</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
) : null}
|
|
171
|
+
<Box flexDirection="column">
|
|
172
|
+
{(() => {
|
|
173
|
+
const ensStatus = selectEnsStatus(identity)
|
|
174
|
+
return (
|
|
175
|
+
<Text>
|
|
176
|
+
<Text color={theme.dim}>{'ENS'.padEnd(14)}</Text>
|
|
177
|
+
{ensStatus.kind === 'linked'
|
|
178
|
+
? <Text color={theme.accentPeriwinkle}>{ensStatus.name}</Text>
|
|
179
|
+
: ensStatus.kind === 'issue'
|
|
180
|
+
? <Text color={theme.accentError}>{ensStatus.name} ({ensValidationReasonText(ensStatus.reason)})</Text>
|
|
181
|
+
: <Text color={theme.dim}>Not Linked</Text>}
|
|
182
|
+
</Text>
|
|
183
|
+
)
|
|
184
|
+
})()}
|
|
185
|
+
<Row label="Custody" value={modeLabel} />
|
|
186
|
+
<Row label="Owner" value={shortAddress(ownerAddress || tokenOwner)} />
|
|
187
|
+
{isAdvanced && vaultAddress ? <Row label="Agent Vault" value={shortAddress(vaultAddress)} /> : null}
|
|
188
|
+
{isAdvanced ? (
|
|
189
|
+
<Row
|
|
190
|
+
label="Operators"
|
|
191
|
+
value={approvedOperatorCount > 1
|
|
192
|
+
? `${approvedOperatorCount} authorized${activeOperator ? ` (active ${shortAddress(activeOperator)})` : ''}`
|
|
193
|
+
: activeOperator
|
|
194
|
+
? shortAddress(activeOperator)
|
|
195
|
+
: 'None Authorized'}
|
|
196
|
+
muted={!activeOperator && approvedOperatorCount === 0}
|
|
197
|
+
/>
|
|
198
|
+
) : null}
|
|
199
|
+
{(() => {
|
|
200
|
+
const lastBackup = lastBackupLabel(identity)
|
|
201
|
+
return <Row label="Last Saved" value={lastBackup} muted={lastBackup === 'never'} />
|
|
202
|
+
})()}
|
|
203
|
+
</Box>
|
|
204
|
+
{fixPlan && fixPlan.items.length > 0 ? (
|
|
205
|
+
<Box marginTop={1} flexDirection="column">
|
|
206
|
+
<Text color={theme.accentPeriwinkle} bold>Records out of sync:</Text>
|
|
207
|
+
{fixPlan.items.map((item, idx) => (
|
|
208
|
+
<Text key={idx} color={theme.dim}>· {describeFixPlanItem(item)}</Text>
|
|
209
|
+
))}
|
|
210
|
+
</Box>
|
|
211
|
+
) : null}
|
|
212
|
+
<Box marginTop={1}>
|
|
213
|
+
<Select<Action>
|
|
214
|
+
options={options}
|
|
215
|
+
hintLayout="inline"
|
|
216
|
+
onSubmit={choice => {
|
|
217
|
+
if (choice === 'back') return onBack()
|
|
218
|
+
if (choice === 'manage-operator-wallets') return onManageOperatorWallets()
|
|
219
|
+
if (choice === 'withdraw-token') return onWithdrawToken(returnTo ?? { kind: 'menu' })
|
|
220
|
+
if (choice === 'return-to-vault') {
|
|
221
|
+
if (!vaultAddress) return
|
|
222
|
+
return onReturnToVault(returnTo ?? { kind: 'menu' }, vaultAddress)
|
|
223
|
+
}
|
|
224
|
+
if (choice === 'resume-advanced') return onResumeAdvanced(returnTo ?? { kind: 'menu' })
|
|
225
|
+
if (choice === 'cancel-advanced') {
|
|
226
|
+
onSetStep({ kind: 'custody-simple-confirm', identity, registry, returnTo })
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
if (choice === 'fix-records') {
|
|
230
|
+
if (fixPlan) onFixRecords(fixPlan)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
if (choice === 'switch-advanced') {
|
|
234
|
+
onSetStep({ kind: 'custody-advanced-confirm', identity, registry, returnTo })
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
if (choice === 'switch-simple') {
|
|
238
|
+
onSetStep({ kind: 'custody-simple-confirm', identity, registry, returnTo })
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
}}
|
|
242
|
+
onCancel={onBack}
|
|
243
|
+
/>
|
|
244
|
+
</Box>
|
|
245
|
+
</Surface>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (step.kind === 'custody-advanced-confirm') {
|
|
250
|
+
type Action = 'confirm' | 'transfer' | 'back'
|
|
251
|
+
return (
|
|
252
|
+
<Surface
|
|
253
|
+
title="Switch to Advanced"
|
|
254
|
+
subtitle="Move this token into its own OperatorVault so authorized operator wallets can update this agent onchain without your signature each time."
|
|
255
|
+
footer={footerHint('enter confirm, esc back')}
|
|
256
|
+
>
|
|
257
|
+
<Box flexDirection="column">
|
|
258
|
+
<Row label="Token" value={tokenLabel} />
|
|
259
|
+
{agentName ? <Row label="Name" value={agentName} /> : null}
|
|
260
|
+
<Row label="Owner Wallet" value={shortAddress(ownerAddress || tokenOwner)} />
|
|
261
|
+
<Text color={theme.textSubtle}>You sign once now to deposit token #{identity.agentId ?? 'unknown'} into a dedicated OperatorVault.</Text>
|
|
262
|
+
<Text color={theme.textSubtle}>This vault can hold only this ERC-8004 token.</Text>
|
|
263
|
+
<Text color={theme.textSubtle}>Other agent tokens use their own vaults.</Text>
|
|
264
|
+
<Text color={theme.textSubtle}>After that, operator wallets you authorize can publish updates for this agent.</Text>
|
|
265
|
+
<Box marginTop={1} flexDirection="column">
|
|
266
|
+
<Text color={theme.accentBlue}>Want a different wallet to be the owner?</Text>
|
|
267
|
+
<Text color={theme.textSubtle}>Move the token there first via Prepare Token Transfer; your continuity files come along.</Text>
|
|
268
|
+
</Box>
|
|
269
|
+
</Box>
|
|
270
|
+
<Box marginTop={1}>
|
|
271
|
+
<Select<Action>
|
|
272
|
+
options={[
|
|
273
|
+
{ value: 'confirm', role: 'section', label: 'Confirm' },
|
|
274
|
+
{ value: 'confirm', label: 'Yes, Switch to Advanced', hint: `Sign with ${shortAddress(ownerAddress || tokenOwner)} to deposit this token into its agent vault` },
|
|
275
|
+
{ value: 'transfer', role: 'section', label: 'Move Token First' },
|
|
276
|
+
{ value: 'transfer', label: 'Prepare Token Transfer', hint: 'Move the token to a different wallet first, with snapshot handoff' },
|
|
277
|
+
{ value: 'back', role: 'section', label: 'Cancel' },
|
|
278
|
+
{ value: 'back', label: 'No, Go Back', hint: 'Return without changing custody', role: 'utility' },
|
|
279
|
+
]}
|
|
280
|
+
hintLayout="inline"
|
|
281
|
+
onSubmit={choice => {
|
|
282
|
+
if (choice === 'back') return onBack()
|
|
283
|
+
if (choice === 'transfer') return onPrepareTransfer()
|
|
284
|
+
const updates: ProfileUpdates = {
|
|
285
|
+
custodyMode: 'advanced',
|
|
286
|
+
ownerAddress: ownerAddress || tokenOwner,
|
|
287
|
+
bumpRestoreAccessEpoch: true,
|
|
288
|
+
custodyPhase: 'switch-advanced',
|
|
289
|
+
}
|
|
290
|
+
onSwitchToAdvanced(returnTo ?? { kind: 'menu' }, updates)
|
|
291
|
+
}}
|
|
292
|
+
onCancel={onBack}
|
|
293
|
+
/>
|
|
294
|
+
</Box>
|
|
295
|
+
</Surface>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
type Action = 'confirm' | 'back'
|
|
300
|
+
return (
|
|
301
|
+
<Surface
|
|
302
|
+
title="Switch to Simple"
|
|
303
|
+
subtitle="Unwraps this ERC-8004 token from its agent vault and returns it directly to the owner wallet."
|
|
304
|
+
footer={footerHint('enter confirm · esc back')}
|
|
305
|
+
>
|
|
306
|
+
<Box flexDirection="column">
|
|
307
|
+
<Row label="Token" value={tokenLabel} />
|
|
308
|
+
{agentName ? <Row label="Name" value={agentName} /> : null}
|
|
309
|
+
<Text> </Text>
|
|
310
|
+
<Text color={theme.accentBlue}>Operators lose decrypt access on future snapshots immediately.</Text>
|
|
311
|
+
<Text color={theme.textSubtle}>Operator approvals are cleared from local state for future snapshots. Revoke onchain via Manage Operators first if needed.</Text>
|
|
312
|
+
<Text color={theme.textSubtle}>This switch calls the agent vault's unwrap function for this token, so the owner wallet must sign the transaction.</Text>
|
|
313
|
+
</Box>
|
|
314
|
+
<Box marginTop={1}>
|
|
315
|
+
<Select<Action>
|
|
316
|
+
options={[
|
|
317
|
+
{ value: 'confirm', role: 'section', label: 'Confirm' },
|
|
318
|
+
{ value: 'confirm', label: 'Yes, Switch to Simple', hint: `Sign with the owner wallet to unwrap ${tokenLabel} from its agent vault` },
|
|
319
|
+
{ value: 'back', role: 'section', label: 'Cancel' },
|
|
320
|
+
{ value: 'back', label: 'No, Go Back', hint: 'Return without changing custody', role: 'utility' },
|
|
321
|
+
]}
|
|
322
|
+
hintLayout="inline"
|
|
323
|
+
onSubmit={choice => {
|
|
324
|
+
if (choice === 'back') return onBack()
|
|
325
|
+
const updates: ProfileUpdates = {
|
|
326
|
+
custodyMode: 'simple',
|
|
327
|
+
bumpRestoreAccessEpoch: true,
|
|
328
|
+
custodyPhase: 'switch-simple',
|
|
329
|
+
approvedOperatorWallets: [],
|
|
330
|
+
activeOperatorAddress: '',
|
|
331
|
+
operatorVaultAddress: '',
|
|
332
|
+
}
|
|
333
|
+
onSwitchToSimple(returnTo ?? { kind: 'menu' }, updates)
|
|
334
|
+
}}
|
|
335
|
+
onCancel={onBack}
|
|
336
|
+
/>
|
|
337
|
+
</Box>
|
|
338
|
+
</Surface>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const Row: React.FC<{ label: string; value: string; muted?: boolean }> = ({ label, value, muted }) => (
|
|
343
|
+
<Text>
|
|
344
|
+
<Text color={theme.dim}>{label.padEnd(14)}</Text>
|
|
345
|
+
<Text color={muted ? theme.dim : theme.text}>{value}</Text>
|
|
346
|
+
</Text>
|
|
347
|
+
)
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { encodeDeployData, getAddress, type Address, type Hex, type PublicClient } from 'viem'
|
|
2
|
+
import {
|
|
3
|
+
confirmAgentWithdrawnFromVault,
|
|
4
|
+
encodeDepositAgent,
|
|
5
|
+
encodeUnwrapAgent,
|
|
6
|
+
isAgentInVault,
|
|
7
|
+
resolveConfiguredOperatorVaultAddress,
|
|
8
|
+
OPERATOR_VAULT_ABI,
|
|
9
|
+
OPERATOR_VAULT_DEPLOY_BYTECODE,
|
|
10
|
+
assertVaultBytecode,
|
|
11
|
+
} from '../../../registry/operatorVault.js'
|
|
12
|
+
import {
|
|
13
|
+
createErc8004PublicClient,
|
|
14
|
+
type Erc8004RegistryConfig,
|
|
15
|
+
} from '../../../registry/erc8004.js'
|
|
16
|
+
import type { EthagentIdentity } from '../../../../storage/config.js'
|
|
17
|
+
import { readOperatorVaultAddressField, readOwnerAddressField } from '../../../identityCompat.js'
|
|
18
|
+
import { prepareTransactionGasFee, sendBrowserWalletTransaction } from '../../../wallet/browserWallet.js'
|
|
19
|
+
import { acquireTxGuard, releaseTxGuard, type TxGuardKind } from '../../txGuard.js'
|
|
20
|
+
import { awaitConfirmedReceipt } from '../../effects/receipts.js'
|
|
21
|
+
import type { EffectCallbacks } from '../../effects/types.js'
|
|
22
|
+
import { readCustodyMode } from '../../model/custody.js'
|
|
23
|
+
|
|
24
|
+
export function resolveOperatorVaultAddress(
|
|
25
|
+
identity: EthagentIdentity,
|
|
26
|
+
operatorVaults?: Readonly<Record<string, string>>,
|
|
27
|
+
): Address | undefined {
|
|
28
|
+
const identityVault = readOperatorVaultAddressField(identity.state as Record<string, unknown> | undefined)
|
|
29
|
+
if (identityVault) return getAddress(identityVault)
|
|
30
|
+
if (readCustodyMode(identity.state as Record<string, unknown> | undefined) !== 'advanced') return undefined
|
|
31
|
+
if (!identity.chainId) return undefined
|
|
32
|
+
return resolveConfiguredOperatorVaultAddress(operatorVaults, identity.chainId)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function withTxGuard<T>(kind: TxGuardKind, fn: () => Promise<T>): Promise<T> {
|
|
36
|
+
acquireTxGuard(kind)
|
|
37
|
+
try {
|
|
38
|
+
return await fn()
|
|
39
|
+
} finally {
|
|
40
|
+
releaseTxGuard(kind)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function runVaultDeployTransaction(args: {
|
|
45
|
+
registry: Erc8004RegistryConfig
|
|
46
|
+
walletAddress: Address
|
|
47
|
+
agentId: bigint
|
|
48
|
+
callbacks: EffectCallbacks
|
|
49
|
+
publicClient?: Pick<PublicClient, 'waitForTransactionReceipt' | 'getBytecode'>
|
|
50
|
+
flowId?: string
|
|
51
|
+
}): Promise<{ txHash: Hex; vaultAddress: Address }> {
|
|
52
|
+
return withTxGuard('vault-deploy', () => runVaultDeployTransactionInner(args))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runVaultDeployTransactionInner(args: {
|
|
56
|
+
registry: Erc8004RegistryConfig
|
|
57
|
+
walletAddress: Address
|
|
58
|
+
agentId: bigint
|
|
59
|
+
callbacks: EffectCallbacks
|
|
60
|
+
publicClient?: Pick<PublicClient, 'waitForTransactionReceipt' | 'getBytecode'>
|
|
61
|
+
flowId?: string
|
|
62
|
+
}): Promise<{ txHash: Hex; vaultAddress: Address }> {
|
|
63
|
+
const walletAddress = getAddress(args.walletAddress)
|
|
64
|
+
const registryAddress = getAddress(args.registry.identityRegistryAddress)
|
|
65
|
+
const deployData = encodeDeployData({
|
|
66
|
+
abi: OPERATOR_VAULT_ABI,
|
|
67
|
+
bytecode: OPERATOR_VAULT_DEPLOY_BYTECODE,
|
|
68
|
+
args: [registryAddress, args.agentId],
|
|
69
|
+
})
|
|
70
|
+
const gasFeeClient = createErc8004PublicClient(args.registry)
|
|
71
|
+
const gasFee = await prepareTransactionGasFee({
|
|
72
|
+
client: gasFeeClient,
|
|
73
|
+
account: walletAddress,
|
|
74
|
+
data: deployData,
|
|
75
|
+
})
|
|
76
|
+
const result = await sendBrowserWalletTransaction({
|
|
77
|
+
chainId: args.registry.chainId,
|
|
78
|
+
expectedAccount: walletAddress,
|
|
79
|
+
data: deployData,
|
|
80
|
+
gas: gasFee.gas,
|
|
81
|
+
maxFeePerGas: gasFee.maxFeePerGas,
|
|
82
|
+
maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas,
|
|
83
|
+
purpose: 'deploy-agent-vault',
|
|
84
|
+
onReady: args.callbacks.onWalletReady,
|
|
85
|
+
...(args.flowId ? { flowId: args.flowId } : {}),
|
|
86
|
+
})
|
|
87
|
+
args.callbacks.onWalletReady(null)
|
|
88
|
+
const client = args.publicClient ?? createErc8004PublicClient(args.registry)
|
|
89
|
+
const receipt = await awaitConfirmedReceipt(client, result.txHash, 'Operator delegation vault deploy', { kind: 'vault-deploy', chainId: args.registry.chainId })
|
|
90
|
+
if (!receipt.contractAddress) {
|
|
91
|
+
throw new Error('Operator delegation vault deploy receipt is missing contractAddress; the transaction was not a contract creation')
|
|
92
|
+
}
|
|
93
|
+
const vaultAddress = getAddress(receipt.contractAddress)
|
|
94
|
+
await assertVaultBytecode(client, vaultAddress, result.txHash)
|
|
95
|
+
return { txHash: result.txHash, vaultAddress }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function runVaultDepositTransaction(args: {
|
|
99
|
+
identity: EthagentIdentity
|
|
100
|
+
registry: Erc8004RegistryConfig
|
|
101
|
+
vaultAddress: Address
|
|
102
|
+
callbacks: EffectCallbacks
|
|
103
|
+
flowId?: string
|
|
104
|
+
}): Promise<{ txHash: string }> {
|
|
105
|
+
return withTxGuard('vault-deposit', () => runVaultDepositTransactionInner(args))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function runVaultDepositTransactionInner(args: {
|
|
109
|
+
identity: EthagentIdentity
|
|
110
|
+
registry: Erc8004RegistryConfig
|
|
111
|
+
vaultAddress: Address
|
|
112
|
+
callbacks: EffectCallbacks
|
|
113
|
+
flowId?: string
|
|
114
|
+
}): Promise<{ txHash: string }> {
|
|
115
|
+
const { identity, registry, vaultAddress } = args
|
|
116
|
+
if (!identity.agentId) {
|
|
117
|
+
throw new Error('Cannot deposit token to operator delegation vault: agent token ID is missing')
|
|
118
|
+
}
|
|
119
|
+
const tokenOwner = getAddress(identity.ownerAddress ?? identity.address)
|
|
120
|
+
await assertVaultCanAcceptAgent({
|
|
121
|
+
registry,
|
|
122
|
+
vaultAddress,
|
|
123
|
+
agentId: BigInt(identity.agentId),
|
|
124
|
+
})
|
|
125
|
+
const encoded = encodeDepositAgent({
|
|
126
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
127
|
+
agentId: BigInt(identity.agentId),
|
|
128
|
+
walletAddress: tokenOwner,
|
|
129
|
+
vaultAddress,
|
|
130
|
+
})
|
|
131
|
+
const gasFeeClient = createErc8004PublicClient(registry)
|
|
132
|
+
const gasFee = await prepareTransactionGasFee({
|
|
133
|
+
client: gasFeeClient,
|
|
134
|
+
account: tokenOwner,
|
|
135
|
+
to: encoded.to,
|
|
136
|
+
data: encoded.data,
|
|
137
|
+
})
|
|
138
|
+
const result = await sendBrowserWalletTransaction({
|
|
139
|
+
chainId: registry.chainId,
|
|
140
|
+
expectedAccount: tokenOwner,
|
|
141
|
+
to: encoded.to,
|
|
142
|
+
data: encoded.data,
|
|
143
|
+
gas: gasFee.gas,
|
|
144
|
+
maxFeePerGas: gasFee.maxFeePerGas,
|
|
145
|
+
maxPriorityFeePerGas: gasFee.maxPriorityFeePerGas,
|
|
146
|
+
purpose: 'deposit-agent-vault',
|
|
147
|
+
onReady: args.callbacks.onWalletReady,
|
|
148
|
+
...(args.flowId ? { flowId: args.flowId } : {}),
|
|
149
|
+
})
|
|
150
|
+
args.callbacks.onWalletReady(null)
|
|
151
|
+
const depositClient = createErc8004PublicClient(registry)
|
|
152
|
+
await awaitConfirmedReceipt(
|
|
153
|
+
depositClient,
|
|
154
|
+
result.txHash as Hex,
|
|
155
|
+
'Operator delegation vault deposit',
|
|
156
|
+
{ kind: 'vault-deposit', chainId: registry.chainId },
|
|
157
|
+
)
|
|
158
|
+
return { txHash: result.txHash }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function assertVaultCanAcceptAgent(args: {
|
|
162
|
+
registry: Erc8004RegistryConfig
|
|
163
|
+
vaultAddress: Address
|
|
164
|
+
agentId: bigint
|
|
165
|
+
}): Promise<void> {
|
|
166
|
+
const client = createErc8004PublicClient(args.registry)
|
|
167
|
+
let held: readonly [Address, bigint, Address]
|
|
168
|
+
try {
|
|
169
|
+
held = await client.readContract({
|
|
170
|
+
address: getAddress(args.vaultAddress),
|
|
171
|
+
abi: OPERATOR_VAULT_ABI,
|
|
172
|
+
functionName: 'heldAgent',
|
|
173
|
+
}) as readonly [Address, bigint, Address]
|
|
174
|
+
} catch {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
const [heldRegistry, heldAgentId, heldOwner] = held
|
|
178
|
+
if (!heldOwner || heldOwner.toLowerCase() === '0x0000000000000000000000000000000000000000') return
|
|
179
|
+
const expectedRegistry = getAddress(args.registry.identityRegistryAddress)
|
|
180
|
+
const sameAgent = heldRegistry.toLowerCase() === expectedRegistry.toLowerCase() && heldAgentId === args.agentId
|
|
181
|
+
if (sameAgent) {
|
|
182
|
+
throw new Error(`Agent vault ${getAddress(args.vaultAddress)} already holds ERC-8004 token #${args.agentId.toString()}. Publish the pending update instead of depositing again.`)
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Agent vault ${getAddress(args.vaultAddress)} already holds ERC-8004 token #${heldAgentId.toString()} for registry ${getAddress(heldRegistry)}. Deploy a fresh vault for this agent.`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function runVaultUnwrapTransaction(args: {
|
|
188
|
+
identity: EthagentIdentity
|
|
189
|
+
registry: Erc8004RegistryConfig
|
|
190
|
+
vaultAddress: Address
|
|
191
|
+
callbacks: EffectCallbacks
|
|
192
|
+
flowId?: string
|
|
193
|
+
agentId?: bigint
|
|
194
|
+
}): Promise<{ txHash: string } | null> {
|
|
195
|
+
return withTxGuard('vault-unwrap', () => runVaultUnwrapTransactionInner(args))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function runVaultUnwrapTransactionInner(args: {
|
|
199
|
+
identity: EthagentIdentity
|
|
200
|
+
registry: Erc8004RegistryConfig
|
|
201
|
+
vaultAddress: Address
|
|
202
|
+
callbacks: EffectCallbacks
|
|
203
|
+
flowId?: string
|
|
204
|
+
agentId?: bigint
|
|
205
|
+
}): Promise<{ txHash: string } | null> {
|
|
206
|
+
const { identity, registry, vaultAddress } = args
|
|
207
|
+
const targetAgentId = args.agentId ?? (identity.agentId ? BigInt(identity.agentId) : undefined)
|
|
208
|
+
if (targetAgentId === undefined) {
|
|
209
|
+
throw new Error('Cannot unwrap token from operator delegation vault: agent token ID is missing')
|
|
210
|
+
}
|
|
211
|
+
const baseState = (identity.state ?? {}) as Record<string, unknown>
|
|
212
|
+
const ownerAddressRaw = readOwnerAddressField(baseState)
|
|
213
|
+
const ownerAddress = ownerAddressRaw
|
|
214
|
+
? getAddress(ownerAddressRaw)
|
|
215
|
+
: getAddress(identity.ownerAddress ?? identity.address)
|
|
216
|
+
const publicClient = createErc8004PublicClient(registry)
|
|
217
|
+
const status = await isAgentInVault({
|
|
218
|
+
client: publicClient,
|
|
219
|
+
vaultAddress,
|
|
220
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
221
|
+
agentId: targetAgentId,
|
|
222
|
+
})
|
|
223
|
+
if (!status.inVault) return null
|
|
224
|
+
const encoded = encodeUnwrapAgent({
|
|
225
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
226
|
+
agentId: targetAgentId,
|
|
227
|
+
recipient: ownerAddress,
|
|
228
|
+
vaultAddress,
|
|
229
|
+
})
|
|
230
|
+
const result = await sendBrowserWalletTransaction({
|
|
231
|
+
chainId: registry.chainId,
|
|
232
|
+
expectedAccount: ownerAddress,
|
|
233
|
+
to: encoded.to,
|
|
234
|
+
data: encoded.data,
|
|
235
|
+
purpose: 'unwrap-agent-vault',
|
|
236
|
+
onReady: args.callbacks.onWalletReady,
|
|
237
|
+
...(args.flowId ? { flowId: args.flowId } : {}),
|
|
238
|
+
})
|
|
239
|
+
args.callbacks.onWalletReady(null)
|
|
240
|
+
await awaitConfirmedReceipt(
|
|
241
|
+
publicClient,
|
|
242
|
+
result.txHash as Hex,
|
|
243
|
+
'Operator delegation vault unwrap',
|
|
244
|
+
{ kind: 'vault-unwrap', chainId: registry.chainId },
|
|
245
|
+
)
|
|
246
|
+
await confirmAgentWithdrawnFromVault({
|
|
247
|
+
client: publicClient,
|
|
248
|
+
vaultAddress,
|
|
249
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
250
|
+
agentId: targetAgentId,
|
|
251
|
+
recipient: ownerAddress,
|
|
252
|
+
})
|
|
253
|
+
return { txHash: result.txHash }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function runVaultWithdrawTransaction(args: {
|
|
257
|
+
identity: EthagentIdentity
|
|
258
|
+
registry: Erc8004RegistryConfig
|
|
259
|
+
vaultAddress: Address
|
|
260
|
+
callbacks: EffectCallbacks
|
|
261
|
+
agentId?: bigint
|
|
262
|
+
}): Promise<{ txHash: string; recipient: Address }> {
|
|
263
|
+
return withTxGuard('vault-withdraw', () => runVaultWithdrawTransactionInner(args))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function runVaultWithdrawTransactionInner(args: {
|
|
267
|
+
identity: EthagentIdentity
|
|
268
|
+
registry: Erc8004RegistryConfig
|
|
269
|
+
vaultAddress: Address
|
|
270
|
+
callbacks: EffectCallbacks
|
|
271
|
+
agentId?: bigint
|
|
272
|
+
}): Promise<{ txHash: string; recipient: Address }> {
|
|
273
|
+
const { identity, registry, vaultAddress } = args
|
|
274
|
+
const targetAgentId = args.agentId ?? (identity.agentId ? BigInt(identity.agentId) : undefined)
|
|
275
|
+
if (targetAgentId === undefined) {
|
|
276
|
+
throw new Error('Cannot withdraw token: agent token ID is missing')
|
|
277
|
+
}
|
|
278
|
+
const publicClient = createErc8004PublicClient(registry)
|
|
279
|
+
const status = await isAgentInVault({
|
|
280
|
+
client: publicClient,
|
|
281
|
+
vaultAddress,
|
|
282
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
283
|
+
agentId: targetAgentId,
|
|
284
|
+
})
|
|
285
|
+
if (!status.inVault) {
|
|
286
|
+
throw new Error('Token is not currently held by the vault, nothing to unwrap')
|
|
287
|
+
}
|
|
288
|
+
if (!status.ownerAddress) {
|
|
289
|
+
throw new Error('Vault has no recorded depositor for this token; cannot determine recipient')
|
|
290
|
+
}
|
|
291
|
+
const recipient = getAddress(status.ownerAddress)
|
|
292
|
+
const encoded = encodeUnwrapAgent({
|
|
293
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
294
|
+
agentId: targetAgentId,
|
|
295
|
+
recipient,
|
|
296
|
+
vaultAddress,
|
|
297
|
+
})
|
|
298
|
+
const result = await sendBrowserWalletTransaction({
|
|
299
|
+
chainId: registry.chainId,
|
|
300
|
+
expectedAccount: recipient,
|
|
301
|
+
to: encoded.to,
|
|
302
|
+
data: encoded.data,
|
|
303
|
+
purpose: 'withdraw-vault',
|
|
304
|
+
onReady: args.callbacks.onWalletReady,
|
|
305
|
+
})
|
|
306
|
+
args.callbacks.onWalletReady(null)
|
|
307
|
+
await awaitConfirmedReceipt(
|
|
308
|
+
publicClient,
|
|
309
|
+
result.txHash as Hex,
|
|
310
|
+
'Operator delegation vault withdraw',
|
|
311
|
+
{ kind: 'vault-withdraw', chainId: registry.chainId },
|
|
312
|
+
)
|
|
313
|
+
await confirmAgentWithdrawnFromVault({
|
|
314
|
+
client: publicClient,
|
|
315
|
+
vaultAddress,
|
|
316
|
+
registry: getAddress(registry.identityRegistryAddress),
|
|
317
|
+
agentId: targetAgentId,
|
|
318
|
+
recipient,
|
|
319
|
+
})
|
|
320
|
+
return { txHash: result.txHash, recipient }
|
|
321
|
+
}
|