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.
Files changed (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +124 -32
  3. package/package.json +8 -3
  4. package/src/app/FirstRun.tsx +190 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /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
- export type ImageFilePickerResult =
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 image path manually'
32
- : 'no native image picker is available; enter the image path manually',
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: 'image selection cancelled' }
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 agent image"',
59
- '$dialog.Filter = "Images (*.png;*.jpg;*.jpeg;*.gif;*.webp;*.svg)|*.png;*.jpg;*.jpeg;*.gif;*.webp;*.svg|All files (*.*)|*.*"',
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 agent image"',
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 agent image',
86
- '--file-filter=Images | *.png *.jpg *.jpeg *.gif *.webp *.svg',
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', '.', 'Images (*.png *.jpg *.jpeg *.gif *.webp *.svg)'],
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: 'image picker timed out' })
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 ? 'image selection cancelled' : detail || `${command.method} exited ${code}` })
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
+ }