ethagent 1.1.1 → 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 (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +127 -29
  3. package/package.json +16 -9
  4. package/src/app/FirstRun.tsx +192 -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 +43 -18
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +11 -17
  13. package/src/chat/ConversationStack.tsx +3 -0
  14. package/src/chat/CopyPicker.tsx +0 -1
  15. package/src/chat/MessageList.tsx +62 -45
  16. package/src/chat/PermissionPrompt.tsx +13 -9
  17. package/src/chat/PlanApprovalView.tsx +3 -3
  18. package/src/chat/ResumeView.tsx +1 -4
  19. package/src/chat/RewindView.tsx +2 -2
  20. package/src/chat/TranscriptView.tsx +6 -0
  21. package/src/chat/chatInputState.ts +1 -1
  22. package/src/chat/chatScreenUtils.ts +22 -11
  23. package/src/chat/chatSessionState.ts +2 -2
  24. package/src/chat/chatTurnOrchestrator.ts +16 -81
  25. package/src/chat/commands.ts +1 -1
  26. package/src/chat/textCursor.ts +1 -1
  27. package/src/chat/transcriptViewport.ts +2 -7
  28. package/src/cli/ResetConfirmView.tsx +1 -1
  29. package/src/cli/main.tsx +9 -3
  30. package/src/cli/preview.tsx +0 -5
  31. package/src/cli/updateNotice.ts +5 -3
  32. package/src/identity/continuity/editor.ts +7 -107
  33. package/src/identity/continuity/envelope.ts +1048 -40
  34. package/src/identity/continuity/history.ts +4 -4
  35. package/src/identity/continuity/localBackup.ts +249 -0
  36. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  37. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  38. package/src/identity/continuity/privateEdit/files.ts +23 -0
  39. package/src/identity/continuity/privateEdit/types.ts +28 -0
  40. package/src/identity/continuity/privateEdit.ts +10 -298
  41. package/src/identity/continuity/publicSkills.ts +8 -9
  42. package/src/identity/continuity/snapshots.ts +17 -6
  43. package/src/identity/continuity/storage/defaults.ts +111 -0
  44. package/src/identity/continuity/storage/files.ts +72 -0
  45. package/src/identity/continuity/storage/markdown.ts +81 -0
  46. package/src/identity/continuity/storage/paths.ts +24 -0
  47. package/src/identity/continuity/storage/scaffold.ts +124 -0
  48. package/src/identity/continuity/storage/status.ts +86 -0
  49. package/src/identity/continuity/storage/types.ts +27 -0
  50. package/src/identity/continuity/storage.ts +32 -507
  51. package/src/identity/continuity/zipWriter.ts +95 -0
  52. package/src/identity/crypto/backupEnvelope.ts +14 -247
  53. package/src/identity/crypto/eth.ts +7 -7
  54. package/src/identity/ens/agentRecords.ts +96 -0
  55. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  56. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  57. package/src/identity/ens/ensAutomation/names.ts +14 -0
  58. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  59. package/src/identity/ens/ensAutomation/read.ts +114 -0
  60. package/src/identity/ens/ensAutomation/root.ts +63 -0
  61. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  62. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  63. package/src/identity/ens/ensAutomation/types.ts +126 -0
  64. package/src/identity/ens/ensAutomation.ts +29 -0
  65. package/src/identity/ens/ensLookup/client.ts +43 -0
  66. package/src/identity/ens/ensLookup/constants.ts +26 -0
  67. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  68. package/src/identity/ens/ensLookup/names.ts +34 -0
  69. package/src/identity/ens/ensLookup/records.ts +45 -0
  70. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  71. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  72. package/src/identity/ens/ensLookup/types.ts +38 -0
  73. package/src/identity/ens/ensLookup/validation.ts +72 -0
  74. package/src/identity/ens/ensLookup.ts +19 -0
  75. package/src/identity/ens/ensRegistration.ts +199 -0
  76. package/src/identity/ens/resolverDelegation.ts +48 -0
  77. package/src/identity/hub/IdentityHub.tsx +13 -815
  78. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  79. package/src/identity/hub/Routes.tsx +361 -0
  80. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  81. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  82. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  83. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  84. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  85. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  86. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  87. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  88. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  89. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  90. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  91. package/src/identity/hub/effects/create.ts +310 -0
  92. package/src/identity/hub/effects/ens/flows.ts +218 -0
  93. package/src/identity/hub/effects/ens/index.ts +11 -0
  94. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  95. package/src/identity/hub/effects/index.ts +74 -0
  96. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  97. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  98. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  99. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  100. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  101. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  102. package/src/identity/hub/effects/receipts.ts +46 -0
  103. package/src/identity/hub/effects/restore/apply.ts +112 -0
  104. package/src/identity/hub/effects/restore/auth.ts +159 -0
  105. package/src/identity/hub/effects/restore/discover.ts +86 -0
  106. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  107. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  108. package/src/identity/hub/effects/restore/index.ts +22 -0
  109. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  110. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  111. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  112. package/src/identity/hub/effects/restore/shared.ts +91 -0
  113. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  114. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  115. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  116. package/src/identity/hub/effects/shared/sync.ts +190 -0
  117. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  118. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  119. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  120. package/src/identity/hub/effects/types.ts +53 -0
  121. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  122. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  123. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  124. package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
  125. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  126. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  127. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  128. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  129. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  130. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  131. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  132. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  133. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  134. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  135. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  136. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  137. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  138. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  139. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  140. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  141. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  142. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  143. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  144. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  145. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  146. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  147. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  148. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  149. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
  150. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  151. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  152. package/src/identity/hub/identityHubReducer.ts +166 -101
  153. package/src/identity/hub/model/continuity.ts +94 -0
  154. package/src/identity/hub/model/copy.ts +35 -0
  155. package/src/identity/hub/model/custody.ts +54 -0
  156. package/src/identity/hub/model/ens.ts +49 -0
  157. package/src/identity/hub/model/errors.ts +140 -0
  158. package/src/identity/hub/model/format.ts +15 -0
  159. package/src/identity/hub/model/identity.ts +94 -0
  160. package/src/identity/hub/model/network.ts +32 -0
  161. package/src/identity/hub/model/transfer.ts +57 -0
  162. package/src/identity/hub/operatorWallets.ts +131 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  165. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  166. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  167. package/src/identity/hub/reconciliation/index.ts +21 -0
  168. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  169. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  170. package/src/identity/hub/txGuard.ts +51 -0
  171. package/src/identity/hub/types.ts +17 -0
  172. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  173. package/src/identity/hub/useIdentityHubController.ts +396 -0
  174. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  175. package/src/identity/hub/utils.ts +79 -0
  176. package/src/identity/identityCompat.ts +34 -0
  177. package/src/identity/profile/agentIcon.ts +61 -0
  178. package/src/identity/profile/imagePicker.ts +12 -12
  179. package/src/identity/registry/erc8004/abi.ts +14 -0
  180. package/src/identity/registry/erc8004/chains.ts +150 -0
  181. package/src/identity/registry/erc8004/client.ts +11 -0
  182. package/src/identity/registry/erc8004/discovery.ts +511 -0
  183. package/src/identity/registry/erc8004/metadata.ts +335 -0
  184. package/src/identity/registry/erc8004/ownership.ts +121 -0
  185. package/src/identity/registry/erc8004/preflight.ts +123 -0
  186. package/src/identity/registry/erc8004/transactions.ts +77 -0
  187. package/src/identity/registry/erc8004/types.ts +88 -0
  188. package/src/identity/registry/erc8004/uri.ts +59 -0
  189. package/src/identity/registry/erc8004/utils.ts +58 -0
  190. package/src/identity/registry/erc8004.ts +53 -1106
  191. package/src/identity/registry/fieldParsers.ts +28 -0
  192. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  193. package/src/identity/registry/operatorVault/constants.ts +38 -0
  194. package/src/identity/registry/operatorVault/read.ts +246 -0
  195. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  196. package/src/identity/registry/operatorVault.ts +44 -0
  197. package/src/identity/storage/ipfs.ts +26 -24
  198. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  199. package/src/identity/wallet/browserWallet/html.ts +106 -0
  200. package/src/identity/wallet/browserWallet/http.ts +28 -0
  201. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  202. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  203. package/src/identity/wallet/browserWallet/session.ts +325 -0
  204. package/src/identity/wallet/browserWallet/types.ts +192 -0
  205. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  206. package/src/identity/wallet/browserWallet.ts +30 -393
  207. package/src/identity/wallet/page/constants.ts +5 -0
  208. package/src/identity/wallet/page/controller.ts +251 -0
  209. package/src/identity/wallet/page/copy.ts +340 -0
  210. package/src/identity/wallet/page/grainient.ts +278 -0
  211. package/src/identity/wallet/page/html.ts +28 -0
  212. package/src/identity/wallet/page/markup.ts +50 -0
  213. package/src/identity/wallet/page/state.ts +9 -0
  214. package/src/identity/wallet/page/styles/base.ts +259 -0
  215. package/src/identity/wallet/page/styles/components.ts +262 -0
  216. package/src/identity/wallet/page/styles/index.ts +5 -0
  217. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  218. package/src/identity/wallet/page/types.ts +47 -0
  219. package/src/identity/wallet/page/view.ts +535 -0
  220. package/src/identity/wallet/page/walletProvider.ts +70 -0
  221. package/src/identity/wallet/page.tsx +38 -0
  222. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  223. package/src/mcp/manager.ts +0 -1
  224. package/src/models/ModelPicker.tsx +36 -30
  225. package/src/models/catalog.ts +5 -2
  226. package/src/models/huggingface.ts +9 -9
  227. package/src/models/llamacpp.ts +13 -13
  228. package/src/models/modelDisplay.ts +75 -0
  229. package/src/models/modelPickerOptions.ts +16 -3
  230. package/src/models/modelRecommendation.ts +0 -1
  231. package/src/providers/errors.ts +16 -0
  232. package/src/providers/gemini.ts +252 -39
  233. package/src/providers/registry.ts +2 -2
  234. package/src/providers/retry.ts +1 -1
  235. package/src/runtime/sessionMode.ts +1 -1
  236. package/src/runtime/systemPrompt.ts +2 -0
  237. package/src/runtime/toolExecution.ts +18 -22
  238. package/src/runtime/toolIntent.ts +0 -20
  239. package/src/runtime/turn.ts +0 -92
  240. package/src/storage/atomicWrite.ts +4 -1
  241. package/src/storage/config.ts +181 -5
  242. package/src/storage/identity.ts +9 -3
  243. package/src/storage/secrets.ts +2 -2
  244. package/src/tools/bashSafety.ts +8 -0
  245. package/src/tools/changeDirectoryTool.ts +1 -1
  246. package/src/tools/deleteFileTool.ts +4 -4
  247. package/src/tools/editTool.ts +4 -4
  248. package/src/tools/editUtils.ts +5 -5
  249. package/src/tools/privateContinuityEditTool.ts +4 -5
  250. package/src/tools/privateContinuityReadTool.ts +1 -2
  251. package/src/tools/registry.ts +30 -0
  252. package/src/tools/writeFileTool.ts +5 -5
  253. package/src/ui/BrandSplash.tsx +20 -85
  254. package/src/ui/ProgressBar.tsx +3 -5
  255. package/src/ui/Select.tsx +21 -9
  256. package/src/ui/Spinner.tsx +38 -3
  257. package/src/ui/Surface.tsx +3 -3
  258. package/src/ui/TextInput.tsx +191 -29
  259. package/src/ui/theme.ts +7 -34
  260. package/src/utils/openExternal.ts +21 -0
  261. package/src/utils/withRetry.ts +47 -3
  262. package/src/identity/hub/identityHubEffects.ts +0 -937
  263. package/src/identity/hub/identityHubModel.ts +0 -291
  264. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
  265. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
  266. package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
  267. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  268. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
  269. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  270. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  271. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -1,1106 +1,53 @@
1
- import {
2
- createPublicClient,
3
- encodeFunctionData,
4
- fallback,
5
- getAddress,
6
- http,
7
- isAddress,
8
- parseAbi,
9
- parseAbiItem,
10
- decodeEventLog,
11
- formatEther,
12
- type Address,
13
- type Chain,
14
- type Hex,
15
- type PublicClient,
16
- } from 'viem'
17
- import { mainnet, arbitrum, base, optimism, polygon } from 'viem/chains'
18
- import type { SelectableNetwork } from '../../storage/config.js'
19
- import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
20
-
21
- export const DEFAULT_ERC8004_CHAIN_ID = 1
22
- export const DEFAULT_ETHEREUM_RPC_URL = 'https://ethereum.publicnode.com'
23
- export const DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432'
24
- const DISCOVERY_CONCURRENCY = 2
25
-
26
- export type SupportedErc8004Chain = {
27
- chainId: number
28
- name: string
29
- rpcUrl: string
30
- fallbackRpcUrls: string[]
31
- identityRegistryAddress?: Address
32
- fromBlock?: bigint
33
- logBlockRange: bigint
34
- kind: 'mainnet' | 'l2'
35
- network: SelectableNetwork
36
- }
37
-
38
- export const SUPPORTED_ERC8004_CHAINS: SupportedErc8004Chain[] = [
39
- chainEntry(mainnet.id, 'Ethereum Mainnet', DEFAULT_ETHEREUM_RPC_URL, [], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 24_339_871n, 10_000n, 'mainnet', 'mainnet'),
40
- chainEntry(arbitrum.id, 'Arbitrum One', 'https://arbitrum-one.publicnode.com', [], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 428_895_443n, 20_000n, 'l2', 'arbitrum'),
41
- chainEntry(base.id, 'Base', 'https://mainnet.base.org', ['https://base.publicnode.com'], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 41_663_783n, 10_000n, 'l2', 'base'),
42
- chainEntry(optimism.id, 'Optimism', 'https://optimism.publicnode.com', ['https://mainnet.optimism.io'], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 147_514_947n, 20_000n, 'l2', 'optimism'),
43
- chainEntry(polygon.id, 'Polygon', 'https://polygon-bor.publicnode.com', ['https://polygon-rpc.com'], DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS, 82_458_484n, 10_000n, 'l2', 'polygon'),
44
- ]
45
-
46
- const NETWORK_TO_CHAIN_ID: Record<SelectableNetwork, number> = {
47
- mainnet: mainnet.id,
48
- arbitrum: arbitrum.id,
49
- base: base.id,
50
- optimism: optimism.id,
51
- polygon: polygon.id,
52
- }
53
-
54
- export function chainIdForNetwork(network: SelectableNetwork): number {
55
- return NETWORK_TO_CHAIN_ID[network]
56
- }
57
-
58
- export function networkForChainId(chainId: number): SelectableNetwork | undefined {
59
- for (const [network, id] of Object.entries(NETWORK_TO_CHAIN_ID) as Array<[SelectableNetwork, number]>) {
60
- if (id === chainId) return network
61
- }
62
- return undefined
63
- }
64
-
65
- export class MissingRegistryAddressError extends Error {
66
- chainId: number
67
- network?: SelectableNetwork
68
- constructor(chainId: number) {
69
- const network = networkForChainId(chainId)
70
- super(`no default ERC-8004 registry on chain ${chainId}${network ? ` (${network})` : ''}`)
71
- this.name = 'MissingRegistryAddressError'
72
- this.chainId = chainId
73
- this.network = network
74
- }
75
- }
76
-
77
- const ERC8004_ABI = parseAbi([
78
- 'function register(string agentURI) returns (uint256)',
79
- 'function balanceOf(address owner) view returns (uint256)',
80
- 'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
81
- 'function ownerOf(uint256 tokenId) view returns (address)',
82
- 'function tokenURI(uint256 tokenId) view returns (string)',
83
- 'function setAgentURI(uint256 agentId, string newURI)',
84
- 'function getMetadata(uint256 agentId, string metadataKey) view returns (bytes)',
85
- ])
86
-
87
- const REGISTERED_EVENT = parseAbiItem('event Registered(uint256 indexed agentId, address indexed owner, string agentURI)')
88
- const TRANSFER_EVENT = parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)')
89
-
90
- type FetchLike = typeof fetch
91
- type TransferLog = { args: { tokenId?: bigint } }
92
- type ReceiptLog = { address?: Address; topics: readonly Hex[]; data: Hex }
93
- type RegisterAgentPreflightClient = {
94
- estimateGas: (args: { account: Address; to: Address; data: Hex }) => Promise<bigint>
95
- getGasPrice: () => Promise<bigint>
96
- getBalance: (args: { address: Address }) => Promise<bigint>
97
- }
98
-
99
- export type Erc8004RegistryConfig = {
100
- chainId: number
101
- rpcUrl: string
102
- identityRegistryAddress: Address
103
- fromBlock?: bigint
104
- }
105
-
106
- export type EthagentBackupPointer = {
107
- cid: string
108
- envelopeVersion?: string
109
- createdAt?: string
110
- agentAddress?: Address
111
- pastBackups?: Array<{ cid: string; createdAt?: string }>
112
- }
113
-
114
- export type EthagentPublicDiscoveryPointer = {
115
- skillsCid?: string
116
- agentCardCid?: string
117
- updatedAt?: string
118
- }
119
-
120
- export type EthagentRegistrationPointer = {
121
- chainId: number
122
- identityRegistryAddress: Address
123
- agentId?: string
124
- }
125
-
126
- export type Erc8004AgentCandidate = {
127
- ownerAddress: Address
128
- chainId: number
129
- rpcUrl: string
130
- identityRegistryAddress: Address
131
- agentId: bigint
132
- agentUri: string
133
- metadataCid?: string
134
- name?: string
135
- description?: string
136
- imageUrl?: string
137
- backup?: EthagentBackupPointer
138
- publicDiscovery?: EthagentPublicDiscoveryPointer
139
- registration: Record<string, unknown> | null
140
- }
141
-
142
- export type DiscoverOwnedAgentsArgs = Erc8004RegistryConfig & {
143
- ownerHandle: string
144
- ipfsApiUrl?: string
145
- publicClient?: PublicClient
146
- fetchImpl?: FetchLike
147
- }
148
-
149
- export type DiscoverOwnedAgentsAcrossSupportedNetworksArgs = {
150
- ownerHandle: string
151
- registryOverrides?: Erc8004RegistryConfig[]
152
- ipfsApiUrl?: string
153
- publicClients?: Partial<Record<number, PublicClient>>
154
- fetchImpl?: FetchLike
155
- }
156
-
157
- export type RegisterAgentPreflight = {
158
- gas: bigint
159
- gasPrice: bigint
160
- estimatedCostWei: bigint
161
- requiredBalanceWei: bigint
162
- balanceWei: bigint
163
- }
164
-
165
- export type RegisterAgentPreflightErrorCode = 'insufficient-funds' | 'simulation-failed'
166
-
167
- export class RegisterAgentPreflightError extends Error {
168
- code: RegisterAgentPreflightErrorCode
169
- title: string
170
- detail: string
171
- hint: string
172
- requiredBalanceWei?: bigint
173
- balanceWei?: bigint
174
-
175
- constructor(args: {
176
- code: RegisterAgentPreflightErrorCode
177
- title: string
178
- detail: string
179
- hint: string
180
- requiredBalanceWei?: bigint
181
- balanceWei?: bigint
182
- }) {
183
- super(args.title)
184
- this.name = 'RegisterAgentPreflightError'
185
- this.code = args.code
186
- this.title = args.title
187
- this.detail = args.detail
188
- this.hint = args.hint
189
- this.requiredBalanceWei = args.requiredBalanceWei
190
- this.balanceWei = args.balanceWei
191
- }
192
- }
193
-
194
- export class AgentTokenIdRequiredError extends Error {
195
- ownerAddress: Address
196
- registry: Erc8004RegistryConfig
197
- balance: bigint
198
- detail?: string
199
-
200
- constructor(args: {
201
- ownerAddress: Address
202
- registry: Erc8004RegistryConfig
203
- balance: bigint
204
- detail?: string
205
- }) {
206
- const chain = supportedErc8004ChainForId(args.registry.chainId)
207
- const label = chain?.network ?? chain?.name ?? `chain ${args.registry.chainId}`
208
- super(`Automatic ${label} lookup timed out. Enter the agent token ID to continue.`)
209
- this.name = 'AgentTokenIdRequiredError'
210
- this.ownerAddress = args.ownerAddress
211
- this.registry = args.registry
212
- this.balance = args.balance
213
- if (args.detail) this.detail = cleanRpcError(args.detail)
214
- }
215
- }
216
-
217
- export function createErc8004PublicClient(args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'>): PublicClient {
218
- const transports = rpcUrlsForClient(args).map(url => http(url, { retryCount: 0, timeout: 8_000 }))
219
- return createPublicClient({
220
- chain: chainForId(args.chainId),
221
- transport: transports.length === 1 ? transports[0]! : fallback(transports, { retryCount: 0 }),
222
- })
223
- }
224
-
225
- export function supportedErc8004ChainForId(chainId: number): SupportedErc8004Chain | undefined {
226
- return SUPPORTED_ERC8004_CHAINS.find(chain => chain.chainId === chainId)
227
- }
228
-
229
- export function normalizeErc8004RegistryConfig(input: {
230
- chainId?: number
231
- rpcUrl?: string
232
- identityRegistryAddress?: string
233
- fromBlock?: string | bigint
234
- }): Erc8004RegistryConfig {
235
- const chainId = input.chainId ?? DEFAULT_ERC8004_CHAIN_ID
236
- const chain = supportedErc8004ChainForId(chainId)
237
- const identityRegistryAddress = input.identityRegistryAddress?.trim() || chain?.identityRegistryAddress
238
- if (!identityRegistryAddress) throw new MissingRegistryAddressError(chainId)
239
- if (!isAddress(identityRegistryAddress)) throw new Error('invalid agent registry address')
240
- let parsedUrl: URL
241
- try {
242
- parsedUrl = new URL(input.rpcUrl?.trim() || chain?.rpcUrl || DEFAULT_ETHEREUM_RPC_URL)
243
- } catch {
244
- throw new Error('invalid Ethereum RPC URL')
245
- }
246
- if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
247
- throw new Error('Ethereum RPC URL must be http(s)')
248
- }
249
- return {
250
- chainId,
251
- rpcUrl: parsedUrl.toString().replace(/\/$/, ''),
252
- identityRegistryAddress: getAddress(identityRegistryAddress),
253
- fromBlock: input.fromBlock !== undefined ? BigInt(input.fromBlock) : chain?.fromBlock,
254
- }
255
- }
256
-
257
- export function erc8004ConfigForSupportedChain(chainId: number): Erc8004RegistryConfig {
258
- const chain = supportedErc8004ChainForId(chainId)
259
- if (!chain) throw new Error(`unsupported ERC-8004 chain id: ${chainId}`)
260
- return normalizeErc8004RegistryConfig(chain)
261
- }
262
-
263
- export async function resolveOwnerHandle(
264
- ownerHandle: string,
265
- args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'> & { publicClient?: PublicClient },
266
- ): Promise<Address> {
267
- const trimmed = ownerHandle.trim()
268
- if (isAddress(trimmed)) return getAddress(trimmed)
269
- if (!trimmed.includes('.')) throw new Error('enter an Ethereum address or ENS name')
270
-
271
- const publicClient = args.publicClient ?? createErc8004PublicClient(args)
272
- const resolved = await publicClient.getEnsAddress({ name: trimmed })
273
- if (!resolved) throw new Error(`ENS name did not resolve: ${trimmed}`)
274
- return getAddress(resolved)
275
- }
276
-
277
- export async function discoverOwnedAgentBackups(args: DiscoverOwnedAgentsArgs): Promise<Erc8004AgentCandidate[]> {
278
- const publicClient = args.publicClient ?? createErc8004PublicClient(args)
279
- const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
280
- const fromBlock = args.fromBlock ?? supportedErc8004ChainForId(args.chainId)?.fromBlock ?? 0n
281
- const tokenIds = await findCandidateTokenIds({
282
- publicClient,
283
- registry: args,
284
- ownerAddress,
285
- fromBlock,
286
- })
287
- const out: Erc8004AgentCandidate[] = []
288
- for (const tokenId of tokenIds) {
289
- const candidate = await loadOwnedAgentCandidate({
290
- ...args,
291
- publicClient,
292
- ownerAddress,
293
- tokenId,
294
- }).catch(err => {
295
- if (err instanceof TokenOwnerMismatchError) return null
296
- throw err
297
- })
298
- if (candidate) out.push(candidate)
299
- }
300
- return out.sort((a, b) => Number(b.agentId - a.agentId))
301
- }
302
-
303
- export async function discoverOwnedAgentBackupByTokenId(args: DiscoverOwnedAgentsArgs & {
304
- tokenId: bigint
305
- }): Promise<Erc8004AgentCandidate> {
306
- const publicClient = args.publicClient ?? createErc8004PublicClient(args)
307
- const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
308
- return loadOwnedAgentCandidate({
309
- ...args,
310
- publicClient,
311
- ownerAddress,
312
- tokenId: args.tokenId,
313
- })
314
- }
315
-
316
- export async function discoverOwnedAgentBackupsAcrossSupportedNetworks(
317
- args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
318
- ): Promise<Erc8004AgentCandidate[]> {
319
- const ownerAddress = await resolveOwnerAddressForSupportedLookup(args)
320
- const configs = SUPPORTED_ERC8004_CHAINS.map(chain => {
321
- const override = args.registryOverrides?.find(item => item.chainId === chain.chainId)
322
- return override ?? erc8004ConfigForSupportedChain(chain.chainId)
323
- })
324
- const results = await mapWithConcurrency(configs, DISCOVERY_CONCURRENCY, async config => {
325
- try {
326
- return {
327
- ok: true as const,
328
- candidates: await discoverOwnedAgentBackups({
329
- ...config,
330
- ownerHandle: ownerAddress,
331
- ipfsApiUrl: args.ipfsApiUrl,
332
- publicClient: args.publicClients?.[config.chainId],
333
- fetchImpl: args.fetchImpl,
334
- }),
335
- }
336
- } catch (err: unknown) {
337
- return { ok: false as const, error: err }
338
- }
339
- })
340
-
341
- const candidates = results.flatMap(result => result.ok ? result.candidates : [])
342
- if (candidates.length > 0) {
343
- return candidates.sort(compareCandidatesByNetworkThenNewest)
344
- }
345
- const failures = results.filter(result => !result.ok)
346
- if (failures.length === results.length && failures.length > 0) {
347
- throw new Error(`lookup failed on all supported networks: ${cleanRpcError(failures[0]!.error)}`)
348
- }
349
- const tokenIdRequired = failures
350
- .map(result => result.error)
351
- .find((err): err is AgentTokenIdRequiredError => err instanceof AgentTokenIdRequiredError)
352
- if (tokenIdRequired) throw tokenIdRequired
353
- return []
354
- }
355
-
356
- export async function loadAgentRegistration(
357
- uri: string,
358
- args: { ipfsApiUrl?: string; fetchImpl?: FetchLike } = {},
359
- ): Promise<{ metadataCid?: string; registration: Record<string, unknown> }> {
360
- const trimmed = uri.trim()
361
- let raw: string
362
- if (trimmed.startsWith('ipfs://')) {
363
- const cid = cidFromUri(trimmed)
364
- if (!cid) throw new Error('agentURI is missing an IPFS CID')
365
- raw = new TextDecoder().decode(await catFromIpfs(args.ipfsApiUrl ?? DEFAULT_IPFS_API_URL, cid))
366
- return { metadataCid: cid, registration: parseJsonObject(raw) }
367
- }
368
- if (trimmed.startsWith('data:')) {
369
- return { registration: parseJsonObject(decodeDataUri(trimmed)) }
370
- }
371
- if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
372
- const response = await (args.fetchImpl ?? fetch)(trimmed)
373
- if (!response.ok) throw new Error(`agent metadata fetch failed: ${response.status} ${response.statusText}`)
374
- return { registration: parseJsonObject(await response.text()) }
375
- }
376
- throw new Error('unsupported agentURI scheme')
377
- }
378
-
379
- export function parseEthagentBackupPointer(registration: Record<string, unknown> | null): EthagentBackupPointer | null {
380
- if (!registration) return null
381
- const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
382
- const backup = ext ? objectField(ext, 'backup') : null
383
- const cid = backup ? stringField(backup, 'cid') : undefined
384
- if (!cid) return null
385
- const agentAddress = stringField(ext, 'agentAddress')
386
- const pastBackupsArray = arrayField(backup ?? {}, 'pastBackups')
387
- const pastBackups = pastBackupsArray?.flatMap(item => {
388
- if (!item || typeof item !== 'object') return []
389
- const obj = item as Record<string, unknown>
390
- const itemCid = stringField(obj, 'cid')
391
- if (!itemCid) return []
392
- return [{
393
- cid: itemCid,
394
- ...(stringField(obj, 'createdAt') ? { createdAt: stringField(obj, 'createdAt') } : {}),
395
- }]
396
- })
397
-
398
- return {
399
- cid,
400
- envelopeVersion: backup ? stringField(backup, 'envelopeVersion') : undefined,
401
- createdAt: backup ? stringField(backup, 'createdAt') : undefined,
402
- ...(agentAddress && isAddress(agentAddress) ? { agentAddress: getAddress(agentAddress) } : {}),
403
- ...(pastBackups && pastBackups.length > 0 ? { pastBackups } : {}),
404
- }
405
- }
406
-
407
- export function parseEthagentPublicDiscoveryPointer(registration: Record<string, unknown> | null): EthagentPublicDiscoveryPointer | null {
408
- if (!registration) return null
409
- const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
410
- const publicSkills = ext ? objectField(ext, 'publicSkills') : null
411
- const agentCard = ext ? objectField(ext, 'agentCard') : null
412
- const skillsCid = publicSkills ? stringField(publicSkills, 'cid') : undefined
413
- const agentCardCid = agentCard ? stringField(agentCard, 'cid') : undefined
414
- const updatedAt = (publicSkills ? stringField(publicSkills, 'updatedAt') : undefined)
415
- ?? (agentCard ? stringField(agentCard, 'updatedAt') : undefined)
416
- if (!skillsCid && !agentCardCid) return null
417
- return {
418
- ...(skillsCid ? { skillsCid } : {}),
419
- ...(agentCardCid ? { agentCardCid } : {}),
420
- ...(updatedAt ? { updatedAt } : {}),
421
- }
422
- }
423
-
424
- export function withEthagentBackupPointer(
425
- registration: Record<string, unknown> | null,
426
- backup: EthagentBackupPointer,
427
- publicDiscovery?: EthagentPublicDiscoveryPointer,
428
- registrationPointer?: EthagentRegistrationPointer,
429
- ): Record<string, unknown> {
430
- return withEthagentPointers(registration, { backup, publicDiscovery, registration: registrationPointer })
431
- }
432
-
433
- export function withEthagentPointers(
434
- registration: Record<string, unknown> | null,
435
- pointers: {
436
- backup?: EthagentBackupPointer
437
- publicDiscovery?: EthagentPublicDiscoveryPointer
438
- registration?: EthagentRegistrationPointer
439
- },
440
- ): Record<string, unknown> {
441
- const next: Record<string, unknown> = registration ? { ...registration } : {}
442
- const prior = objectField(next, 'x-ethagent') ?? {}
443
- const { backup, publicDiscovery, registration: registrationPointer } = pointers
444
- const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
445
- next['x-ethagent'] = {
446
- ...prior,
447
- version: 1,
448
- ...(backup?.agentAddress ? { agentAddress: backup.agentAddress } : {}),
449
- ...(backup ? {
450
- backup: {
451
- cid: backup.cid,
452
- ...(backup.envelopeVersion ? { envelopeVersion: backup.envelopeVersion } : {}),
453
- ...(backup.createdAt ? { createdAt: backup.createdAt } : {}),
454
- },
455
- } : {}),
456
- ...(publicDiscovery?.skillsCid ? {
457
- publicSkills: {
458
- cid: publicDiscovery.skillsCid,
459
- format: 'application/json',
460
- ...(updatedAt ? { updatedAt } : {}),
461
- },
462
- } : {}),
463
- ...(publicDiscovery?.agentCardCid ? {
464
- agentCard: {
465
- cid: publicDiscovery.agentCardCid,
466
- format: 'application/json',
467
- ...(updatedAt ? { updatedAt } : {}),
468
- },
469
- } : {}),
470
- }
471
- if (publicDiscovery) {
472
- next.services = withPublicDiscoveryServices(next.services, publicDiscovery)
473
- }
474
- if (registrationPointer?.agentId) {
475
- next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
476
- }
477
- return next
478
- }
479
-
480
- export function encodeRegisterAgent(args: {
481
- agentURI: string
482
- }): Hex {
483
- return encodeFunctionData({
484
- abi: ERC8004_ABI,
485
- functionName: 'register',
486
- args: [args.agentURI],
487
- })
488
- }
489
-
490
- export async function preflightRegisterAgent(args: Erc8004RegistryConfig & {
491
- ownerAddress: Address
492
- agentURI: string
493
- publicClient?: RegisterAgentPreflightClient
494
- }): Promise<RegisterAgentPreflight> {
495
- const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
496
- const data = encodeRegisterAgent({ agentURI: args.agentURI })
497
- let gas: bigint
498
- try {
499
- gas = await publicClient.estimateGas({
500
- account: args.ownerAddress,
501
- to: args.identityRegistryAddress,
502
- data,
503
- })
504
- } catch (err: unknown) {
505
- throw new RegisterAgentPreflightError({
506
- code: 'simulation-failed',
507
- title: 'registration blocked',
508
- detail: cleanRpcError(err),
509
- hint: 'No transaction was sent.',
510
- })
511
- }
512
- const [gasPrice, balance] = await Promise.all([
513
- publicClient.getGasPrice(),
514
- publicClient.getBalance({ address: args.ownerAddress }),
515
- ])
516
- const estimatedCost = gas * gasPrice
517
- const requiredBalance = estimatedCost + estimatedCost / 5n
518
- if (balance < requiredBalance) {
519
- throw new RegisterAgentPreflightError({
520
- code: 'insufficient-funds',
521
- title: 'not enough ETH',
522
- detail: `Need ~${formatEthAmount(requiredBalance)} ETH. Wallet has ${formatEthAmount(balance)} ETH.`,
523
- hint: 'Add ETH to this wallet, then try again.',
524
- requiredBalanceWei: requiredBalance,
525
- balanceWei: balance,
526
- })
527
- }
528
- return {
529
- gas,
530
- gasPrice,
531
- estimatedCostWei: estimatedCost,
532
- requiredBalanceWei: requiredBalance,
533
- balanceWei: balance,
534
- }
535
- }
536
-
537
- export async function preflightSetAgentUri(args: Erc8004RegistryConfig & {
538
- account: Address
539
- agentId: bigint
540
- newUri: string
541
- publicClient?: RegisterAgentPreflightClient
542
- }): Promise<void> {
543
- const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
544
- const data = encodeSetAgentUri({ agentId: args.agentId, newUri: args.newUri })
545
- try {
546
- await publicClient.estimateGas({
547
- account: args.account,
548
- to: args.identityRegistryAddress,
549
- data,
550
- })
551
- } catch (err: unknown) {
552
- const detail = cleanRpcError(err)
553
- const looksLikeOwnershipRevert = /not.*owner|owner.*only|unauthor|forbidden|caller/i.test(detail)
554
- throw new RegisterAgentPreflightError({
555
- code: 'simulation-failed',
556
- title: 'backup update blocked',
557
- detail,
558
- hint: looksLikeOwnershipRevert
559
- ? `Connect the wallet that owns this agent (${args.account}) and try again.`
560
- : 'No transaction was sent.',
561
- })
562
- }
563
- }
564
-
565
- export function encodeSetAgentUri(args: {
566
- agentId: bigint
567
- newUri: string
568
- }): Hex {
569
- return encodeFunctionData({
570
- abi: ERC8004_ABI,
571
- functionName: 'setAgentURI',
572
- args: [args.agentId, args.newUri],
573
- })
574
- }
575
-
576
- export function registeredAgentFromReceipt(args: {
577
- logs: ReceiptLog[]
578
- identityRegistryAddress: Address
579
- ownerAddress?: Address
580
- }): { agentId: bigint; agentURI: string; owner: Address } {
581
- for (const log of args.logs) {
582
- if (log.address && log.address.toLowerCase() !== args.identityRegistryAddress.toLowerCase()) continue
583
- try {
584
- const decoded = decodeEventLog({
585
- abi: [REGISTERED_EVENT],
586
- topics: log.topics as [Hex, ...Hex[]],
587
- data: log.data,
588
- })
589
- if (decoded.eventName !== 'Registered') continue
590
- const eventArgs = decoded.args as { agentId?: bigint; agentURI?: string; owner?: Address }
591
- if (eventArgs.agentId === undefined || !eventArgs.agentURI || !eventArgs.owner) continue
592
- if (args.ownerAddress && eventArgs.owner.toLowerCase() !== args.ownerAddress.toLowerCase()) continue
593
- return {
594
- agentId: eventArgs.agentId,
595
- agentURI: eventArgs.agentURI,
596
- owner: getAddress(eventArgs.owner),
597
- }
598
- } catch {
599
- }
600
- }
601
- throw new Error('ERC-8004 registration event was not found in transaction receipt')
602
- }
603
-
604
- export function cidFromUri(uri: string): string | undefined {
605
- if (!uri.startsWith('ipfs://')) return undefined
606
- const withoutScheme = uri.slice('ipfs://'.length)
607
- return withoutScheme.startsWith('ipfs/') ? withoutScheme.slice('ipfs/'.length) : withoutScheme
608
- }
609
-
610
- async function findCandidateTokenIds(args: {
611
- publicClient: PublicClient
612
- registry: Erc8004RegistryConfig
613
- ownerAddress: Address
614
- fromBlock: bigint
615
- }): Promise<bigint[]> {
616
- const tokenIds = new Set<bigint>()
617
- let balance: bigint | undefined
618
- let attempt = 0
619
- while (true) {
620
- try {
621
- balance = await args.publicClient.readContract({
622
- address: args.registry.identityRegistryAddress,
623
- abi: ERC8004_ABI,
624
- functionName: 'balanceOf',
625
- args: [args.ownerAddress],
626
- }) as bigint
627
- break
628
- } catch (err: unknown) {
629
- if (++attempt > 3) {
630
- throw new AgentTokenIdRequiredError({
631
- ownerAddress: args.ownerAddress,
632
- registry: args.registry,
633
- balance: 0n,
634
- detail: cleanRpcError(err),
635
- })
636
- }
637
- await new Promise(r => setTimeout(r, attempt * 1000))
638
- }
639
- }
640
- if (balance === 0n) return []
641
-
642
- const enumerableTokenIds = await findEnumerableTokenIds({
643
- publicClient: args.publicClient,
644
- registry: args.registry,
645
- ownerAddress: args.ownerAddress,
646
- balance,
647
- })
648
- if (enumerableTokenIds) return enumerableTokenIds
649
-
650
- try {
651
- for await (const logs of getTransferLogChunksBackwards({
652
- publicClient: args.publicClient,
653
- registry: args.registry,
654
- ownerAddress: args.ownerAddress,
655
- fromBlock: args.fromBlock,
656
- })) {
657
- for (const log of logs) {
658
- const tokenId = log.args.tokenId
659
- if (tokenId === undefined || tokenIds.has(tokenId)) continue
660
- if (await isCurrentTokenOwner(args.publicClient, args.registry.identityRegistryAddress, tokenId, args.ownerAddress)) {
661
- tokenIds.add(tokenId)
662
- if (BigInt(tokenIds.size) >= balance) return [...tokenIds]
663
- }
664
- }
665
- }
666
- } catch (err: unknown) {
667
- throw new AgentTokenIdRequiredError({
668
- ownerAddress: args.ownerAddress,
669
- registry: args.registry,
670
- balance,
671
- detail: cleanRpcError(err),
672
- })
673
- }
674
- if (BigInt(tokenIds.size) < balance) {
675
- throw new AgentTokenIdRequiredError({
676
- ownerAddress: args.ownerAddress,
677
- registry: args.registry,
678
- balance,
679
- detail: 'owned token ids were not found in logs',
680
- })
681
- }
682
- return [...tokenIds]
683
- }
684
-
685
- async function* getTransferLogChunksBackwards(args: {
686
- publicClient: PublicClient
687
- registry: Erc8004RegistryConfig
688
- ownerAddress: Address
689
- fromBlock: bigint
690
- }): AsyncGenerator<TransferLog[]> {
691
- const latest = await args.publicClient.getBlockNumber()
692
- if (args.fromBlock > latest) return
693
-
694
- const ranges = blockRangesBackwards(args.fromBlock, latest, logBlockRangeForChain(args.registry.chainId))
695
- const CONCURRENCY = 5
696
-
697
- for (let i = 0; i < ranges.length; i += CONCURRENCY) {
698
- const batch = ranges.slice(i, i + CONCURRENCY)
699
- const logsArrays = await Promise.all(batch.map(async range => {
700
- try {
701
- return await getTransferLogsAdaptive({
702
- ...args,
703
- fromBlock: range.fromBlock,
704
- toBlock: range.toBlock,
705
- minBlockRange: minLogBlockRangeForChain(args.registry.chainId),
706
- })
707
- } catch {
708
- return [] as TransferLog[]
709
- }
710
- }))
711
- for (const logs of logsArrays) {
712
- if (logs.length > 0) yield logs
713
- }
714
- }
715
- }
716
-
717
- async function findEnumerableTokenIds(args: {
718
- publicClient: PublicClient
719
- registry: Erc8004RegistryConfig
720
- ownerAddress: Address
721
- balance: bigint
722
- }): Promise<bigint[] | null> {
723
- const tokenIds: bigint[] = []
724
- try {
725
- for (let index = 0n; index < args.balance; index += 1n) {
726
- const tokenId = await args.publicClient.readContract({
727
- address: args.registry.identityRegistryAddress,
728
- abi: ERC8004_ABI,
729
- functionName: 'tokenOfOwnerByIndex',
730
- args: [args.ownerAddress, index],
731
- }) as bigint
732
- tokenIds.push(tokenId)
733
- }
734
- return tokenIds
735
- } catch {
736
- return null
737
- }
738
- }
739
-
740
- async function getTransferLogsAdaptive(args: {
741
- publicClient: PublicClient
742
- registry: Erc8004RegistryConfig
743
- ownerAddress: Address
744
- fromBlock: bigint
745
- toBlock: bigint
746
- minBlockRange: bigint
747
- }): Promise<TransferLog[]> {
748
- const size = args.toBlock - args.fromBlock + 1n
749
- let attempt = 0
750
- while (true) {
751
- try {
752
- const logs = await args.publicClient.getLogs({
753
- address: args.registry.identityRegistryAddress,
754
- event: TRANSFER_EVENT,
755
- args: { to: args.ownerAddress },
756
- fromBlock: args.fromBlock,
757
- toBlock: args.toBlock,
758
- })
759
- return logs as TransferLog[]
760
- } catch (err: unknown) {
761
- attempt++
762
- const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase()
763
- const isSizeLimit = msg.includes('limit') || msg.includes('range') || msg.includes('too many') || msg.includes('exceeds') || msg.includes('block count')
764
-
765
- if (!isSizeLimit && attempt <= 3) {
766
- await new Promise(r => setTimeout(r, attempt * 1000))
767
- continue
768
- }
769
- if (size <= args.minBlockRange) {
770
- if (attempt <= 3) {
771
- await new Promise(r => setTimeout(r, attempt * 1000))
772
- continue
773
- }
774
- throw err
775
- }
776
-
777
- const mid = args.fromBlock + size / 2n - 1n
778
- const [newer, older] = await Promise.all([
779
- getTransferLogsAdaptive({ ...args, fromBlock: mid + 1n, toBlock: args.toBlock }),
780
- getTransferLogsAdaptive({ ...args, fromBlock: args.fromBlock, toBlock: mid })
781
- ])
782
- return [...newer, ...older]
783
- }
784
- }
785
- }
786
-
787
- async function findContractDeploymentBlock(publicClient: PublicClient, address: Address): Promise<bigint> {
788
- const latest = await publicClient.getBlockNumber()
789
- const latestCode = await publicClient.getBytecode({ address, blockNumber: latest })
790
- if (!latestCode || latestCode === '0x') throw new Error(`no contract code at ${address}`)
791
-
792
- let low = 0n
793
- let high = latest
794
- while (low < high) {
795
- const mid = (low + high) / 2n
796
- const code = await publicClient.getBytecode({ address, blockNumber: mid })
797
- if (code && code !== '0x') high = mid
798
- else low = mid + 1n
799
- }
800
- return low
801
- }
802
-
803
- class TokenOwnerMismatchError extends Error {
804
- constructor() {
805
- super('token is not owned by this wallet')
806
- this.name = 'TokenOwnerMismatchError'
807
- }
808
- }
809
-
810
- async function loadOwnedAgentCandidate(args: DiscoverOwnedAgentsArgs & {
811
- publicClient: PublicClient
812
- ownerAddress: Address
813
- tokenId: bigint
814
- }): Promise<Erc8004AgentCandidate> {
815
- let attempt = 0
816
- let currentOwner: Address | undefined
817
- let agentUri: string | undefined
818
- while (true) {
819
- try {
820
- if (!currentOwner) {
821
- currentOwner = await args.publicClient.readContract({
822
- address: args.identityRegistryAddress,
823
- abi: ERC8004_ABI,
824
- functionName: 'ownerOf',
825
- args: [args.tokenId],
826
- }) as Address
827
- }
828
- if (!agentUri) {
829
- agentUri = await args.publicClient.readContract({
830
- address: args.identityRegistryAddress,
831
- abi: ERC8004_ABI,
832
- functionName: 'tokenURI',
833
- args: [args.tokenId],
834
- }) as string
835
- }
836
- break
837
- } catch (err: unknown) {
838
- if (++attempt > 3) throw err
839
- await new Promise(r => setTimeout(r, attempt * 1000))
840
- }
841
- }
842
-
843
- if (currentOwner.toLowerCase() !== args.ownerAddress.toLowerCase()) {
844
- throw new TokenOwnerMismatchError()
845
- }
846
- const loaded = await loadAgentRegistration(agentUri, {
847
- ipfsApiUrl: args.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
848
- fetchImpl: args.fetchImpl,
849
- }).catch(() => ({ metadataCid: cidFromUri(agentUri!), registration: null }))
850
- const parsed = parseEthagentBackupPointer(loaded.registration)
851
- const publicDiscovery = parseEthagentPublicDiscoveryPointer(loaded.registration)
852
- return {
853
- ownerAddress: args.ownerAddress,
854
- chainId: args.chainId,
855
- rpcUrl: args.rpcUrl,
856
- identityRegistryAddress: args.identityRegistryAddress,
857
- agentId: args.tokenId,
858
- agentUri,
859
- metadataCid: loaded.metadataCid,
860
- name: stringField(loaded.registration, 'name'),
861
- description: stringField(loaded.registration, 'description'),
862
- imageUrl: stringField(loaded.registration, 'image'),
863
- backup: parsed ?? undefined,
864
- publicDiscovery: publicDiscovery ?? undefined,
865
- registration: loaded.registration,
866
- }
867
- }
868
-
869
- async function isCurrentTokenOwner(
870
- publicClient: PublicClient,
871
- registry: Address,
872
- tokenId: bigint,
873
- ownerAddress: Address,
874
- ): Promise<boolean> {
875
- let attempt = 0
876
- while (true) {
877
- try {
878
- const currentOwner = await publicClient.readContract({
879
- address: registry,
880
- abi: ERC8004_ABI,
881
- functionName: 'ownerOf',
882
- args: [tokenId],
883
- }) as Address
884
- return currentOwner.toLowerCase() === ownerAddress.toLowerCase()
885
- } catch (err: unknown) {
886
- if (++attempt > 3) throw err
887
- await new Promise(r => setTimeout(r, attempt * 1000))
888
- }
889
- }
890
- }
891
-
892
- async function resolveOwnerAddressForSupportedLookup(
893
- args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
894
- ): Promise<Address> {
895
- const trimmed = args.ownerHandle.trim()
896
- if (isAddress(trimmed)) return getAddress(trimmed)
897
- const mainnetConfig = erc8004ConfigForSupportedChain(mainnet.id)
898
- return resolveOwnerHandle(trimmed, {
899
- ...mainnetConfig,
900
- publicClient: args.publicClients?.[mainnet.id],
901
- })
902
- }
903
-
904
- async function mapWithConcurrency<input, output>(
905
- inputs: input[],
906
- concurrency: number,
907
- mapper: (input: input) => Promise<output>,
908
- ): Promise<output[]> {
909
- const out: output[] = new Array(inputs.length)
910
- let next = 0
911
- const workers = Array.from({ length: Math.min(concurrency, inputs.length) }, async () => {
912
- while (next < inputs.length) {
913
- const index = next++
914
- out[index] = await mapper(inputs[index]!)
915
- }
916
- })
917
- await Promise.all(workers)
918
- return out
919
- }
920
-
921
- function compareCandidatesByNetworkThenNewest(a: Erc8004AgentCandidate, b: Erc8004AgentCandidate): number {
922
- const networkOrder = chainSortIndex(a.chainId) - chainSortIndex(b.chainId)
923
- if (networkOrder !== 0) return networkOrder
924
- return Number(b.agentId - a.agentId)
925
- }
926
-
927
- function blockRangesBackwards(
928
- fromBlock: bigint,
929
- latest: bigint,
930
- blockRange: bigint,
931
- ): Array<{ fromBlock: bigint; toBlock: bigint }> {
932
- const ranges: Array<{ fromBlock: bigint; toBlock: bigint }> = []
933
- for (let end = latest; end >= fromBlock;) {
934
- const start = end - blockRange + 1n > fromBlock ? end - blockRange + 1n : fromBlock
935
- ranges.push({ fromBlock: start, toBlock: end })
936
- if (start === fromBlock) break
937
- end = start - 1n
938
- }
939
- return ranges
940
- }
941
-
942
- function logBlockRangeForChain(chainId: number): bigint {
943
- const chain = supportedErc8004ChainForId(chainId)
944
- if (!chain) return 10_000n
945
- return chain.logBlockRange
946
- }
947
-
948
- function minLogBlockRangeForChain(chainId: number): bigint {
949
- const chain = supportedErc8004ChainForId(chainId)
950
- if (!chain) return 2_000n
951
- return chain.kind === 'l2' ? chain.logBlockRange : chain.logBlockRange / 2n || 1n
952
- }
953
-
954
- function chainSortIndex(chainId: number): number {
955
- const index = SUPPORTED_ERC8004_CHAINS.findIndex(chain => chain.chainId === chainId)
956
- return index === -1 ? Number.MAX_SAFE_INTEGER : index
957
- }
958
-
959
- function rpcUrlsForClient(args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'>): string[] {
960
- const chain = supportedErc8004ChainForId(args.chainId)
961
- return uniqueStrings([
962
- args.rpcUrl,
963
- ...(chain && args.rpcUrl !== chain.rpcUrl ? [chain.rpcUrl] : []),
964
- ...(chain?.fallbackRpcUrls ?? []),
965
- ])
966
- }
967
-
968
- function uniqueStrings(values: string[]): string[] {
969
- const out: string[] = []
970
- for (const value of values) {
971
- const normalized = value.trim().replace(/\/$/, '')
972
- if (normalized && !out.includes(normalized)) out.push(normalized)
973
- }
974
- return out
975
- }
976
-
977
- function parseJsonObject(raw: string): Record<string, unknown> {
978
- const parsed = JSON.parse(raw) as unknown
979
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
980
- throw new Error('agent metadata must be a JSON object')
981
- }
982
- return parsed as Record<string, unknown>
983
- }
984
-
985
- function decodeDataUri(uri: string): string {
986
- const comma = uri.indexOf(',')
987
- if (comma === -1) throw new Error('invalid data URI')
988
- const meta = uri.slice(0, comma)
989
- const body = uri.slice(comma + 1)
990
- return meta.endsWith(';base64')
991
- ? Buffer.from(body, 'base64').toString('utf8')
992
- : decodeURIComponent(body)
993
- }
994
-
995
- function objectField(input: Record<string, unknown>, key: string): Record<string, unknown> | null {
996
- const value = input[key]
997
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null
998
- return value as Record<string, unknown>
999
- }
1000
-
1001
- function arrayField(input: Record<string, unknown>, key: string): Array<unknown> | null {
1002
- const value = input[key]
1003
- return Array.isArray(value) ? value : null
1004
- }
1005
-
1006
- function stringField(input: Record<string, unknown> | null, key: string): string | undefined {
1007
- const value = input?.[key]
1008
- return typeof value === 'string' && value.length > 0 ? value : undefined
1009
- }
1010
-
1011
- function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPublicDiscoveryPointer): unknown[] {
1012
- const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
1013
- const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
1014
- if (publicDiscovery.agentCardCid) {
1015
- const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
1016
- pushUniqueService(services, {
1017
- type: 'a2a',
1018
- name: 'agent-card',
1019
- endpoint,
1020
- url: endpoint,
1021
- })
1022
- }
1023
- if (publicDiscovery.skillsCid) {
1024
- const endpoint = `ipfs://${publicDiscovery.skillsCid}`
1025
- pushUniqueService(services, {
1026
- type: 'A2A-skills',
1027
- name: 'public-skills',
1028
- endpoint,
1029
- url: endpoint,
1030
- })
1031
- }
1032
- return services
1033
- }
1034
-
1035
- function pushUniqueService(services: unknown[], service: Record<string, string>): void {
1036
- const duplicate = services.some(item => {
1037
- if (!item || typeof item !== 'object' || Array.isArray(item)) return false
1038
- const obj = item as Record<string, unknown>
1039
- return obj.type === service.type && obj.endpoint === service.endpoint
1040
- })
1041
- if (!duplicate) services.push(service)
1042
- }
1043
-
1044
- function isEthagentManagedService(item: unknown): boolean {
1045
- if (!item || typeof item !== 'object' || Array.isArray(item)) return false
1046
- const obj = item as Record<string, unknown>
1047
- const type = obj.type
1048
- const name = obj.name
1049
- if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
1050
- return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
1051
- }
1052
-
1053
- function withRegistrationsArray(_input: unknown, registration: EthagentRegistrationPointer): unknown[] {
1054
- return [{
1055
- agentId: registration.agentId,
1056
- agentRegistry: `eip155:${registration.chainId}:${registration.identityRegistryAddress}`,
1057
- }]
1058
- }
1059
-
1060
- function cleanRpcError(err: unknown): string {
1061
- const message = err instanceof Error ? err.message : String(err)
1062
- return message
1063
- .replace(/\s+/g, ' ')
1064
- .slice(0, 220)
1065
- }
1066
-
1067
- function formatEthAmount(wei: bigint): string {
1068
- const [whole = '0', fraction = ''] = formatEther(wei).split('.')
1069
- const trimmedFraction = fraction.slice(0, 6).replace(/0+$/, '')
1070
- return trimmedFraction ? `${whole}.${trimmedFraction}` : whole
1071
- }
1072
-
1073
- function chainEntry(
1074
- chainId: number,
1075
- name: string,
1076
- rpcUrl: string,
1077
- fallbackRpcUrls: string[],
1078
- identityRegistryAddress: string | undefined,
1079
- fromBlock: bigint | undefined,
1080
- logBlockRange: bigint,
1081
- kind: SupportedErc8004Chain['kind'],
1082
- network: SelectableNetwork,
1083
- ): SupportedErc8004Chain {
1084
- return {
1085
- chainId,
1086
- name,
1087
- rpcUrl: rpcUrl.replace(/\/$/, ''),
1088
- fallbackRpcUrls: fallbackRpcUrls.map(url => url.replace(/\/$/, '')),
1089
- ...(identityRegistryAddress ? { identityRegistryAddress: getAddress(identityRegistryAddress) } : {}),
1090
- ...(fromBlock !== undefined ? { fromBlock } : {}),
1091
- logBlockRange,
1092
- kind,
1093
- network,
1094
- }
1095
- }
1096
-
1097
- function chainForId(chainId: number): Chain | undefined {
1098
- switch (chainId) {
1099
- case mainnet.id: return mainnet
1100
- case arbitrum.id: return arbitrum
1101
- case base.id: return base
1102
- case optimism.id: return optimism
1103
- case polygon.id: return polygon
1104
- default: return undefined
1105
- }
1106
- }
1
+ export {
2
+ DEFAULT_ERC8004_CHAIN_ID,
3
+ DEFAULT_ERC8004_IDENTITY_REGISTRY_ADDRESS,
4
+ DEFAULT_ETHEREUM_RPC_URL,
5
+ MissingRegistryAddressError,
6
+ SUPPORTED_ERC8004_CHAINS,
7
+ chainIdForNetwork,
8
+ erc8004ConfigForSupportedChain,
9
+ networkForChainId,
10
+ normalizeErc8004RegistryConfig,
11
+ supportedErc8004ChainForId,
12
+ } from './erc8004/chains.js'
13
+ export type { SupportedErc8004Chain } from './erc8004/chains.js'
14
+ export { createErc8004PublicClient } from './erc8004/client.js'
15
+ export {
16
+ AgentTokenIdRequiredError,
17
+ discoverOwnedAgentBackupByTokenId,
18
+ discoverOwnedAgentBackups,
19
+ discoverOwnedAgentBackupsAcrossSupportedNetworks,
20
+ } from './erc8004/discovery.js'
21
+ export {
22
+ parseEthagentBackupPointer,
23
+ parseEthagentOperatorsPointer,
24
+ parseEthagentPublicDiscoveryPointer,
25
+ withEthagentBackupPointer,
26
+ withEthagentPointers,
27
+ } from './erc8004/metadata.js'
28
+ export {
29
+ RegisterAgentPreflightError,
30
+ preflightRegisterAgent,
31
+ preflightSetAgentUri,
32
+ } from './erc8004/preflight.js'
33
+ export type { RegisterAgentPreflight, RegisterAgentPreflightErrorCode } from './erc8004/preflight.js'
34
+ export type {
35
+ ApprovedOperatorWalletRecord,
36
+ DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
37
+ DiscoverOwnedAgentsArgs,
38
+ Erc8004AgentCandidate,
39
+ Erc8004RegistryConfig,
40
+ EthagentBackupPointer,
41
+ EthagentOperatorsPointer,
42
+ EthagentPublicDiscoveryPointer,
43
+ EthagentRegistrationPointer,
44
+ EthagentX402Pointer,
45
+ } from './erc8004/types.js'
46
+ export { cidFromUri, loadAgentRegistration } from './erc8004/uri.js'
47
+ export { validateErc8004TokenOwner } from './erc8004/ownership.js'
48
+ export type { Erc8004TokenOwnerValidation } from './erc8004/ownership.js'
49
+ export {
50
+ encodeRegisterAgent,
51
+ encodeSetAgentUri,
52
+ registeredAgentFromReceipt,
53
+ } from './erc8004/transactions.js'