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,79 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity } from '../../storage/config.js'
|
|
2
|
+
import { supportedErc8004ChainForId } from '../registry/erc8004.js'
|
|
3
|
+
import { snapshotSaveRequiresOwnerSigner } from './effects/shared/snapshot.js'
|
|
4
|
+
import type { IdentityHubInitialAction } from './types.js'
|
|
5
|
+
import type { ProfileUpdates, Step } from './identityHubReducer.js'
|
|
6
|
+
|
|
7
|
+
const MIN_BUSY_ERROR_MS = 2000
|
|
8
|
+
|
|
9
|
+
export function isWalletCancelled(err: unknown): boolean {
|
|
10
|
+
if (!err) return false
|
|
11
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
12
|
+
return /wallet request was cancelled/i.test(message)
|
|
13
|
+
|| /user rejected/i.test(message)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isStorageError(err: unknown): boolean {
|
|
17
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
18
|
+
return /pinata|ipfs|pin|storage/i.test(message)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function chainLabel(chainId: number): string {
|
|
22
|
+
return supportedErc8004ChainForId(chainId)?.name ?? `chain ${chainId}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function waitForMinimumBusyTime(startedAt: number): Promise<void> {
|
|
26
|
+
const remaining = MIN_BUSY_ERROR_MS - (Date.now() - startedAt)
|
|
27
|
+
return remaining > 0
|
|
28
|
+
? new Promise(resolve => setTimeout(resolve, remaining))
|
|
29
|
+
: Promise.resolve()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function capitalizeFeedbackMessage(message: string): string {
|
|
33
|
+
return message.replace(/^(\s*)([a-z])/, (_match, leading: string, first: string) => `${leading}${first.toUpperCase()}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function rebackupWalletApprovalView(
|
|
37
|
+
identity: EthagentIdentity,
|
|
38
|
+
profileUpdates?: ProfileUpdates,
|
|
39
|
+
): { title: string; subtitle: string; label: string } {
|
|
40
|
+
if (snapshotSaveRequiresOwnerSigner(identity, profileUpdates)) {
|
|
41
|
+
return {
|
|
42
|
+
title: 'Use Owner Wallet',
|
|
43
|
+
subtitle: 'Owner wallet signs this custody-controlled identity update.',
|
|
44
|
+
label: 'waiting for owner wallet signature...',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
title: 'Use Wallet',
|
|
49
|
+
subtitle: 'Owner or operator wallet signs the encrypted snapshot and token URI update.',
|
|
50
|
+
label: 'waiting for wallet signature...',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isCreateStep(step: Step): step is Extract<Step, { kind: 'replace-confirm' | 'create-name' | 'create-description' | 'create-custody' | 'create-preflight' | 'create-registry' | 'create-signing' | 'create-storage' }> {
|
|
55
|
+
return step.kind === 'replace-confirm'
|
|
56
|
+
|| step.kind === 'create-name'
|
|
57
|
+
|| step.kind === 'create-description'
|
|
58
|
+
|| step.kind === 'create-custody'
|
|
59
|
+
|| step.kind === 'create-preflight'
|
|
60
|
+
|| step.kind === 'create-registry'
|
|
61
|
+
|| step.kind === 'create-signing'
|
|
62
|
+
|| step.kind === 'create-storage'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function isRestoreStep(step: Step): step is Exclude<Extract<Step, { kind: `restore-${string}` }>, { kind: 'restore-wallet' | 'restore-network' }> {
|
|
66
|
+
return step.kind.startsWith('restore-') && step.kind !== 'restore-wallet' && step.kind !== 'restore-network'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function initialStepForAction(
|
|
70
|
+
action: IdentityHubInitialAction | undefined,
|
|
71
|
+
config: EthagentConfig | undefined,
|
|
72
|
+
): Step {
|
|
73
|
+
if (action === 'create') return config?.identity ? { kind: 'replace-confirm', next: 'create' } : { kind: 'create-name' }
|
|
74
|
+
if (action === 'load') return { kind: 'restore-wallet', purpose: config?.identity ? 'switch' : 'restore' }
|
|
75
|
+
if (action === 'save-snapshot') return config?.identity ? { kind: 'rebackup-start', back: { kind: 'menu' } } : { kind: 'menu' }
|
|
76
|
+
if (action === 'save-prompt') return config?.identity ? { kind: 'save-prompt', back: { kind: 'menu' } } : { kind: 'menu' }
|
|
77
|
+
if (action === 'settings') return { kind: 'menu' }
|
|
78
|
+
return { kind: 'menu' }
|
|
79
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const LEGACY_OWNER_ADDRESS_KEY = ['c', 'oldOwnerAddress'].join('')
|
|
2
|
+
const OPERATOR_VAULT_ADDRESS_KEY = 'operatorVaultAddress'
|
|
3
|
+
|
|
4
|
+
export function readOwnerAddressField(input: Record<string, unknown> | null | undefined): string | undefined {
|
|
5
|
+
const current = stringValue(input?.ownerAddress)
|
|
6
|
+
if (current) return current
|
|
7
|
+
return stringValue(input?.[LEGACY_OWNER_ADDRESS_KEY])
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function setOwnerAddressField(target: Record<string, unknown>, value: string): void {
|
|
11
|
+
delete target[LEGACY_OWNER_ADDRESS_KEY]
|
|
12
|
+
target.ownerAddress = value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function clearOwnerAddressField(target: Record<string, unknown>): void {
|
|
16
|
+
delete target.ownerAddress
|
|
17
|
+
delete target[LEGACY_OWNER_ADDRESS_KEY]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readOperatorVaultAddressField(input: Record<string, unknown> | null | undefined): string | undefined {
|
|
21
|
+
return stringValue(input?.[OPERATOR_VAULT_ADDRESS_KEY])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function setOperatorVaultAddressField(target: Record<string, unknown>, value: string): void {
|
|
25
|
+
target[OPERATOR_VAULT_ADDRESS_KEY] = value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clearOperatorVaultAddressField(target: Record<string, unknown>): void {
|
|
29
|
+
delete target[OPERATOR_VAULT_ADDRESS_KEY]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function stringValue(value: unknown): string | undefined {
|
|
33
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
34
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
const AGENT_ICON_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.mp4', '.webm', '.mov'] as const
|
|
4
|
+
|
|
5
|
+
export function validateAgentIconReference(input: string): string | null {
|
|
6
|
+
const trimmed = input.trim()
|
|
7
|
+
if (!trimmed) return 'enter an Agent Icon URL or local file path'
|
|
8
|
+
if (hasUnsupportedUrlScheme(trimmed)) return 'Agent Icon must be a local path, https URL, or ipfs:// URL'
|
|
9
|
+
if (!agentIconExtension(trimmed)) return 'Agent Icon must be png, jpg, jpeg, gif, webp, svg, mp4, webm, or mov'
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isAgentIconUrl(input: string): boolean {
|
|
14
|
+
return /^(https|ipfs):\/\//i.test(input.trim())
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function agentIconContentType(file: string): string {
|
|
18
|
+
const ext = path.extname(file).toLowerCase()
|
|
19
|
+
switch (ext) {
|
|
20
|
+
case '.png':
|
|
21
|
+
return 'image/png'
|
|
22
|
+
case '.jpg':
|
|
23
|
+
case '.jpeg':
|
|
24
|
+
return 'image/jpeg'
|
|
25
|
+
case '.gif':
|
|
26
|
+
return 'image/gif'
|
|
27
|
+
case '.webp':
|
|
28
|
+
return 'image/webp'
|
|
29
|
+
case '.svg':
|
|
30
|
+
return 'image/svg+xml'
|
|
31
|
+
case '.mp4':
|
|
32
|
+
return 'video/mp4'
|
|
33
|
+
case '.webm':
|
|
34
|
+
return 'video/webm'
|
|
35
|
+
case '.mov':
|
|
36
|
+
return 'video/quicktime'
|
|
37
|
+
default:
|
|
38
|
+
return 'application/octet-stream'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function agentIconExtension(input: string): string | null {
|
|
43
|
+
const pathname = isAgentIconUrl(input) ? urlPathname(input) : input
|
|
44
|
+
const ext = path.extname(pathname).toLowerCase()
|
|
45
|
+
return AGENT_ICON_EXTENSIONS.includes(ext as typeof AGENT_ICON_EXTENSIONS[number]) ? ext : null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function urlPathname(input: string): string {
|
|
49
|
+
try {
|
|
50
|
+
const url = new URL(input)
|
|
51
|
+
return `${url.hostname}${url.pathname}`
|
|
52
|
+
} catch {
|
|
53
|
+
return input.split(/[?#]/, 1)[0] ?? input
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasUnsupportedUrlScheme(input: string): boolean {
|
|
58
|
+
const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(input)
|
|
59
|
+
if (!match) return false
|
|
60
|
+
return !/^(https|ipfs)$/i.test(match[1] ?? '')
|
|
61
|
+
}
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process'
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type ImageFilePickerResult =
|
|
6
6
|
| { ok: true; file: string; method: string }
|
|
7
7
|
| { ok: false; cancelled: boolean; error: string }
|
|
8
8
|
|
|
@@ -28,14 +28,14 @@ export async function openImageFilePicker(
|
|
|
28
28
|
ok: false,
|
|
29
29
|
cancelled: false,
|
|
30
30
|
error: platform === 'linux'
|
|
31
|
-
? 'install zenity or kdialog, or enter the
|
|
32
|
-
: 'no native
|
|
31
|
+
? 'install zenity or kdialog, or enter the icon path manually'
|
|
32
|
+
: 'no native icon picker is available; enter the icon path manually',
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
const result = await runPickerCommand(command, options.spawnImpl ?? spawn, options.timeoutMs ?? 120_000)
|
|
36
36
|
if (!result.ok) return result
|
|
37
37
|
const file = result.file.trim()
|
|
38
|
-
if (!file) return { ok: false, cancelled: true, error: '
|
|
38
|
+
if (!file) return { ok: false, cancelled: true, error: 'icon selection cancelled' }
|
|
39
39
|
return { ok: true, file, method: command.method }
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -55,8 +55,8 @@ function resolveImagePickerCommand(platform: NodeJS.Platform, env: NodeJS.Proces
|
|
|
55
55
|
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
|
|
56
56
|
'Add-Type -AssemblyName System.Windows.Forms',
|
|
57
57
|
'$dialog = New-Object System.Windows.Forms.OpenFileDialog',
|
|
58
|
-
'$dialog.Title = "Choose
|
|
59
|
-
'$dialog.Filter = "
|
|
58
|
+
'$dialog.Title = "Choose Agent Icon"',
|
|
59
|
+
'$dialog.Filter = "Agent Icon (*.png;*.jpg;*.jpeg;*.gif;*.webp;*.svg;*.mp4;*.webm;*.mov)|*.png;*.jpg;*.jpeg;*.gif;*.webp;*.svg;*.mp4;*.webm;*.mov|All files (*.*)|*.*"',
|
|
60
60
|
'$dialog.CheckFileExists = $true',
|
|
61
61
|
'if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dialog.FileName }',
|
|
62
62
|
].join('; '),
|
|
@@ -69,7 +69,7 @@ function resolveImagePickerCommand(platform: NodeJS.Platform, env: NodeJS.Proces
|
|
|
69
69
|
cmd: 'osascript',
|
|
70
70
|
args: [
|
|
71
71
|
'-e',
|
|
72
|
-
'set selectedFile to choose file with prompt "Choose
|
|
72
|
+
'set selectedFile to choose file with prompt "Choose Agent Icon"',
|
|
73
73
|
'-e',
|
|
74
74
|
'POSIX path of selectedFile',
|
|
75
75
|
],
|
|
@@ -82,8 +82,8 @@ function resolveImagePickerCommand(platform: NodeJS.Platform, env: NodeJS.Proces
|
|
|
82
82
|
cmd: zenity,
|
|
83
83
|
args: [
|
|
84
84
|
'--file-selection',
|
|
85
|
-
'--title=Choose
|
|
86
|
-
'--file-filter=
|
|
85
|
+
'--title=Choose Agent Icon',
|
|
86
|
+
'--file-filter=Agent Icon | *.png *.jpg *.jpeg *.gif *.webp *.svg *.mp4 *.webm *.mov',
|
|
87
87
|
],
|
|
88
88
|
method: 'zenity',
|
|
89
89
|
}
|
|
@@ -92,7 +92,7 @@ function resolveImagePickerCommand(platform: NodeJS.Platform, env: NodeJS.Proces
|
|
|
92
92
|
if (kdialog) {
|
|
93
93
|
return {
|
|
94
94
|
cmd: kdialog,
|
|
95
|
-
args: ['--getopenfilename', '.', '
|
|
95
|
+
args: ['--getopenfilename', '.', 'Agent Icon (*.png *.jpg *.jpeg *.gif *.webp *.svg *.mp4 *.webm *.mov)'],
|
|
96
96
|
method: 'kdialog',
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -122,7 +122,7 @@ function runPickerCommand(
|
|
|
122
122
|
if (settled) return
|
|
123
123
|
settled = true
|
|
124
124
|
child.kill()
|
|
125
|
-
resolve({ ok: false, cancelled: false, error: '
|
|
125
|
+
resolve({ ok: false, cancelled: false, error: 'icon picker timed out' })
|
|
126
126
|
}, timeoutMs)
|
|
127
127
|
child.stdout?.setEncoding('utf8')
|
|
128
128
|
child.stderr?.setEncoding('utf8')
|
|
@@ -145,7 +145,7 @@ function runPickerCommand(
|
|
|
145
145
|
}
|
|
146
146
|
const detail = stderr.trim()
|
|
147
147
|
const cancelled = code === 0 || /cancel/i.test(detail)
|
|
148
|
-
resolve({ ok: false, cancelled, error: cancelled ? '
|
|
148
|
+
resolve({ ok: false, cancelled, error: cancelled ? 'icon selection cancelled' : detail || `${command.method} exited ${code}` })
|
|
149
149
|
})
|
|
150
150
|
})
|
|
151
151
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseAbi, parseAbiItem } from 'viem'
|
|
2
|
+
|
|
3
|
+
export const ERC8004_ABI = parseAbi([
|
|
4
|
+
'function register(string agentURI) returns (uint256)',
|
|
5
|
+
'function balanceOf(address owner) view returns (uint256)',
|
|
6
|
+
'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
|
|
7
|
+
'function ownerOf(uint256 tokenId) view returns (address)',
|
|
8
|
+
'function tokenURI(uint256 tokenId) view returns (string)',
|
|
9
|
+
'function setAgentURI(uint256 agentId, string newURI)',
|
|
10
|
+
'function getMetadata(uint256 agentId, string metadataKey) view returns (bytes)',
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
export const REGISTERED_EVENT = parseAbiItem('event Registered(uint256 indexed agentId, address indexed owner, string agentURI)')
|
|
14
|
+
export const TRANSFER_EVENT = parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)')
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getAddress, isAddress, type Address, type Chain } from 'viem'
|
|
2
|
+
import { base, mainnet } from 'viem/chains'
|
|
3
|
+
import type { SelectableNetwork } from '../../../storage/config.js'
|
|
4
|
+
import type { Erc8004RegistryConfig } from './types.js'
|
|
5
|
+
import { uniqueStrings } from './utils.js'
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_ERC8004_CHAIN_ID = 1
|
|
8
|
+
export const DEFAULT_ETHEREUM_RPC_URL = 'https://ethereum.publicnode.com'
|
|
9
|
+
export const DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432'
|
|
10
|
+
|
|
11
|
+
export type SupportedErc8004Chain = {
|
|
12
|
+
chainId: number
|
|
13
|
+
name: string
|
|
14
|
+
rpcUrl: string
|
|
15
|
+
fallbackRpcUrls: string[]
|
|
16
|
+
identityRegistryAddress?: Address
|
|
17
|
+
fromBlock?: bigint
|
|
18
|
+
logBlockRange: bigint
|
|
19
|
+
kind: 'mainnet' | 'l2'
|
|
20
|
+
network: SelectableNetwork
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const SUPPORTED_ERC8004_CHAINS: SupportedErc8004Chain[] = [
|
|
24
|
+
chainEntry(mainnet.id, 'Ethereum Mainnet', DEFAULT_ETHEREUM_RPC_URL, [], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 24_339_871n, 10_000n, 'mainnet', 'mainnet'),
|
|
25
|
+
chainEntry(base.id, 'Base', 'https://mainnet.base.org', ['https://base.publicnode.com'], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 41_663_783n, 10_000n, 'l2', 'base'),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const NETWORK_TO_CHAIN_ID: Record<SelectableNetwork, number> = {
|
|
29
|
+
mainnet: mainnet.id,
|
|
30
|
+
base: base.id,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function chainIdForNetwork(network: SelectableNetwork): number {
|
|
34
|
+
return NETWORK_TO_CHAIN_ID[network]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function networkForChainId(chainId: number): SelectableNetwork | undefined {
|
|
38
|
+
for (const [network, id] of Object.entries(NETWORK_TO_CHAIN_ID) as Array<[SelectableNetwork, number]>) {
|
|
39
|
+
if (id === chainId) return network
|
|
40
|
+
}
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class MissingRegistryAddressError extends Error {
|
|
45
|
+
chainId: number
|
|
46
|
+
network?: SelectableNetwork
|
|
47
|
+
constructor(chainId: number) {
|
|
48
|
+
const network = networkForChainId(chainId)
|
|
49
|
+
super('no default ERC-8004 registry onchain ' + chainId + (network ? ' (' + network + ')' : ''))
|
|
50
|
+
this.name = 'MissingRegistryAddressError'
|
|
51
|
+
this.chainId = chainId
|
|
52
|
+
this.network = network
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function supportedErc8004ChainForId(chainId: number): SupportedErc8004Chain | undefined {
|
|
57
|
+
return SUPPORTED_ERC8004_CHAINS.find(chain => chain.chainId === chainId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeErc8004RegistryConfig(input: {
|
|
61
|
+
chainId?: number
|
|
62
|
+
rpcUrl?: string
|
|
63
|
+
identityRegistryAddress?: string
|
|
64
|
+
fromBlock?: string | bigint
|
|
65
|
+
}): Erc8004RegistryConfig {
|
|
66
|
+
const chainId = input.chainId ?? DEFAULT_ERC8004_CHAIN_ID
|
|
67
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
68
|
+
const identityRegistryAddress = input.identityRegistryAddress?.trim() || chain?.identityRegistryAddress
|
|
69
|
+
if (!identityRegistryAddress) throw new MissingRegistryAddressError(chainId)
|
|
70
|
+
if (!isAddress(identityRegistryAddress)) throw new Error('Invalid agent registry address')
|
|
71
|
+
let parsedUrl: URL
|
|
72
|
+
try {
|
|
73
|
+
parsedUrl = new URL(input.rpcUrl?.trim() || chain?.rpcUrl || DEFAULT_ETHEREUM_RPC_URL)
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error('Invalid Ethereum RPC URL')
|
|
76
|
+
}
|
|
77
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
78
|
+
throw new Error('Ethereum RPC URL must be http(s)')
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
chainId,
|
|
82
|
+
rpcUrl: parsedUrl.toString().replace(/\/$/, ''),
|
|
83
|
+
identityRegistryAddress: getAddress(identityRegistryAddress),
|
|
84
|
+
fromBlock: input.fromBlock !== undefined ? BigInt(input.fromBlock) : chain?.fromBlock,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function erc8004ConfigForSupportedChain(chainId: number): Erc8004RegistryConfig {
|
|
89
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
90
|
+
if (!chain) throw new Error('Unsupported ERC-8004 chain id: ' + chainId)
|
|
91
|
+
return normalizeErc8004RegistryConfig(chain)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function chainSortIndex(chainId: number): number {
|
|
95
|
+
const index = SUPPORTED_ERC8004_CHAINS.findIndex(chain => chain.chainId === chainId)
|
|
96
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function logBlockRangeForChain(chainId: number): bigint {
|
|
100
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
101
|
+
if (!chain) return 10_000n
|
|
102
|
+
return chain.logBlockRange
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function minLogBlockRangeForChain(chainId: number): bigint {
|
|
106
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
107
|
+
if (!chain) return 2_000n
|
|
108
|
+
return chain.kind === 'l2' ? chain.logBlockRange : chain.logBlockRange / 2n || 1n
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function rpcUrlsForClient(args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'>): string[] {
|
|
112
|
+
const chain = supportedErc8004ChainForId(args.chainId)
|
|
113
|
+
return uniqueStrings([
|
|
114
|
+
args.rpcUrl,
|
|
115
|
+
...(chain && args.rpcUrl !== chain.rpcUrl ? [chain.rpcUrl] : []),
|
|
116
|
+
...(chain?.fallbackRpcUrls ?? []),
|
|
117
|
+
])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function chainForId(chainId: number): Chain | undefined {
|
|
121
|
+
switch (chainId) {
|
|
122
|
+
case mainnet.id: return mainnet
|
|
123
|
+
case base.id: return base
|
|
124
|
+
default: return undefined
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function chainEntry(
|
|
129
|
+
chainId: number,
|
|
130
|
+
name: string,
|
|
131
|
+
rpcUrl: string,
|
|
132
|
+
fallbackRpcUrls: string[],
|
|
133
|
+
identityRegistryAddress: string | undefined,
|
|
134
|
+
fromBlock: bigint | undefined,
|
|
135
|
+
logBlockRange: bigint,
|
|
136
|
+
kind: SupportedErc8004Chain['kind'],
|
|
137
|
+
network: SelectableNetwork,
|
|
138
|
+
): SupportedErc8004Chain {
|
|
139
|
+
return {
|
|
140
|
+
chainId,
|
|
141
|
+
name,
|
|
142
|
+
rpcUrl: rpcUrl.replace(/\/$/, ''),
|
|
143
|
+
fallbackRpcUrls: fallbackRpcUrls.map(url => url.replace(/\/$/, '')),
|
|
144
|
+
...(identityRegistryAddress ? { identityRegistryAddress: getAddress(identityRegistryAddress) } : {}),
|
|
145
|
+
...(fromBlock !== undefined ? { fromBlock } : {}),
|
|
146
|
+
logBlockRange,
|
|
147
|
+
kind,
|
|
148
|
+
network,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createPublicClient, fallback, http, type PublicClient } from 'viem'
|
|
2
|
+
import type { Erc8004RegistryConfig } from './types.js'
|
|
3
|
+
import { chainForId, rpcUrlsForClient } from './chains.js'
|
|
4
|
+
|
|
5
|
+
export function createErc8004PublicClient(args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'>): PublicClient {
|
|
6
|
+
const transports = rpcUrlsForClient(args).map(url => http(url, { retryCount: 0, timeout: 8_000 }))
|
|
7
|
+
return createPublicClient({
|
|
8
|
+
chain: chainForId(args.chainId),
|
|
9
|
+
transport: transports.length === 1 ? transports[0]! : fallback(transports, { retryCount: 0 }),
|
|
10
|
+
})
|
|
11
|
+
}
|