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.
Files changed (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +126 -30
  3. package/package.json +7 -2
  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,511 @@
1
+ import { getAddress, isAddress, type Address, type PublicClient } from 'viem'
2
+ import { mainnet } from 'viem/chains'
3
+ import { DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
4
+ import { isAgentInVault } from '../operatorVault.js'
5
+ import { ERC8004_ABI, TRANSFER_EVENT } from './abi.js'
6
+ import {
7
+ SUPPORTED_ERC8004_CHAINS,
8
+ erc8004ConfigForSupportedChain,
9
+ supportedErc8004ChainForId,
10
+ } from './chains.js'
11
+ import { createErc8004PublicClient } from './client.js'
12
+ import { parseEthagentBackupPointer, parseEthagentOperatorsPointer, parseEthagentPublicDiscoveryPointer } from './metadata.js'
13
+ import type { DiscoverOwnedAgentsAcrossSupportedNetworksArgs, DiscoverOwnedAgentsArgs, Erc8004AgentCandidate, Erc8004RegistryConfig, EthagentOperatorsPointer } from './types.js'
14
+ import { loadAgentRegistrationWithRetry } from './uri.js'
15
+ import { cleanRpcError, mapWithConcurrency } from './utils.js'
16
+ import { stringField } from '../fieldParsers.js'
17
+
18
+ const DISCOVERY_CONCURRENCY = 2
19
+
20
+ type TransferLog = { args: { tokenId?: bigint } }
21
+
22
+ export class AgentTokenIdRequiredError extends Error {
23
+ ownerAddress: Address
24
+ registry: Erc8004RegistryConfig
25
+ balance: bigint
26
+ detail?: string
27
+
28
+ constructor(args: {
29
+ ownerAddress: Address
30
+ registry: Erc8004RegistryConfig
31
+ balance: bigint
32
+ detail?: string
33
+ }) {
34
+ const chain = supportedErc8004ChainForId(args.registry.chainId)
35
+ const label = chain?.network ?? chain?.name ?? `chain ${args.registry.chainId}`
36
+ super(`Automatic ${label} token ownership lookup could not enumerate this wallet's ERC-8004 token IDs.`)
37
+ this.name = 'AgentTokenIdRequiredError'
38
+ this.ownerAddress = args.ownerAddress
39
+ this.registry = args.registry
40
+ this.balance = args.balance
41
+ if (args.detail) this.detail = cleanRpcError(args.detail)
42
+ }
43
+ }
44
+
45
+ async function resolveOwnerHandle(
46
+ ownerHandle: string,
47
+ args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'> & { publicClient?: PublicClient },
48
+ ): Promise<Address> {
49
+ const trimmed = ownerHandle.trim()
50
+ if (isAddress(trimmed)) return getAddress(trimmed)
51
+ if (!trimmed.includes('.')) throw new Error('Enter an Ethereum address or ENS name')
52
+
53
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args)
54
+ const resolved = await publicClient.getEnsAddress({ name: trimmed })
55
+ if (!resolved) throw new Error(`ENS name did not resolve: ${trimmed}`)
56
+ return getAddress(resolved)
57
+ }
58
+
59
+ export async function discoverOwnedAgentBackups(args: DiscoverOwnedAgentsArgs): Promise<Erc8004AgentCandidate[]> {
60
+ if (args.signal?.aborted) throw new DOMException('discovery cancelled', 'AbortError')
61
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args)
62
+ const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
63
+ const fromBlock = args.fromBlock ?? supportedErc8004ChainForId(args.chainId)?.fromBlock ?? 0n
64
+ const tokenIds = await findCandidateTokenIds({
65
+ publicClient,
66
+ registry: args,
67
+ ownerAddress,
68
+ fromBlock,
69
+ })
70
+ const out: Erc8004AgentCandidate[] = []
71
+ for (const tokenId of tokenIds) {
72
+ if (args.signal?.aborted) throw new DOMException('discovery cancelled', 'AbortError')
73
+ const candidate = await loadOwnedAgentCandidate({
74
+ ...args,
75
+ publicClient,
76
+ ownerAddress,
77
+ tokenId,
78
+ }).catch(err => {
79
+ if (err instanceof TokenOwnerMismatchError) return null
80
+ throw err
81
+ })
82
+ if (candidate) out.push(candidate)
83
+ }
84
+ return out.sort((a, b) => Number(b.agentId - a.agentId))
85
+ }
86
+
87
+ export async function discoverOwnedAgentBackupByTokenId(args: DiscoverOwnedAgentsArgs & {
88
+ tokenId: bigint
89
+ }): Promise<Erc8004AgentCandidate> {
90
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args)
91
+ const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
92
+ return loadOwnedAgentCandidate({
93
+ ...args,
94
+ publicClient,
95
+ ownerAddress,
96
+ tokenId: args.tokenId,
97
+ })
98
+ }
99
+
100
+ export async function discoverOwnedAgentBackupsAcrossSupportedNetworks(
101
+ args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
102
+ ): Promise<Erc8004AgentCandidate[]> {
103
+ const ownerAddress = await resolveOwnerAddressForSupportedLookup(args)
104
+ const configs = SUPPORTED_ERC8004_CHAINS.map(chain => {
105
+ const override = args.registryOverrides?.find(item => item.chainId === chain.chainId)
106
+ return override ?? erc8004ConfigForSupportedChain(chain.chainId)
107
+ })
108
+ const results = await mapWithConcurrency(configs, DISCOVERY_CONCURRENCY, async config => {
109
+ try {
110
+ return {
111
+ ok: true as const,
112
+ candidates: await discoverOwnedAgentBackups({
113
+ ...config,
114
+ ownerHandle: ownerAddress,
115
+ ipfsApiUrl: args.ipfsApiUrl,
116
+ publicClient: args.publicClients?.[config.chainId],
117
+ fetchImpl: args.fetchImpl,
118
+ ...(args.signal ? { signal: args.signal } : {}),
119
+ }),
120
+ }
121
+ } catch (err: unknown) {
122
+ return { ok: false as const, error: err }
123
+ }
124
+ })
125
+
126
+ const candidates = results.flatMap(result => result.ok ? result.candidates : [])
127
+ if (candidates.length > 0) {
128
+ return candidates.sort(compareCandidatesByNetworkThenNewest)
129
+ }
130
+ const failures = results.filter(result => !result.ok)
131
+ if (failures.length === results.length && failures.length > 0) {
132
+ throw new Error(`lookup failed on all supported networks: ${cleanRpcError(failures[0]!.error)}`)
133
+ }
134
+ const tokenIdRequired = failures
135
+ .map(result => result.error)
136
+ .find((err): err is AgentTokenIdRequiredError => err instanceof AgentTokenIdRequiredError)
137
+ if (tokenIdRequired) throw tokenIdRequired
138
+ return []
139
+ }
140
+
141
+ async function findCandidateTokenIds(args: {
142
+ publicClient: PublicClient
143
+ registry: Erc8004RegistryConfig
144
+ ownerAddress: Address
145
+ fromBlock: bigint
146
+ }): Promise<bigint[]> {
147
+ const tokenIds = new Set<bigint>()
148
+ let balance: bigint | undefined
149
+ let attempt = 0
150
+ while (true) {
151
+ try {
152
+ balance = await args.publicClient.readContract({
153
+ address: args.registry.identityRegistryAddress,
154
+ abi: ERC8004_ABI,
155
+ functionName: 'balanceOf',
156
+ args: [args.ownerAddress],
157
+ }) as bigint
158
+ break
159
+ } catch (err: unknown) {
160
+ if (++attempt > 3) {
161
+ throw new AgentTokenIdRequiredError({
162
+ ownerAddress: args.ownerAddress,
163
+ registry: args.registry,
164
+ balance: 0n,
165
+ detail: cleanRpcError(err),
166
+ })
167
+ }
168
+ await new Promise(r => setTimeout(r, attempt * 1000))
169
+ }
170
+ }
171
+ if (balance === 0n) return []
172
+
173
+ const enumerableTokenIds = await findEnumerableTokenIds({
174
+ publicClient: args.publicClient,
175
+ registry: args.registry,
176
+ ownerAddress: args.ownerAddress,
177
+ balance,
178
+ })
179
+ if (enumerableTokenIds) return enumerableTokenIds
180
+
181
+ try {
182
+ for await (const logs of getTransferLogChunksBackwards({
183
+ publicClient: args.publicClient,
184
+ registry: args.registry,
185
+ ownerAddress: args.ownerAddress,
186
+ fromBlock: args.fromBlock,
187
+ })) {
188
+ for (const log of logs) {
189
+ const tokenId = log.args.tokenId
190
+ if (tokenId === undefined || tokenIds.has(tokenId)) continue
191
+ if (await isCurrentTokenOwner(args.publicClient, args.registry.identityRegistryAddress, tokenId, args.ownerAddress)) {
192
+ tokenIds.add(tokenId)
193
+ if (BigInt(tokenIds.size) >= balance) return [...tokenIds]
194
+ }
195
+ }
196
+ }
197
+ } catch (err: unknown) {
198
+ throw new AgentTokenIdRequiredError({
199
+ ownerAddress: args.ownerAddress,
200
+ registry: args.registry,
201
+ balance,
202
+ detail: cleanRpcError(err),
203
+ })
204
+ }
205
+ if (BigInt(tokenIds.size) < balance) {
206
+ throw new AgentTokenIdRequiredError({
207
+ ownerAddress: args.ownerAddress,
208
+ registry: args.registry,
209
+ balance,
210
+ detail: 'Owned token ids were not found in logs',
211
+ })
212
+ }
213
+ return [...tokenIds]
214
+ }
215
+
216
+ async function* getTransferLogChunksBackwards(args: {
217
+ publicClient: PublicClient
218
+ registry: Erc8004RegistryConfig
219
+ ownerAddress: Address
220
+ fromBlock: bigint
221
+ }): AsyncGenerator<TransferLog[]> {
222
+ const latest = await args.publicClient.getBlockNumber()
223
+ if (args.fromBlock > latest) return
224
+
225
+ const ranges = blockRangesBackwards(args.fromBlock, latest, logBlockRangeForChain(args.registry.chainId))
226
+ const CONCURRENCY = 5
227
+
228
+ for (let i = 0; i < ranges.length; i += CONCURRENCY) {
229
+ const batch = ranges.slice(i, i + CONCURRENCY)
230
+ const logsArrays = await Promise.all(batch.map(async range => {
231
+ try {
232
+ return await getTransferLogsAdaptive({
233
+ ...args,
234
+ fromBlock: range.fromBlock,
235
+ toBlock: range.toBlock,
236
+ minBlockRange: minLogBlockRangeForChain(args.registry.chainId),
237
+ })
238
+ } catch {
239
+ return [] as TransferLog[]
240
+ }
241
+ }))
242
+ for (const logs of logsArrays) {
243
+ if (logs.length > 0) yield logs
244
+ }
245
+ }
246
+ }
247
+
248
+ async function findEnumerableTokenIds(args: {
249
+ publicClient: PublicClient
250
+ registry: Erc8004RegistryConfig
251
+ ownerAddress: Address
252
+ balance: bigint
253
+ }): Promise<bigint[] | null> {
254
+ const tokenIds: bigint[] = []
255
+ try {
256
+ for (let index = 0n; index < args.balance; index += 1n) {
257
+ const tokenId = await args.publicClient.readContract({
258
+ address: args.registry.identityRegistryAddress,
259
+ abi: ERC8004_ABI,
260
+ functionName: 'tokenOfOwnerByIndex',
261
+ args: [args.ownerAddress, index],
262
+ }) as bigint
263
+ tokenIds.push(tokenId)
264
+ }
265
+ return tokenIds
266
+ } catch {
267
+ return null
268
+ }
269
+ }
270
+
271
+ async function getTransferLogsAdaptive(args: {
272
+ publicClient: PublicClient
273
+ registry: Erc8004RegistryConfig
274
+ ownerAddress: Address
275
+ fromBlock: bigint
276
+ toBlock: bigint
277
+ minBlockRange: bigint
278
+ }): Promise<TransferLog[]> {
279
+ const size = args.toBlock - args.fromBlock + 1n
280
+ let attempt = 0
281
+ while (true) {
282
+ try {
283
+ const logs = await args.publicClient.getLogs({
284
+ address: args.registry.identityRegistryAddress,
285
+ event: TRANSFER_EVENT,
286
+ args: { to: args.ownerAddress },
287
+ fromBlock: args.fromBlock,
288
+ toBlock: args.toBlock,
289
+ })
290
+ return logs as TransferLog[]
291
+ } catch (err: unknown) {
292
+ attempt++
293
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase()
294
+ const isSizeLimit = msg.includes('limit') || msg.includes('range') || msg.includes('too many') || msg.includes('exceeds') || msg.includes('block count')
295
+
296
+ if (!isSizeLimit && attempt <= 3) {
297
+ await new Promise(r => setTimeout(r, attempt * 1000))
298
+ continue
299
+ }
300
+ if (size <= args.minBlockRange) {
301
+ if (attempt <= 3) {
302
+ await new Promise(r => setTimeout(r, attempt * 1000))
303
+ continue
304
+ }
305
+ throw err
306
+ }
307
+
308
+ const mid = args.fromBlock + size / 2n - 1n
309
+ const [newer, older] = await Promise.all([
310
+ getTransferLogsAdaptive({ ...args, fromBlock: mid + 1n, toBlock: args.toBlock }),
311
+ getTransferLogsAdaptive({ ...args, fromBlock: args.fromBlock, toBlock: mid })
312
+ ])
313
+ return [...newer, ...older]
314
+ }
315
+ }
316
+ }
317
+
318
+ class TokenOwnerMismatchError extends Error {
319
+ constructor() {
320
+ super('Wallet is not the token owner or an operator wallet')
321
+ this.name = 'TokenOwnerMismatchError'
322
+ }
323
+ }
324
+
325
+ class MetadataFetchError extends Error {
326
+ readonly tokenId: bigint
327
+ readonly agentUri: string
328
+ override readonly cause: unknown
329
+ constructor(tokenId: bigint, agentUri: string, cause: unknown) {
330
+ super(`failed to fetch agent metadata for token #${tokenId.toString()} at ${agentUri}: ${cause instanceof Error ? cause.message : String(cause)}`)
331
+ this.name = 'MetadataFetchError'
332
+ this.tokenId = tokenId
333
+ this.agentUri = agentUri
334
+ this.cause = cause
335
+ }
336
+ }
337
+
338
+ function isAuthorizedAgentLookupAddress(args: {
339
+ requesterAddress: Address
340
+ tokenOwnerAddress: Address
341
+ operators: EthagentOperatorsPointer | null
342
+ }): boolean {
343
+ const requester = args.requesterAddress.toLowerCase()
344
+ if (args.tokenOwnerAddress.toLowerCase() === requester) return true
345
+ if (args.operators?.activeOperatorAddress?.toLowerCase() === requester) return true
346
+ return args.operators?.approvedOperatorWallets.some(record => record.address.toLowerCase() === requester) ?? false
347
+ }
348
+
349
+ async function loadOwnedAgentCandidate(args: DiscoverOwnedAgentsArgs & {
350
+ publicClient: PublicClient
351
+ ownerAddress: Address
352
+ tokenId: bigint
353
+ }): Promise<Erc8004AgentCandidate> {
354
+ let attempt = 0
355
+ let currentOwner: Address | undefined
356
+ let agentUri: string | undefined
357
+ while (true) {
358
+ try {
359
+ if (!currentOwner) {
360
+ currentOwner = await args.publicClient.readContract({
361
+ address: args.identityRegistryAddress,
362
+ abi: ERC8004_ABI,
363
+ functionName: 'ownerOf',
364
+ args: [args.tokenId],
365
+ }) as Address
366
+ }
367
+ if (!agentUri) {
368
+ agentUri = await args.publicClient.readContract({
369
+ address: args.identityRegistryAddress,
370
+ abi: ERC8004_ABI,
371
+ functionName: 'tokenURI',
372
+ args: [args.tokenId],
373
+ }) as string
374
+ }
375
+ break
376
+ } catch (err: unknown) {
377
+ if (++attempt > 3) throw err
378
+ await new Promise(r => setTimeout(r, attempt * 1000))
379
+ }
380
+ }
381
+
382
+ const tokenOwnerAddress = getAddress(currentOwner)
383
+ let loaded: { metadataCid?: string; registration: Record<string, unknown> }
384
+ try {
385
+ loaded = await loadAgentRegistrationWithRetry(agentUri, {
386
+ ipfsApiUrl: args.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
387
+ fetchImpl: args.fetchImpl,
388
+ ...(args.signal ? { signal: args.signal } : {}),
389
+ })
390
+ } catch (err: unknown) {
391
+ if (args.signal?.aborted || (err instanceof Error && err.name === 'AbortError')) throw err
392
+ throw new MetadataFetchError(args.tokenId, agentUri, err)
393
+ }
394
+ const parsed = parseEthagentBackupPointer(loaded.registration)
395
+ const publicDiscovery = parseEthagentPublicDiscoveryPointer(loaded.registration)
396
+ const operators = parseEthagentOperatorsPointer(loaded.registration)
397
+ let vaultLevelOwner: Address | undefined
398
+ try {
399
+ const status = await isAgentInVault({
400
+ client: args.publicClient,
401
+ vaultAddress: tokenOwnerAddress,
402
+ registry: args.identityRegistryAddress,
403
+ agentId: args.tokenId,
404
+ })
405
+ if (status.inVault && status.ownerAddress) {
406
+ vaultLevelOwner = status.ownerAddress
407
+ }
408
+ } catch {
409
+ vaultLevelOwner = undefined
410
+ }
411
+ if (!isAuthorizedAgentLookupAddress({
412
+ requesterAddress: args.ownerAddress,
413
+ tokenOwnerAddress,
414
+ operators,
415
+ })) {
416
+ if (!vaultLevelOwner || vaultLevelOwner.toLowerCase() !== args.ownerAddress.toLowerCase()) {
417
+ throw new TokenOwnerMismatchError()
418
+ }
419
+ }
420
+ const ownerAddress = vaultLevelOwner ?? tokenOwnerAddress
421
+ return {
422
+ tokenOwnerAddress,
423
+ ownerAddress,
424
+ chainId: args.chainId,
425
+ rpcUrl: args.rpcUrl,
426
+ identityRegistryAddress: args.identityRegistryAddress,
427
+ agentId: args.tokenId,
428
+ agentUri,
429
+ metadataCid: loaded.metadataCid,
430
+ name: stringField(loaded.registration, 'name'),
431
+ description: stringField(loaded.registration, 'description'),
432
+ imageUrl: stringField(loaded.registration, 'image'),
433
+ backup: parsed ?? undefined,
434
+ publicDiscovery: publicDiscovery ?? undefined,
435
+ operators: operators ?? undefined,
436
+ registration: loaded.registration,
437
+ }
438
+ }
439
+
440
+ async function isCurrentTokenOwner(
441
+ publicClient: PublicClient,
442
+ registry: Address,
443
+ tokenId: bigint,
444
+ ownerAddress: Address,
445
+ ): Promise<boolean> {
446
+ let attempt = 0
447
+ while (true) {
448
+ try {
449
+ const currentOwner = await publicClient.readContract({
450
+ address: registry,
451
+ abi: ERC8004_ABI,
452
+ functionName: 'ownerOf',
453
+ args: [tokenId],
454
+ }) as Address
455
+ return currentOwner.toLowerCase() === ownerAddress.toLowerCase()
456
+ } catch (err: unknown) {
457
+ if (++attempt > 3) throw err
458
+ await new Promise(r => setTimeout(r, attempt * 1000))
459
+ }
460
+ }
461
+ }
462
+
463
+ async function resolveOwnerAddressForSupportedLookup(
464
+ args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
465
+ ): Promise<Address> {
466
+ const trimmed = args.ownerHandle.trim()
467
+ if (isAddress(trimmed)) return getAddress(trimmed)
468
+ const mainnetConfig = erc8004ConfigForSupportedChain(mainnet.id)
469
+ return resolveOwnerHandle(trimmed, {
470
+ ...mainnetConfig,
471
+ publicClient: args.publicClients?.[mainnet.id],
472
+ })
473
+ }
474
+
475
+ function compareCandidatesByNetworkThenNewest(a: Erc8004AgentCandidate, b: Erc8004AgentCandidate): number {
476
+ const networkOrder = chainSortIndex(a.chainId) - chainSortIndex(b.chainId)
477
+ if (networkOrder !== 0) return networkOrder
478
+ return Number(b.agentId - a.agentId)
479
+ }
480
+
481
+ function blockRangesBackwards(
482
+ fromBlock: bigint,
483
+ latest: bigint,
484
+ blockRange: bigint,
485
+ ): Array<{ fromBlock: bigint; toBlock: bigint }> {
486
+ const ranges: Array<{ fromBlock: bigint; toBlock: bigint }> = []
487
+ for (let end = latest; end >= fromBlock;) {
488
+ const start = end - blockRange + 1n > fromBlock ? end - blockRange + 1n : fromBlock
489
+ ranges.push({ fromBlock: start, toBlock: end })
490
+ if (start === fromBlock) break
491
+ end = start - 1n
492
+ }
493
+ return ranges
494
+ }
495
+
496
+ function logBlockRangeForChain(chainId: number): bigint {
497
+ const chain = supportedErc8004ChainForId(chainId)
498
+ if (!chain) return 10_000n
499
+ return chain.logBlockRange
500
+ }
501
+
502
+ function minLogBlockRangeForChain(chainId: number): bigint {
503
+ const chain = supportedErc8004ChainForId(chainId)
504
+ if (!chain) return 2_000n
505
+ return chain.kind === 'l2' ? chain.logBlockRange : chain.logBlockRange / 2n || 1n
506
+ }
507
+
508
+ function chainSortIndex(chainId: number): number {
509
+ const index = SUPPORTED_ERC8004_CHAINS.findIndex(chain => chain.chainId === chainId)
510
+ return index === -1 ? Number.MAX_SAFE_INTEGER : index
511
+ }