ethagent 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { getAddress, type Address } from 'viem'
|
|
2
|
+
import type { EthagentIdentity } from '../../../../storage/config.js'
|
|
3
|
+
import {
|
|
4
|
+
createErc8004PublicClient,
|
|
5
|
+
type Erc8004RegistryConfig,
|
|
6
|
+
} from '../../../registry/erc8004.js'
|
|
7
|
+
import {
|
|
8
|
+
OPERATOR_VAULT_ABI,
|
|
9
|
+
encodeSetMetadataOperator,
|
|
10
|
+
readMetadataOperators,
|
|
11
|
+
} from '../../../registry/operatorVault.js'
|
|
12
|
+
import {
|
|
13
|
+
sendBrowserWalletTransaction,
|
|
14
|
+
type WalletPurpose,
|
|
15
|
+
} from '../../../wallet/browserWallet.js'
|
|
16
|
+
import {
|
|
17
|
+
computeApprovalDiff,
|
|
18
|
+
encodeResolverApprovalChanges,
|
|
19
|
+
verifyResolverApprovalsLanded,
|
|
20
|
+
type ApprovalDiff,
|
|
21
|
+
} from '../../reconciliation/index.js'
|
|
22
|
+
import { normalizeApprovedOperatorWallets } from '../../operatorWallets.js'
|
|
23
|
+
import { readOwnerAddressField } from '../../../identityCompat.js'
|
|
24
|
+
import { localContinuitySnapshotContentHashes } from '../../../continuity/storage.js'
|
|
25
|
+
import { updatePublishedContinuitySnapshotContentHashes } from '../../../continuity/snapshots.js'
|
|
26
|
+
import type { EffectCallbacks } from '../types.js'
|
|
27
|
+
import { awaitConfirmedReceipt, awaitOptionalReceipt } from '../receipts.js'
|
|
28
|
+
import { createMainnetEnsPublicClient } from '../ens/transactions.js'
|
|
29
|
+
|
|
30
|
+
export function resolverSyncWarningMessage(err: unknown): string {
|
|
31
|
+
return err instanceof Error ? err.message : String(err)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function appendResolverSyncWarning(message: string, warning: string | null): string {
|
|
35
|
+
if (!warning) return message
|
|
36
|
+
return `${message}\n\nWarning: ${warning}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function syncResolverApprovalsAfterOwnerSave(args: {
|
|
40
|
+
beforeIdentity: EthagentIdentity
|
|
41
|
+
afterIdentity: EthagentIdentity
|
|
42
|
+
registry: Erc8004RegistryConfig
|
|
43
|
+
vaultAddress?: Address
|
|
44
|
+
callbacks: EffectCallbacks
|
|
45
|
+
}): Promise<void> {
|
|
46
|
+
const beforeState = (args.beforeIdentity.state ?? {}) as Record<string, unknown>
|
|
47
|
+
const afterState = (args.afterIdentity.state ?? {}) as Record<string, unknown>
|
|
48
|
+
const ensName = typeof afterState.ensName === 'string' ? afterState.ensName.trim() : ''
|
|
49
|
+
const before = normalizeApprovedOperatorWallets(beforeState.approvedOperatorWallets)
|
|
50
|
+
const after = normalizeApprovedOperatorWallets(afterState.approvedOperatorWallets)
|
|
51
|
+
const diff = computeApprovalDiff(before, after)
|
|
52
|
+
if (diff.added.length === 0 && diff.removed.length === 0) return
|
|
53
|
+
|
|
54
|
+
const ownerAddressRaw = readOwnerAddressField(afterState) ?? args.afterIdentity.ownerAddress ?? args.afterIdentity.address
|
|
55
|
+
const ownerAddress = getAddress(ownerAddressRaw)
|
|
56
|
+
|
|
57
|
+
if (ensName) {
|
|
58
|
+
let encoded
|
|
59
|
+
try {
|
|
60
|
+
encoded = await encodeResolverApprovalChanges({ ensName, diff })
|
|
61
|
+
} catch {
|
|
62
|
+
encoded = null
|
|
63
|
+
}
|
|
64
|
+
if (encoded) {
|
|
65
|
+
const purpose: WalletPurpose = diff.removed.length > 0 && diff.added.length === 0
|
|
66
|
+
? 'revoke-operator-wallet-resolver'
|
|
67
|
+
: 'authorize-operator-wallet-resolver'
|
|
68
|
+
|
|
69
|
+
const tx = await sendBrowserWalletTransaction({
|
|
70
|
+
chainId: 1,
|
|
71
|
+
expectedAccount: ownerAddress,
|
|
72
|
+
to: encoded.resolverAddress,
|
|
73
|
+
data: encoded.data,
|
|
74
|
+
onReady: args.callbacks.onWalletReady,
|
|
75
|
+
purpose,
|
|
76
|
+
})
|
|
77
|
+
args.callbacks.onWalletReady(null)
|
|
78
|
+
const client = createMainnetEnsPublicClient()
|
|
79
|
+
await awaitOptionalReceipt(client, tx.txHash, 'Resolver delegation sync')
|
|
80
|
+
await verifyResolverApprovalsLanded({
|
|
81
|
+
ensName,
|
|
82
|
+
ownerAddress: ownerAddress,
|
|
83
|
+
resolverAddress: encoded.resolverAddress,
|
|
84
|
+
added: encoded.added,
|
|
85
|
+
removed: encoded.removed,
|
|
86
|
+
client,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await syncVaultMetadataOperatorsAfterOwnerSave({
|
|
92
|
+
afterIdentity: args.afterIdentity,
|
|
93
|
+
registry: args.registry,
|
|
94
|
+
vaultAddress: args.vaultAddress,
|
|
95
|
+
diff,
|
|
96
|
+
ownerAddress,
|
|
97
|
+
callbacks: args.callbacks,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function syncVaultMetadataOperatorsAfterOwnerSave(args: {
|
|
102
|
+
afterIdentity: EthagentIdentity
|
|
103
|
+
registry: Erc8004RegistryConfig
|
|
104
|
+
vaultAddress: Address | undefined
|
|
105
|
+
diff: ApprovalDiff
|
|
106
|
+
ownerAddress: Address
|
|
107
|
+
callbacks: EffectCallbacks
|
|
108
|
+
}): Promise<void> {
|
|
109
|
+
if (!args.vaultAddress) return
|
|
110
|
+
const agentIdRaw = args.afterIdentity.agentId
|
|
111
|
+
if (!agentIdRaw) return
|
|
112
|
+
const agentId = BigInt(agentIdRaw)
|
|
113
|
+
const registryAddress = getAddress(args.registry.identityRegistryAddress)
|
|
114
|
+
const vaultAddress = getAddress(args.vaultAddress)
|
|
115
|
+
const probeClient = createErc8004PublicClient(args.registry)
|
|
116
|
+
let depositor: Address | undefined
|
|
117
|
+
try {
|
|
118
|
+
depositor = await probeClient.readContract({
|
|
119
|
+
address: vaultAddress,
|
|
120
|
+
abi: OPERATOR_VAULT_ABI,
|
|
121
|
+
functionName: 'agentOwner',
|
|
122
|
+
args: [registryAddress, agentId],
|
|
123
|
+
}) as Address
|
|
124
|
+
} catch {
|
|
125
|
+
depositor = undefined
|
|
126
|
+
}
|
|
127
|
+
if (!depositor || depositor.toLowerCase() !== args.ownerAddress.toLowerCase()) return
|
|
128
|
+
|
|
129
|
+
const operations: Array<{ operator: Address; approved: boolean }> = []
|
|
130
|
+
for (const operator of args.diff.added) operations.push({ operator: getAddress(operator), approved: true })
|
|
131
|
+
for (const operator of args.diff.removed) operations.push({ operator: getAddress(operator), approved: false })
|
|
132
|
+
if (operations.length === 0) return
|
|
133
|
+
|
|
134
|
+
for (const op of operations) {
|
|
135
|
+
const encoded = encodeSetMetadataOperator({
|
|
136
|
+
registry: registryAddress,
|
|
137
|
+
agentId,
|
|
138
|
+
operator: op.operator,
|
|
139
|
+
approved: op.approved,
|
|
140
|
+
vaultAddress,
|
|
141
|
+
})
|
|
142
|
+
const tx = await sendBrowserWalletTransaction({
|
|
143
|
+
chainId: args.registry.chainId,
|
|
144
|
+
expectedAccount: args.ownerAddress,
|
|
145
|
+
to: encoded.to,
|
|
146
|
+
data: encoded.data,
|
|
147
|
+
onReady: args.callbacks.onWalletReady,
|
|
148
|
+
purpose: 'sync-operator-vault',
|
|
149
|
+
})
|
|
150
|
+
args.callbacks.onWalletReady(null)
|
|
151
|
+
await awaitConfirmedReceipt(probeClient, tx.txHash, 'Vault operator sync')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const VERIFY_MAX_ATTEMPTS = 5
|
|
155
|
+
const VERIFY_DELAY_MS = 1500
|
|
156
|
+
let lastMismatch: { op: typeof operations[number]; observed: boolean } | undefined
|
|
157
|
+
for (let attempt = 0; attempt < VERIFY_MAX_ATTEMPTS; attempt++) {
|
|
158
|
+
if (attempt > 0) await new Promise(resolve => setTimeout(resolve, VERIFY_DELAY_MS))
|
|
159
|
+
const final = await readMetadataOperators({
|
|
160
|
+
client: probeClient,
|
|
161
|
+
vaultAddress,
|
|
162
|
+
registry: registryAddress,
|
|
163
|
+
agentId,
|
|
164
|
+
candidates: operations.map(o => o.operator),
|
|
165
|
+
})
|
|
166
|
+
lastMismatch = undefined
|
|
167
|
+
for (const op of operations) {
|
|
168
|
+
const observed = Boolean(final[op.operator])
|
|
169
|
+
if (observed !== op.approved) {
|
|
170
|
+
lastMismatch = { op, observed }
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!lastMismatch) return
|
|
175
|
+
}
|
|
176
|
+
if (lastMismatch) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
lastMismatch.op.approved
|
|
179
|
+
? `Vault operator authorization didn't land for ${lastMismatch.op.operator}. Your wallet may have rejected the inner transaction; retry the save to apply it.`
|
|
180
|
+
: `Vault operator revocation didn't land for ${lastMismatch.op.operator}. Your wallet may have rejected the inner transaction; retry the save to apply it.`,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function markCurrentContinuityFilesPublished(identity: EthagentIdentity): Promise<void> {
|
|
186
|
+
const cid = identity.backup?.cid
|
|
187
|
+
if (!cid) return
|
|
188
|
+
const contentHashes = await localContinuitySnapshotContentHashes(identity)
|
|
189
|
+
await updatePublishedContinuitySnapshotContentHashes(identity, cid, contentHashes).catch(() => null)
|
|
190
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getAddress } from 'viem'
|
|
2
|
+
import type { TokenTransferProgress } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export function tokenTransferProgressForPhase(
|
|
5
|
+
phase: TokenTransferProgress['phase'],
|
|
6
|
+
ownerAddress: string,
|
|
7
|
+
targetAddress: string,
|
|
8
|
+
): TokenTransferProgress {
|
|
9
|
+
const sender = getAddress(ownerAddress)
|
|
10
|
+
const receiver = getAddress(targetAddress)
|
|
11
|
+
switch (phase) {
|
|
12
|
+
case 'sender-sign':
|
|
13
|
+
return {
|
|
14
|
+
phase,
|
|
15
|
+
walletRole: 'sender',
|
|
16
|
+
expectedAddress: sender,
|
|
17
|
+
title: 'Use Sender Wallet',
|
|
18
|
+
detail: 'Sign to save a transfer snapshot.',
|
|
19
|
+
walletAction: 'Sign Snapshot',
|
|
20
|
+
label: 'Sender Wallet: sign to save the transfer snapshot.',
|
|
21
|
+
}
|
|
22
|
+
case 'target-sign':
|
|
23
|
+
return {
|
|
24
|
+
phase,
|
|
25
|
+
walletRole: 'receiver',
|
|
26
|
+
expectedAddress: receiver,
|
|
27
|
+
title: 'Use Receiver Wallet',
|
|
28
|
+
detail: 'Sign so this wallet can restore after it receives the token.',
|
|
29
|
+
walletAction: 'Sign Restore Access',
|
|
30
|
+
label: 'Receiver Wallet: sign once to authorize future restore after the token transfer.',
|
|
31
|
+
}
|
|
32
|
+
case 'sender-transaction':
|
|
33
|
+
return {
|
|
34
|
+
phase,
|
|
35
|
+
walletRole: 'sender',
|
|
36
|
+
expectedAddress: sender,
|
|
37
|
+
title: 'Use Sender Wallet Again',
|
|
38
|
+
detail: 'Publish the transfer snapshot to the ERC-8004 token URI.',
|
|
39
|
+
walletAction: 'Update Token URI',
|
|
40
|
+
label: 'Sender Wallet: sign one ERC-8004 token URI update that points the token at the transfer snapshot.',
|
|
41
|
+
}
|
|
42
|
+
case 'pinning':
|
|
43
|
+
return {
|
|
44
|
+
phase,
|
|
45
|
+
walletRole: 'none',
|
|
46
|
+
title: 'Publishing Snapshot',
|
|
47
|
+
detail: 'Encrypting and pinning the dual-wallet transfer snapshot.',
|
|
48
|
+
label: 'Encrypting and pinning the dual-wallet transfer snapshot.',
|
|
49
|
+
}
|
|
50
|
+
case 'confirming':
|
|
51
|
+
return {
|
|
52
|
+
phase,
|
|
53
|
+
walletRole: 'none',
|
|
54
|
+
title: 'Confirming Token URI Update',
|
|
55
|
+
detail: 'Waiting for the ERC-8004 token URI transaction.',
|
|
56
|
+
label: 'Confirming the ERC-8004 token URI update.',
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { getAddress, isAddress, type Address, type Hex } from 'viem'
|
|
2
|
+
import type { EthagentIdentity } from '../../../../storage/config.js'
|
|
3
|
+
import {
|
|
4
|
+
createTransferContinuitySnapshotChallenge,
|
|
5
|
+
createTransferContinuitySnapshotEnvelope,
|
|
6
|
+
serializeContinuitySnapshotEnvelope,
|
|
7
|
+
transferSnapshotMetadataFromEnvelope,
|
|
8
|
+
} from '../../../continuity/envelope.js'
|
|
9
|
+
import {
|
|
10
|
+
continuityAgentSnapshot,
|
|
11
|
+
continuityVaultStatus,
|
|
12
|
+
readContinuityFiles,
|
|
13
|
+
readPublicSkillsFile,
|
|
14
|
+
writePublicSkillsFile,
|
|
15
|
+
} from '../../../continuity/storage.js'
|
|
16
|
+
import {
|
|
17
|
+
createAgentCard,
|
|
18
|
+
defaultPublicSkillsProfile,
|
|
19
|
+
serializeAgentCard,
|
|
20
|
+
} from '../../../continuity/publicSkills.js'
|
|
21
|
+
import { recordPublishedContinuitySnapshot } from '../../../continuity/snapshots.js'
|
|
22
|
+
import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../../storage/ipfs.js'
|
|
23
|
+
import {
|
|
24
|
+
createErc8004PublicClient,
|
|
25
|
+
encodeSetAgentUri,
|
|
26
|
+
preflightSetAgentUri,
|
|
27
|
+
withEthagentPointers,
|
|
28
|
+
} from '../../../registry/erc8004.js'
|
|
29
|
+
import { resolveValidatedPinataJwt, savePinataJwt } from '../../../storage/pinataJwt.js'
|
|
30
|
+
import { openBrowserWalletSession } from '../../../wallet/browserWallet.js'
|
|
31
|
+
import { resolveEnsAddress } from '../../../ens/ensLookup.js'
|
|
32
|
+
import type { Step } from '../../identityHubReducer.js'
|
|
33
|
+
import type { EffectCallbacks } from '../types.js'
|
|
34
|
+
import { awaitConfirmedReceipt } from '../receipts.js'
|
|
35
|
+
import {
|
|
36
|
+
assertVerifiedPin,
|
|
37
|
+
deriveAgentName,
|
|
38
|
+
} from '../shared/profilePrep.js'
|
|
39
|
+
import { operatorsPointerFromState } from '../shared/snapshot.js'
|
|
40
|
+
import { tokenTransferProgressForPhase } from './progress.js'
|
|
41
|
+
import { assertTokenNotInVault } from '../vault/preflight.js'
|
|
42
|
+
|
|
43
|
+
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
44
|
+
type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
|
|
45
|
+
|
|
46
|
+
type TokenTransferResult = {
|
|
47
|
+
identity: EthagentIdentity
|
|
48
|
+
snapshotCid: string
|
|
49
|
+
txHash: Hex
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function runTokenTransferTargetSubmit(
|
|
53
|
+
value: string,
|
|
54
|
+
step: Extract<Step, { kind: 'token-transfer-target' }>,
|
|
55
|
+
callbacks: EffectCallbacks,
|
|
56
|
+
options: { signal?: AbortSignal } = {},
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
if (!step.identity.agentId) throw new Error('Cannot prepare token transfer: identity is missing an agent token ID')
|
|
59
|
+
const targetHandle = value.trim()
|
|
60
|
+
if (!targetHandle) throw new Error('Enter the receiver wallet ENS name or 0x address')
|
|
61
|
+
throwIfTransferAborted(options.signal)
|
|
62
|
+
await assertTokenNotInVault({ identity: step.identity, registry: step.registry })
|
|
63
|
+
throwIfTransferAborted(options.signal)
|
|
64
|
+
callbacks.onStep({ kind: 'token-transfer-resolving', identity: step.identity, registry: step.registry, targetHandle, returnTo: step.returnTo })
|
|
65
|
+
const targetAddress = await resolveTransferTargetAddress(targetHandle, { signal: options.signal })
|
|
66
|
+
throwIfTransferAborted(options.signal)
|
|
67
|
+
const ownerAddress = getAddress(step.identity.ownerAddress ?? step.identity.address)
|
|
68
|
+
if (targetAddress.toLowerCase() === ownerAddress.toLowerCase()) {
|
|
69
|
+
throw new Error('Receiver wallet must be different from the sender wallet')
|
|
70
|
+
}
|
|
71
|
+
const status = await continuityVaultStatus(step.identity)
|
|
72
|
+
throwIfTransferAborted(options.signal)
|
|
73
|
+
if (!status.ready) {
|
|
74
|
+
throw new Error('Restore local continuity files before preparing a transfer snapshot')
|
|
75
|
+
}
|
|
76
|
+
const apiUrl = DEFAULT_IPFS_API_URL
|
|
77
|
+
let jwt: string | undefined
|
|
78
|
+
try {
|
|
79
|
+
jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
|
|
80
|
+
throwIfTransferAborted(options.signal)
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
throwIfTransferAborted(options.signal)
|
|
83
|
+
callbacks.onStep({
|
|
84
|
+
kind: 'token-transfer-storage',
|
|
85
|
+
identity: step.identity,
|
|
86
|
+
registry: step.registry,
|
|
87
|
+
targetHandle,
|
|
88
|
+
targetAddress,
|
|
89
|
+
error: (err as Error).message,
|
|
90
|
+
returnTo: step.returnTo,
|
|
91
|
+
})
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (isPinataUploadUrl(apiUrl) && !jwt) {
|
|
95
|
+
throwIfTransferAborted(options.signal)
|
|
96
|
+
callbacks.onStep({ kind: 'token-transfer-storage', identity: step.identity, registry: step.registry, targetHandle, targetAddress, returnTo: step.returnTo })
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
throwIfTransferAborted(options.signal)
|
|
100
|
+
callbacks.onStep({ kind: 'token-transfer-signing', identity: step.identity, registry: step.registry, targetHandle, targetAddress, pinataJwt: jwt, returnTo: step.returnTo })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function runTokenTransferStorageSubmit(
|
|
104
|
+
input: string,
|
|
105
|
+
step: Extract<Step, { kind: 'token-transfer-storage' }>,
|
|
106
|
+
callbacks: EffectCallbacks,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const { jwt: pinataJwt } = await savePinataJwt(input)
|
|
109
|
+
callbacks.onStep({
|
|
110
|
+
kind: 'token-transfer-signing',
|
|
111
|
+
identity: step.identity,
|
|
112
|
+
registry: step.registry,
|
|
113
|
+
targetHandle: step.targetHandle,
|
|
114
|
+
targetAddress: step.targetAddress,
|
|
115
|
+
pinataJwt,
|
|
116
|
+
returnTo: step.returnTo,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function runTokenTransferSigning(
|
|
121
|
+
step: Extract<Step, { kind: 'token-transfer-signing' }>,
|
|
122
|
+
callbacks: EffectCallbacks,
|
|
123
|
+
): Promise<TokenTransferResult> {
|
|
124
|
+
if (!step.identity.agentId) throw new Error('Cannot prepare token transfer: identity is missing an agent token ID')
|
|
125
|
+
const transferAgentId = step.identity.agentId
|
|
126
|
+
const ownerAddress = getAddress(step.identity.ownerAddress ?? step.identity.address)
|
|
127
|
+
const targetAddress = getAddress(step.targetAddress)
|
|
128
|
+
const token = {
|
|
129
|
+
chainId: step.registry.chainId,
|
|
130
|
+
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
131
|
+
agentId: transferAgentId,
|
|
132
|
+
}
|
|
133
|
+
const senderChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'sender' })
|
|
134
|
+
const targetChallenge = createTransferContinuitySnapshotChallenge({ token, ownerAddress, targetAddress, role: 'receiver' })
|
|
135
|
+
|
|
136
|
+
const session = await openBrowserWalletSession({ onReady: callbacks.onWalletReady })
|
|
137
|
+
try {
|
|
138
|
+
|
|
139
|
+
callbacks.onTokenTransferProgress?.(tokenTransferProgressForPhase('sender-sign', ownerAddress, targetAddress))
|
|
140
|
+
const senderSignature = await session.requestSignature({
|
|
141
|
+
chainId: step.registry.chainId,
|
|
142
|
+
expectedAccount: ownerAddress,
|
|
143
|
+
message: senderChallenge,
|
|
144
|
+
purpose: 'prepare-transfer-sender',
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
callbacks.onTokenTransferProgress?.(tokenTransferProgressForPhase('target-sign', ownerAddress, targetAddress))
|
|
148
|
+
const targetSignature = await session.requestSignature({
|
|
149
|
+
chainId: step.registry.chainId,
|
|
150
|
+
expectedAccount: targetAddress,
|
|
151
|
+
message: targetChallenge,
|
|
152
|
+
purpose: 'prepare-transfer-target',
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
callbacks.onTokenTransferProgress?.(tokenTransferProgressForPhase('pinning', ownerAddress, targetAddress))
|
|
156
|
+
const baseState = (step.identity.state ?? {}) as Record<string, unknown>
|
|
157
|
+
const state: Record<string, unknown> = {
|
|
158
|
+
...baseState,
|
|
159
|
+
ownerAddress,
|
|
160
|
+
lastBackedUpAt: new Date().toISOString(),
|
|
161
|
+
}
|
|
162
|
+
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
163
|
+
const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
|
|
164
|
+
const publicSkillsJson = await readPublicSkillsFile(nextIdentityForFiles)
|
|
165
|
+
const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
|
|
166
|
+
assertVerifiedPin(publicSkillsPin)
|
|
167
|
+
const agentCardPin = await addToIpfs(
|
|
168
|
+
DEFAULT_IPFS_API_URL,
|
|
169
|
+
serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
|
|
170
|
+
fetch,
|
|
171
|
+
{ pinataJwt: step.pinataJwt },
|
|
172
|
+
)
|
|
173
|
+
assertVerifiedPin(agentCardPin)
|
|
174
|
+
const envelope = createTransferContinuitySnapshotEnvelope({
|
|
175
|
+
ownerAddress,
|
|
176
|
+
ownerWalletSignature: senderSignature.signature,
|
|
177
|
+
targetAddress,
|
|
178
|
+
targetWalletSignature: targetSignature.signature,
|
|
179
|
+
targetHandle: step.targetHandle,
|
|
180
|
+
token,
|
|
181
|
+
payload: {
|
|
182
|
+
agent: continuityAgentSnapshot(nextIdentityForFiles),
|
|
183
|
+
files: continuityFiles,
|
|
184
|
+
transcript: [],
|
|
185
|
+
state,
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
const transferSnapshot = transferSnapshotMetadataFromEnvelope(envelope)
|
|
189
|
+
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
190
|
+
assertVerifiedPin(statePin)
|
|
191
|
+
const snapshotCid = statePin.cid
|
|
192
|
+
const backup: BackupMetadata = {
|
|
193
|
+
cid: snapshotCid,
|
|
194
|
+
createdAt: envelope.createdAt,
|
|
195
|
+
envelopeVersion: envelope.envelopeVersion,
|
|
196
|
+
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
197
|
+
status: 'pinned',
|
|
198
|
+
ownerAddress,
|
|
199
|
+
chainId: step.registry.chainId,
|
|
200
|
+
rpcUrl: step.registry.rpcUrl,
|
|
201
|
+
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
202
|
+
agentId: transferAgentId,
|
|
203
|
+
...(transferSnapshot ? { transferSnapshot } : {}),
|
|
204
|
+
}
|
|
205
|
+
const publicSkills: PublicSkillsMetadata = {
|
|
206
|
+
cid: publicSkillsPin.cid,
|
|
207
|
+
agentCardCid: agentCardPin.cid,
|
|
208
|
+
updatedAt: envelope.createdAt,
|
|
209
|
+
status: 'pinned',
|
|
210
|
+
}
|
|
211
|
+
const nextName = typeof state.name === 'string' && state.name.trim() ? state.name.trim() : deriveAgentName(step.identity)
|
|
212
|
+
const nextDescription = typeof state.description === 'string' && state.description.trim() ? state.description.trim() : ''
|
|
213
|
+
const uploadedImageUri = typeof state.imageUrl === 'string' && state.imageUrl.trim() ? state.imageUrl.trim() : undefined
|
|
214
|
+
const nextEnsName = typeof state.ensName === 'string' && state.ensName.trim() ? state.ensName.trim() : undefined
|
|
215
|
+
const registration = withEthagentPointers({
|
|
216
|
+
type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
|
|
217
|
+
name: nextName,
|
|
218
|
+
...(nextDescription ? { description: nextDescription } : {}),
|
|
219
|
+
...(uploadedImageUri ? { image: uploadedImageUri } : {}),
|
|
220
|
+
}, {
|
|
221
|
+
backup: { cid: snapshotCid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt, ...(transferSnapshot ? { transferSnapshot } : {}) },
|
|
222
|
+
publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
|
|
223
|
+
registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: transferAgentId },
|
|
224
|
+
ensName: nextEnsName,
|
|
225
|
+
operators: operatorsPointerFromState(state, nextEnsName),
|
|
226
|
+
ownerAddress,
|
|
227
|
+
})
|
|
228
|
+
const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
|
|
229
|
+
assertVerifiedPin(metadataPin)
|
|
230
|
+
const metadataCid = metadataPin.cid
|
|
231
|
+
const agentUri = `ipfs://${metadataCid}`
|
|
232
|
+
const agentId = BigInt(transferAgentId)
|
|
233
|
+
await preflightSetAgentUri({
|
|
234
|
+
...step.registry,
|
|
235
|
+
account: ownerAddress,
|
|
236
|
+
agentId,
|
|
237
|
+
newUri: agentUri,
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
callbacks.onTokenTransferProgress?.(tokenTransferProgressForPhase('sender-transaction', ownerAddress, targetAddress))
|
|
241
|
+
const tx = await session.sendTransaction({
|
|
242
|
+
chainId: step.registry.chainId,
|
|
243
|
+
expectedAccount: ownerAddress,
|
|
244
|
+
to: step.registry.identityRegistryAddress,
|
|
245
|
+
data: encodeSetAgentUri({ agentId, newUri: agentUri }),
|
|
246
|
+
purpose: 'publish-transfer-snapshot',
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
callbacks.onTokenTransferProgress?.(tokenTransferProgressForPhase('confirming', ownerAddress, targetAddress))
|
|
250
|
+
const client = createErc8004PublicClient(step.registry)
|
|
251
|
+
await awaitConfirmedReceipt(client, tx.txHash, 'Token transfer URI publish', { kind: 'token-transfer', chainId: step.registry.chainId })
|
|
252
|
+
const nextIdentity: EthagentIdentity = {
|
|
253
|
+
...step.identity,
|
|
254
|
+
source: 'erc8004',
|
|
255
|
+
address: ownerAddress,
|
|
256
|
+
ownerAddress,
|
|
257
|
+
chainId: step.registry.chainId,
|
|
258
|
+
rpcUrl: step.registry.rpcUrl,
|
|
259
|
+
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
260
|
+
agentUri,
|
|
261
|
+
metadataCid,
|
|
262
|
+
backup: { ...backup, metadataCid, agentUri, txHash: tx.txHash },
|
|
263
|
+
publicSkills,
|
|
264
|
+
state,
|
|
265
|
+
}
|
|
266
|
+
await writePublicSkillsFile(nextIdentity, publicSkillsJson)
|
|
267
|
+
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published transfer snapshot' }).catch(() => null)
|
|
268
|
+
callbacks.onTokenTransferProgress?.(null)
|
|
269
|
+
return {
|
|
270
|
+
identity: nextIdentity,
|
|
271
|
+
snapshotCid,
|
|
272
|
+
txHash: tx.txHash,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} finally {
|
|
276
|
+
await session.close().catch(() => {})
|
|
277
|
+
callbacks.onWalletReady(null)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function resolveTransferTargetAddress(value: string, options: { signal?: AbortSignal } = {}): Promise<Address> {
|
|
282
|
+
const trimmed = value.trim()
|
|
283
|
+
throwIfTransferAborted(options.signal)
|
|
284
|
+
if (isAddress(trimmed, { strict: false })) return getAddress(trimmed)
|
|
285
|
+
if (!trimmed.includes('.')) throw new Error('Enter a receiver Ethereum address or ENS name')
|
|
286
|
+
const resolved = await resolveEnsAddress(trimmed, { signal: options.signal })
|
|
287
|
+
throwIfTransferAborted(options.signal)
|
|
288
|
+
if (!resolved) throw new Error(`ENS name did not resolve: ${trimmed}`)
|
|
289
|
+
return getAddress(resolved)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function throwIfTransferAborted(signal: AbortSignal | undefined): void {
|
|
293
|
+
if (!signal?.aborted) return
|
|
294
|
+
const err = new Error('token transfer preparation cancelled')
|
|
295
|
+
err.name = 'AbortError'
|
|
296
|
+
throw err
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export { tokenTransferProgressForPhase } from './progress.js'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
+
import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
|
|
4
|
+
import type { Step } from '../identityHubReducer.js'
|
|
5
|
+
|
|
6
|
+
export type IdentityCompletionSource = 'create' | 'restore' | 'update'
|
|
7
|
+
|
|
8
|
+
export type EffectCallbacks = {
|
|
9
|
+
onStep: (step: Step) => void
|
|
10
|
+
onWalletReady: (session: BrowserWalletReady | null) => void
|
|
11
|
+
onIdentityComplete: (identity: EthagentIdentity, message: string, source?: IdentityCompletionSource) => Promise<void>
|
|
12
|
+
onRestoreProgress?: (progress: RestoreProgress | null) => void
|
|
13
|
+
onTokenTransferProgress?: (progress: TokenTransferProgress | null) => void
|
|
14
|
+
onEnsClearProgress?: (progress: EnsClearProgress | null) => void
|
|
15
|
+
onEnsLinkProgress?: (progress: EnsLinkProgress | null) => void
|
|
16
|
+
onEnsUpdateProgress?: (progress: EnsUpdateProgress | null) => void
|
|
17
|
+
onCustodySwitchAdvancedProgress?: (progress: CustodySwitchAdvancedProgress | null) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RestoreProgress = {
|
|
21
|
+
phase: 'decrypting' | 'writing' | 'finishing'
|
|
22
|
+
label: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TokenTransferProgress = {
|
|
26
|
+
phase: 'sender-sign' | 'target-sign' | 'pinning' | 'sender-transaction' | 'confirming'
|
|
27
|
+
walletRole: 'sender' | 'receiver' | 'none'
|
|
28
|
+
title: string
|
|
29
|
+
detail: string
|
|
30
|
+
label: string
|
|
31
|
+
expectedAddress?: Address
|
|
32
|
+
walletAction?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type EnsClearProgress = {
|
|
36
|
+
phase: 'records-tx' | 'records-confirming' | 'snapshot-sign' | 'snapshot-tx' | 'snapshot-confirming'
|
|
37
|
+
label: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type EnsLinkProgress = {
|
|
41
|
+
phase: 'registry-tx' | 'registry-confirming' | 'records-tx' | 'records-confirming' | 'snapshot-sign' | 'snapshot-tx' | 'snapshot-confirming'
|
|
42
|
+
label: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type EnsUpdateProgress = {
|
|
46
|
+
phase: 'records-tx' | 'records-confirming' | 'snapshot-sign' | 'snapshot-tx' | 'snapshot-confirming'
|
|
47
|
+
label: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type CustodySwitchAdvancedProgress = {
|
|
51
|
+
phase: 'deploy-tx' | 'deploy-confirming' | 'deposit-tx' | 'deposit-confirming' | 'reconcile-tx' | 'reconcile-confirming'
|
|
52
|
+
label: string
|
|
53
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getAddress, type Address, type PublicClient } from 'viem'
|
|
2
|
+
import type { EthagentIdentity } from '../../../../storage/config.js'
|
|
3
|
+
import { readOperatorVaultAddressField } from '../../../identityCompat.js'
|
|
4
|
+
import { createErc8004PublicClient, type Erc8004RegistryConfig } from '../../../registry/erc8004.js'
|
|
5
|
+
import { isAgentInVault, resolveConfiguredOperatorVaultAddress } from '../../../registry/operatorVault.js'
|
|
6
|
+
import { readCustodyMode } from '../../model/custody.js'
|
|
7
|
+
|
|
8
|
+
export class OperatorVaultUnavailableError extends Error {
|
|
9
|
+
constructor(chainId: number) {
|
|
10
|
+
super(`Operator delegation vault is not deployed for chainId ${chainId}. Switching custody mode is unavailable until a deployment is recorded.`)
|
|
11
|
+
this.name = 'OperatorVaultUnavailableError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TokenInVaultError extends Error {
|
|
16
|
+
constructor(public vaultAddress: Address) {
|
|
17
|
+
super('Token is in the operator delegation vault. Withdraw it first to prepare a transfer.')
|
|
18
|
+
this.name = 'TokenInVaultError'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function assertTokenNotInVault(args: {
|
|
23
|
+
identity: EthagentIdentity
|
|
24
|
+
registry: Erc8004RegistryConfig
|
|
25
|
+
operatorVaults?: Readonly<Record<string, string>>
|
|
26
|
+
client?: Pick<PublicClient, 'readContract'>
|
|
27
|
+
}): Promise<void> {
|
|
28
|
+
if (!args.identity.agentId) return
|
|
29
|
+
const vaultAddress = vaultAddressForTransferPreflight(args.identity, args.operatorVaults)
|
|
30
|
+
if (!vaultAddress) return
|
|
31
|
+
const client = args.client ?? createErc8004PublicClient(args.registry)
|
|
32
|
+
const status = await isAgentInVault({
|
|
33
|
+
client,
|
|
34
|
+
vaultAddress,
|
|
35
|
+
registry: getAddress(args.registry.identityRegistryAddress),
|
|
36
|
+
agentId: BigInt(args.identity.agentId),
|
|
37
|
+
})
|
|
38
|
+
if (status.inVault) throw new TokenInVaultError(vaultAddress)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function vaultAddressForTransferPreflight(
|
|
42
|
+
identity: EthagentIdentity,
|
|
43
|
+
operatorVaults?: Readonly<Record<string, string>>,
|
|
44
|
+
): Address | undefined {
|
|
45
|
+
const identityVault = readOperatorVaultAddressField(identity.state as Record<string, unknown> | undefined)
|
|
46
|
+
if (identityVault) return getAddress(identityVault)
|
|
47
|
+
if (readCustodyMode(identity.state as Record<string, unknown> | undefined) !== 'advanced') return undefined
|
|
48
|
+
if (!identity.chainId) return undefined
|
|
49
|
+
return resolveConfiguredOperatorVaultAddress(operatorVaults, identity.chainId)
|
|
50
|
+
}
|