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,335 @@
1
+ import { getAddress, isAddress, type Address } from 'viem'
2
+ import type { TransferSnapshotMetadata } from '../../../storage/config.js'
3
+ import type { WalletContinuityRestoreAccessKey } from '../../continuity/envelope.js'
4
+ import { readOwnerAddressField } from '../../identityCompat.js'
5
+ import { arrayField, numberField, objectField, stringField } from '../fieldParsers.js'
6
+ import type {
7
+ ApprovedOperatorWalletRecord,
8
+ EthagentBackupPointer,
9
+ EthagentOperatorsPointer,
10
+ EthagentPublicDiscoveryPointer,
11
+ EthagentRegistrationPointer,
12
+ } from './types.js'
13
+
14
+ export function parseEthagentBackupPointer(registration: Record<string, unknown> | null): EthagentBackupPointer | null {
15
+ if (!registration) return null
16
+ const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
17
+ const backup = ext ? objectField(ext, 'backup') : null
18
+ const cid = backup ? stringField(backup, 'cid') : undefined
19
+ if (!cid) return null
20
+ const agentAddress = stringField(ext, 'agentAddress')
21
+ const transferSnapshot = backup ? parseTransferSnapshotMetadata(objectField(backup, 'transferSnapshot')) : undefined
22
+ const pastBackupsArray = arrayField(backup ?? {}, 'pastBackups')
23
+ const pastBackups = pastBackupsArray?.flatMap(item => {
24
+ if (!item || typeof item !== 'object') return []
25
+ const obj = item as Record<string, unknown>
26
+ const itemCid = stringField(obj, 'cid')
27
+ if (!itemCid) return []
28
+ return [{
29
+ cid: itemCid,
30
+ ...(stringField(obj, 'createdAt') ? { createdAt: stringField(obj, 'createdAt') } : {}),
31
+ }]
32
+ })
33
+
34
+ return {
35
+ cid,
36
+ envelopeVersion: backup ? stringField(backup, 'envelopeVersion') : undefined,
37
+ createdAt: backup ? stringField(backup, 'createdAt') : undefined,
38
+ ...(agentAddress && isAddress(agentAddress) ? { agentAddress: getAddress(agentAddress) } : {}),
39
+ ...(transferSnapshot ? { transferSnapshot } : {}),
40
+ ...(pastBackups && pastBackups.length > 0 ? { pastBackups } : {}),
41
+ }
42
+ }
43
+
44
+ function parseTransferSnapshotMetadata(input: Record<string, unknown> | null): TransferSnapshotMetadata | undefined {
45
+ if (!input) return undefined
46
+ const kind = stringField(input, 'kind')
47
+ const senderRaw = stringField(input, 'senderAddress')
48
+ const receiverRaw = stringField(input, 'receiverAddress')
49
+ const slotCount = numberField(input, 'slotCount')
50
+ if (kind !== 'dual-wallet' || !senderRaw || !receiverRaw || !slotCount) return undefined
51
+ if (!isAddress(senderRaw, { strict: false }) || !isAddress(receiverRaw, { strict: false })) return undefined
52
+ return {
53
+ kind: 'dual-wallet',
54
+ senderAddress: getAddress(senderRaw),
55
+ receiverAddress: getAddress(receiverRaw),
56
+ ...(stringField(input, 'receiverHandle') ? { receiverHandle: stringField(input, 'receiverHandle') } : {}),
57
+ slotCount,
58
+ ...(stringField(input, 'createdAt') ? { createdAt: stringField(input, 'createdAt') } : {}),
59
+ }
60
+ }
61
+
62
+ export function parseEthagentPublicDiscoveryPointer(registration: Record<string, unknown> | null): EthagentPublicDiscoveryPointer | null {
63
+ if (!registration) return null
64
+ const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
65
+ const publicSkills = ext ? objectField(ext, 'publicSkills') : null
66
+ const agentCard = ext ? objectField(ext, 'agentCard') : null
67
+ const skillsCid = publicSkills ? stringField(publicSkills, 'cid') : undefined
68
+ const agentCardCid = agentCard ? stringField(agentCard, 'cid') : undefined
69
+ const updatedAt = (publicSkills ? stringField(publicSkills, 'updatedAt') : undefined)
70
+ ?? (agentCard ? stringField(agentCard, 'updatedAt') : undefined)
71
+ if (!skillsCid && !agentCardCid) return null
72
+ return {
73
+ ...(skillsCid ? { skillsCid } : {}),
74
+ ...(agentCardCid ? { agentCardCid } : {}),
75
+ ...(updatedAt ? { updatedAt } : {}),
76
+ }
77
+ }
78
+
79
+ export function parseEthagentOperatorsPointer(registration: Record<string, unknown> | null): EthagentOperatorsPointer | null {
80
+ if (!registration) return null
81
+ const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
82
+ const operators = ext ? objectField(ext, 'operators') : null
83
+ if (!operators) return null
84
+ const approvedOperatorWallets = parseOperatorRecords(arrayField(operators, 'approvedOperatorWallets') ?? [])
85
+ const activeRaw = stringField(operators, 'activeOperatorAddress')
86
+ const ownerRaw = readOwnerAddressField(operators)
87
+ const ensName = stringField(operators, 'ensName')
88
+ const restoreAccessEpoch = numberField(operators, 'restoreAccessEpoch')
89
+ const ownerRestoreAccessKey = parseRestoreAccessKey(objectField(operators, 'ownerRestoreAccessKey'))
90
+ const activeOperatorAddress = activeRaw && isAddress(activeRaw, { strict: false }) ? getAddress(activeRaw) : undefined
91
+ const ownerAddress = ownerRaw && isAddress(ownerRaw, { strict: false }) ? getAddress(ownerRaw) : undefined
92
+ if (approvedOperatorWallets.length === 0 && !activeOperatorAddress && !ownerAddress && !ensName && !ownerRestoreAccessKey && !restoreAccessEpoch) return null
93
+ return {
94
+ approvedOperatorWallets,
95
+ ...(activeOperatorAddress ? { activeOperatorAddress } : {}),
96
+ ...(ownerAddress ? { ownerAddress } : {}),
97
+ ...(ensName ? { ensName } : {}),
98
+ ...(restoreAccessEpoch && Number.isSafeInteger(restoreAccessEpoch) && restoreAccessEpoch > 0 ? { restoreAccessEpoch } : {}),
99
+ ...(ownerRestoreAccessKey ? { ownerRestoreAccessKey } : {}),
100
+ }
101
+ }
102
+
103
+ function parseOperatorRecords(items: unknown[]): ApprovedOperatorWalletRecord[] {
104
+ const out: ApprovedOperatorWalletRecord[] = []
105
+ const seen = new Set<string>()
106
+ for (const item of items) {
107
+ const record = parseOperatorRecord(item)
108
+ if (!record) continue
109
+ const key = record.address.toLowerCase()
110
+ if (seen.has(key)) continue
111
+ seen.add(key)
112
+ out.push(record)
113
+ }
114
+ return out
115
+ }
116
+
117
+ function parseOperatorRecord(item: unknown): ApprovedOperatorWalletRecord | null {
118
+ if (typeof item === 'string') {
119
+ const trimmed = item.trim()
120
+ if (!isAddress(trimmed, { strict: false })) return null
121
+ return { address: getAddress(trimmed) }
122
+ }
123
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return null
124
+ const obj = item as Record<string, unknown>
125
+ const rawAddress = stringField(obj, 'address')
126
+ if (!rawAddress || !isAddress(rawAddress, { strict: false })) return null
127
+ const challenge = stringField(obj, 'challenge')
128
+ const verifiedAt = stringField(obj, 'verifiedAt')
129
+ const restoreAccessKey = parseRestoreAccessKey(objectField(obj, 'restoreAccessKey'))
130
+ return {
131
+ address: getAddress(rawAddress),
132
+ ...(challenge ? { challenge } : {}),
133
+ ...(verifiedAt ? { verifiedAt } : {}),
134
+ ...(restoreAccessKey ? { restoreAccessKey } : {}),
135
+ }
136
+ }
137
+
138
+ function parseRestoreAccessKey(obj: Record<string, unknown> | null): WalletContinuityRestoreAccessKey | undefined {
139
+ if (!obj) return undefined
140
+ const address = stringField(obj, 'address')
141
+ const challenge = stringField(obj, 'challenge')
142
+ const salt = stringField(obj, 'salt')
143
+ const kemPublicKey = stringField(obj, 'kemPublicKey')
144
+ const createdAt = stringField(obj, 'createdAt')
145
+ if (!address || !isAddress(address, { strict: false }) || !challenge || !salt || !kemPublicKey) return undefined
146
+ return {
147
+ address: getAddress(address),
148
+ challenge,
149
+ salt,
150
+ kemPublicKey,
151
+ ...(createdAt ? { createdAt } : {}),
152
+ }
153
+ }
154
+
155
+ function serializeRestoreAccessKey(key: WalletContinuityRestoreAccessKey): Record<string, unknown> {
156
+ return {
157
+ address: getAddress(key.address),
158
+ challenge: key.challenge,
159
+ salt: key.salt,
160
+ kemPublicKey: key.kemPublicKey,
161
+ ...(key.createdAt ? { createdAt: key.createdAt } : {}),
162
+ }
163
+ }
164
+
165
+ function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<string, unknown> {
166
+ return {
167
+ approvedOperatorWallets: pointer.approvedOperatorWallets.map(record => ({
168
+ address: getAddress(record.address),
169
+ ...(record.challenge ? { challenge: record.challenge } : {}),
170
+ ...(record.verifiedAt ? { verifiedAt: record.verifiedAt } : {}),
171
+ ...(record.restoreAccessKey ? { restoreAccessKey: serializeRestoreAccessKey(record.restoreAccessKey) } : {}),
172
+ })),
173
+ ...(pointer.activeOperatorAddress ? { activeOperatorAddress: getAddress(pointer.activeOperatorAddress) } : {}),
174
+ ...(pointer.ownerAddress ? { ownerAddress: getAddress(pointer.ownerAddress) } : {}),
175
+ ...(pointer.ensName ? { ensName: pointer.ensName } : {}),
176
+ ...(pointer.restoreAccessEpoch ? { restoreAccessEpoch: pointer.restoreAccessEpoch } : {}),
177
+ ...(pointer.ownerRestoreAccessKey ? { ownerRestoreAccessKey: serializeRestoreAccessKey(pointer.ownerRestoreAccessKey) } : {}),
178
+ }
179
+ }
180
+
181
+ export function withEthagentBackupPointer(
182
+ registration: Record<string, unknown> | null,
183
+ backup: EthagentBackupPointer,
184
+ publicDiscovery?: EthagentPublicDiscoveryPointer,
185
+ registrationPointer?: EthagentRegistrationPointer,
186
+ ownerAddress?: Address,
187
+ ): Record<string, unknown> {
188
+ const inferredOwnerAddress = ownerAddress ?? backup.agentAddress
189
+ return withEthagentPointers(registration, {
190
+ backup,
191
+ publicDiscovery,
192
+ registration: registrationPointer,
193
+ ...(inferredOwnerAddress ? { ownerAddress: inferredOwnerAddress } : {}),
194
+ })
195
+ }
196
+
197
+ export function withEthagentPointers(
198
+ registration: Record<string, unknown> | null,
199
+ pointers: {
200
+ backup?: EthagentBackupPointer
201
+ publicDiscovery?: EthagentPublicDiscoveryPointer
202
+ registration?: EthagentRegistrationPointer
203
+ ensName?: string
204
+ operators?: EthagentOperatorsPointer
205
+ ownerAddress?: Address
206
+ },
207
+ ): Record<string, unknown> {
208
+ const next: Record<string, unknown> = registration ? { ...registration } : {}
209
+ const prior = objectField(next, 'x-ethagent') ?? {}
210
+ const { backup, publicDiscovery, registration: registrationPointer, operators } = pointers
211
+ const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
212
+ const ownerAddress = pointers.ownerAddress
213
+ ? getAddress(pointers.ownerAddress)
214
+ : backup?.agentAddress
215
+ ? getAddress(backup.agentAddress)
216
+ : undefined
217
+ const priorX402 = objectField(prior, 'x402') ?? {}
218
+ const ext: Record<string, unknown> = {
219
+ ...prior,
220
+ version: 1,
221
+ ...(ownerAddress ? {
222
+ agentAddress: ownerAddress,
223
+ x402: {
224
+ ...priorX402,
225
+ walletAddress: ownerAddress,
226
+ },
227
+ } : {}),
228
+ ...(backup ? {
229
+ backup: {
230
+ cid: backup.cid,
231
+ ...(backup.envelopeVersion ? { envelopeVersion: backup.envelopeVersion } : {}),
232
+ ...(backup.createdAt ? { createdAt: backup.createdAt } : {}),
233
+ ...(backup.transferSnapshot ? { transferSnapshot: serializeTransferSnapshotMetadata(backup.transferSnapshot) } : {}),
234
+ },
235
+ } : {}),
236
+ ...(publicDiscovery?.skillsCid ? {
237
+ publicSkills: {
238
+ cid: publicDiscovery.skillsCid,
239
+ format: 'application/json',
240
+ ...(updatedAt ? { updatedAt } : {}),
241
+ },
242
+ } : {}),
243
+ ...(publicDiscovery?.agentCardCid ? {
244
+ agentCard: {
245
+ cid: publicDiscovery.agentCardCid,
246
+ format: 'application/json',
247
+ ...(updatedAt ? { updatedAt } : {}),
248
+ },
249
+ } : {}),
250
+ ...(operators ? { operators: serializeOperatorsPointer(operators) } : {}),
251
+ }
252
+ delete ext.transfer
253
+ delete ext.handoff
254
+ next['x-ethagent'] = ext
255
+ if (publicDiscovery) {
256
+ next.services = withPublicDiscoveryServices(next.services, publicDiscovery, pointers.ensName)
257
+ }
258
+ if (registrationPointer && registrationPointer.agentId !== undefined) {
259
+ next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
260
+ }
261
+ return next
262
+ }
263
+
264
+ function serializeTransferSnapshotMetadata(metadata: TransferSnapshotMetadata): Record<string, unknown> {
265
+ return {
266
+ kind: 'dual-wallet',
267
+ senderAddress: getAddress(metadata.senderAddress),
268
+ receiverAddress: getAddress(metadata.receiverAddress),
269
+ ...(metadata.receiverHandle ? { receiverHandle: metadata.receiverHandle } : {}),
270
+ slotCount: metadata.slotCount,
271
+ ...(metadata.createdAt ? { createdAt: metadata.createdAt } : {}),
272
+ }
273
+ }
274
+
275
+ function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPublicDiscoveryPointer, ensName?: string): unknown[] {
276
+ const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
277
+ const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
278
+ if (publicDiscovery.agentCardCid) {
279
+ const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
280
+ pushUniqueService(services, {
281
+ type: 'a2a',
282
+ name: 'agent-card',
283
+ endpoint,
284
+ url: endpoint,
285
+ })
286
+ }
287
+ if (publicDiscovery.skillsCid) {
288
+ const endpoint = `ipfs://${publicDiscovery.skillsCid}`
289
+ pushUniqueService(services, {
290
+ type: 'A2A-skills',
291
+ name: 'public-skills',
292
+ endpoint,
293
+ url: endpoint,
294
+ })
295
+ }
296
+ if (ensName) {
297
+ services.push({ name: 'ENS', endpoint: ensName, version: 'v1' })
298
+ }
299
+ return services
300
+ }
301
+
302
+ function pushUniqueService(services: unknown[], service: Record<string, string>): void {
303
+ const duplicate = services.some(item => {
304
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return false
305
+ const obj = item as Record<string, unknown>
306
+ return obj.type === service.type && obj.endpoint === service.endpoint
307
+ })
308
+ if (!duplicate) services.push(service)
309
+ }
310
+
311
+ function isEthagentManagedService(item: unknown): boolean {
312
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return false
313
+ const obj = item as Record<string, unknown>
314
+ const type = obj.type
315
+ const name = obj.name
316
+ if (name === 'ENS') return true
317
+ if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
318
+ return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
319
+ }
320
+
321
+ function withRegistrationsArray(_input: unknown, registration: EthagentRegistrationPointer): unknown[] {
322
+ return [{
323
+ agentId: registrationAgentIdValue(registration.agentId),
324
+ agentRegistry: `eip155:${registration.chainId}:${registration.identityRegistryAddress}`,
325
+ }]
326
+ }
327
+
328
+ function registrationAgentIdValue(agentId: string | number | undefined): string | number | undefined {
329
+ if (typeof agentId === 'number') return agentId
330
+ if (typeof agentId !== 'string') return agentId
331
+ const trimmed = agentId.trim()
332
+ if (!/^(0|[1-9]\d*)$/.test(trimmed)) return agentId
333
+ const numeric = Number(trimmed)
334
+ return Number.isSafeInteger(numeric) ? numeric : agentId
335
+ }
@@ -0,0 +1,121 @@
1
+ import { getAddress, type Address, type PublicClient } from 'viem'
2
+ import { OPERATOR_VAULT_ABI } from '../operatorVault.js'
3
+ import { ERC8004_ABI } from './abi.js'
4
+ import { createErc8004PublicClient } from './client.js'
5
+ import type { Erc8004RegistryConfig } from './types.js'
6
+
7
+ type TokenOwnerReadClient = Pick<PublicClient, 'readContract'>
8
+
9
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
10
+
11
+ async function readErc8004TokenOwner(args: Erc8004RegistryConfig & {
12
+ agentId: bigint
13
+ publicClient?: TokenOwnerReadClient
14
+ }): Promise<Address> {
15
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args)
16
+ const owner = await publicClient.readContract({
17
+ address: args.identityRegistryAddress,
18
+ abi: ERC8004_ABI,
19
+ functionName: 'ownerOf',
20
+ args: [args.agentId],
21
+ }) as Address
22
+ return getAddress(owner)
23
+ }
24
+
25
+ export type Erc8004TokenOwnerValidation =
26
+ | { ok: true; ownerAddress: Address }
27
+ | { ok: false; reason: 'token-owner-mismatch' | 'token-owner-lookup-failed'; ownerAddress?: Address; detail: string }
28
+
29
+ export async function validateErc8004TokenOwner(args: Erc8004RegistryConfig & {
30
+ agentId: bigint
31
+ expectedOwner: Address
32
+ publicClient?: TokenOwnerReadClient
33
+ operatorVaults?: Readonly<Record<string, string>>
34
+ }): Promise<Erc8004TokenOwnerValidation> {
35
+ let owner: Address
36
+ try {
37
+ owner = await readErc8004TokenOwner(args)
38
+ } catch (err: unknown) {
39
+ return {
40
+ ok: false,
41
+ reason: 'token-owner-lookup-failed',
42
+ detail: err instanceof Error ? err.message : String(err),
43
+ }
44
+ }
45
+ const expectedOwner = getAddress(args.expectedOwner)
46
+ if (owner.toLowerCase() === expectedOwner.toLowerCase()) {
47
+ return { ok: true, ownerAddress: owner }
48
+ }
49
+ const vaultOwnerResult = await readVaultLevelOwner({
50
+ ...args,
51
+ vaultAddress: owner,
52
+ })
53
+ if (vaultOwnerResult.kind === 'empty') {
54
+ return {
55
+ ok: false,
56
+ reason: 'token-owner-lookup-failed',
57
+ detail: `ERC-8004 token #${args.agentId.toString()} is still reported at the operator delegation vault, but that vault record is empty. Ownership is still settling; retry shortly.`,
58
+ }
59
+ }
60
+ if (vaultOwnerResult.kind === 'error') {
61
+ return {
62
+ ok: false,
63
+ reason: 'token-owner-lookup-failed',
64
+ detail: vaultOwnerResult.error instanceof Error ? vaultOwnerResult.error.message : String(vaultOwnerResult.error),
65
+ }
66
+ }
67
+ if (vaultOwnerResult.kind === 'ok') {
68
+ const vaultOwner = vaultOwnerResult.ownerAddress
69
+ if (vaultOwner.toLowerCase() === expectedOwner.toLowerCase()) {
70
+ return { ok: true, ownerAddress: vaultOwner }
71
+ }
72
+ return {
73
+ ok: false,
74
+ reason: 'token-owner-mismatch',
75
+ ownerAddress: vaultOwner,
76
+ detail: `ERC-8004 token #${args.agentId.toString()} is held by the operator delegation vault for ${vaultOwner}`,
77
+ }
78
+ }
79
+ return {
80
+ ok: false,
81
+ reason: 'token-owner-mismatch',
82
+ ownerAddress: owner,
83
+ detail: `ERC-8004 token #${args.agentId.toString()} is owned by ${shortHex(owner)}`,
84
+ }
85
+ }
86
+
87
+ async function readVaultLevelOwner(args: Erc8004RegistryConfig & {
88
+ agentId: bigint
89
+ vaultAddress: Address
90
+ publicClient?: TokenOwnerReadClient
91
+ }): Promise<
92
+ | { kind: 'ok'; ownerAddress: Address }
93
+ | { kind: 'empty' }
94
+ | { kind: 'not-vault' }
95
+ | { kind: 'error'; error: unknown }
96
+ > {
97
+ try {
98
+ const client = args.publicClient ?? createErc8004PublicClient(args)
99
+ const vaultOwner = await client.readContract({
100
+ address: args.vaultAddress,
101
+ abi: OPERATOR_VAULT_ABI,
102
+ functionName: 'agentOwner',
103
+ args: [args.identityRegistryAddress, args.agentId],
104
+ }) as Address
105
+ const normalizedVaultOwner = getAddress(vaultOwner)
106
+ if (normalizedVaultOwner.toLowerCase() === ZERO_ADDRESS) return { kind: 'empty' }
107
+ return { kind: 'ok', ownerAddress: normalizedVaultOwner }
108
+ } catch (err: unknown) {
109
+ return looksLikeTransientVaultReadFailure(err) ? { kind: 'error', error: err } : { kind: 'not-vault' }
110
+ }
111
+ }
112
+
113
+ function looksLikeTransientVaultReadFailure(err: unknown): boolean {
114
+ const message = err instanceof Error ? err.message : String(err)
115
+ return /timeout|network|header not found|rate|server|rpc/i.test(message)
116
+ }
117
+
118
+ function shortHex(value: string): string {
119
+ if (value.length <= 14) return value
120
+ return `${value.slice(0, 6)}...${value.slice(-4)}`
121
+ }
@@ -0,0 +1,123 @@
1
+ import type { Address, Hex } from 'viem'
2
+ import type { Erc8004RegistryConfig } from './types.js'
3
+ import { createErc8004PublicClient } from './client.js'
4
+ import { encodeRegisterAgent, encodeSetAgentUri } from './transactions.js'
5
+ import { cleanRpcError, formatEthAmount } from './utils.js'
6
+
7
+ export type RegisterAgentPreflight = {
8
+ gas: bigint
9
+ gasPrice: bigint
10
+ estimatedCostWei: bigint
11
+ requiredBalanceWei: bigint
12
+ balanceWei: bigint
13
+ }
14
+
15
+ export type RegisterAgentPreflightErrorCode = 'insufficient-funds' | 'simulation-failed'
16
+
17
+ export class RegisterAgentPreflightError extends Error {
18
+ code: RegisterAgentPreflightErrorCode
19
+ title: string
20
+ detail: string
21
+ hint: string
22
+ requiredBalanceWei?: bigint
23
+ balanceWei?: bigint
24
+
25
+ constructor(args: {
26
+ code: RegisterAgentPreflightErrorCode
27
+ title: string
28
+ detail: string
29
+ hint: string
30
+ requiredBalanceWei?: bigint
31
+ balanceWei?: bigint
32
+ }) {
33
+ super(args.detail ? `${args.title}: ${args.detail}` : args.title)
34
+ this.name = 'RegisterAgentPreflightError'
35
+ this.code = args.code
36
+ this.title = args.title
37
+ this.detail = args.detail
38
+ this.hint = args.hint
39
+ this.requiredBalanceWei = args.requiredBalanceWei
40
+ this.balanceWei = args.balanceWei
41
+ }
42
+ }
43
+
44
+ type RegisterAgentPreflightClient = {
45
+ estimateGas: (args: { account: Address; to: Address; data: Hex }) => Promise<bigint>
46
+ getGasPrice: () => Promise<bigint>
47
+ getBalance: (args: { address: Address }) => Promise<bigint>
48
+ }
49
+
50
+ export async function preflightRegisterAgent(args: Erc8004RegistryConfig & {
51
+ ownerAddress: Address
52
+ agentURI: string
53
+ publicClient?: RegisterAgentPreflightClient
54
+ }): Promise<RegisterAgentPreflight> {
55
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
56
+ const data = encodeRegisterAgent({ agentURI: args.agentURI })
57
+ let gas: bigint
58
+ try {
59
+ gas = await publicClient.estimateGas({
60
+ account: args.ownerAddress,
61
+ to: args.identityRegistryAddress,
62
+ data,
63
+ })
64
+ } catch (err: unknown) {
65
+ throw new RegisterAgentPreflightError({
66
+ code: 'simulation-failed',
67
+ title: 'Registration Blocked',
68
+ detail: cleanRpcError(err),
69
+ hint: 'No transaction was sent.',
70
+ })
71
+ }
72
+ const [gasPrice, balance] = await Promise.all([
73
+ publicClient.getGasPrice(),
74
+ publicClient.getBalance({ address: args.ownerAddress }),
75
+ ])
76
+ const estimatedCost = gas * gasPrice
77
+ const requiredBalance = estimatedCost + estimatedCost / 5n
78
+ if (balance < requiredBalance) {
79
+ throw new RegisterAgentPreflightError({
80
+ code: 'insufficient-funds',
81
+ title: 'Not Enough ETH',
82
+ detail: `Need ~${formatEthAmount(requiredBalance)} ETH. Wallet has ${formatEthAmount(balance)} ETH.`,
83
+ hint: 'Add ETH to this wallet, then try again.',
84
+ requiredBalanceWei: requiredBalance,
85
+ balanceWei: balance,
86
+ })
87
+ }
88
+ return {
89
+ gas,
90
+ gasPrice,
91
+ estimatedCostWei: estimatedCost,
92
+ requiredBalanceWei: requiredBalance,
93
+ balanceWei: balance,
94
+ }
95
+ }
96
+
97
+ export async function preflightSetAgentUri(args: Erc8004RegistryConfig & {
98
+ account: Address
99
+ agentId: bigint
100
+ newUri: string
101
+ publicClient?: RegisterAgentPreflightClient
102
+ }): Promise<void> {
103
+ const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
104
+ const data = encodeSetAgentUri({ agentId: args.agentId, newUri: args.newUri })
105
+ try {
106
+ await publicClient.estimateGas({
107
+ account: args.account,
108
+ to: args.identityRegistryAddress,
109
+ data,
110
+ })
111
+ } catch (err: unknown) {
112
+ const detail = cleanRpcError(err)
113
+ const looksLikeOwnershipRevert = /not.*owner|owner.*only|unauthor|forbidden|caller/i.test(detail)
114
+ throw new RegisterAgentPreflightError({
115
+ code: 'simulation-failed',
116
+ title: 'Backup Update Blocked',
117
+ detail,
118
+ hint: looksLikeOwnershipRevert
119
+ ? `Connect the wallet that owns this agent (${args.account}) and try again.`
120
+ : 'No transaction was sent.',
121
+ })
122
+ }
123
+ }
@@ -0,0 +1,77 @@
1
+ import { decodeEventLog, encodeFunctionData, getAddress, type Address, type Hex } from 'viem'
2
+ import { ERC8004_ABI, REGISTERED_EVENT, TRANSFER_EVENT } from './abi.js'
3
+
4
+ type ReceiptLog = { address?: Address; topics: readonly Hex[]; data: Hex }
5
+
6
+ export function encodeRegisterAgent(args: {
7
+ agentURI: string
8
+ }): Hex {
9
+ return encodeFunctionData({
10
+ abi: ERC8004_ABI,
11
+ functionName: 'register',
12
+ args: [args.agentURI],
13
+ })
14
+ }
15
+
16
+ export function encodeSetAgentUri(args: {
17
+ agentId: bigint
18
+ newUri: string
19
+ }): Hex {
20
+ return encodeFunctionData({
21
+ abi: ERC8004_ABI,
22
+ functionName: 'setAgentURI',
23
+ args: [args.agentId, args.newUri],
24
+ })
25
+ }
26
+
27
+ export function registeredAgentFromReceipt(args: {
28
+ logs: ReceiptLog[]
29
+ identityRegistryAddress: Address
30
+ ownerAddress?: Address
31
+ fallbackAgentURI?: string
32
+ }): { agentId: bigint; agentURI: string; owner: Address } {
33
+ for (const log of args.logs) {
34
+ if (log.address && log.address.toLowerCase() !== args.identityRegistryAddress.toLowerCase()) continue
35
+ try {
36
+ const decoded = decodeEventLog({
37
+ abi: [REGISTERED_EVENT],
38
+ topics: log.topics as [Hex, ...Hex[]],
39
+ data: log.data,
40
+ })
41
+ if (decoded.eventName !== 'Registered') continue
42
+ const eventArgs = decoded.args as { agentId?: bigint; agentURI?: string; owner?: Address }
43
+ if (eventArgs.agentId === undefined || !eventArgs.agentURI || !eventArgs.owner) continue
44
+ if (args.ownerAddress && eventArgs.owner.toLowerCase() !== args.ownerAddress.toLowerCase()) continue
45
+ return {
46
+ agentId: eventArgs.agentId,
47
+ agentURI: eventArgs.agentURI,
48
+ owner: getAddress(eventArgs.owner),
49
+ }
50
+ } catch {
51
+ }
52
+ }
53
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
54
+ for (const log of args.logs) {
55
+ if (log.address && log.address.toLowerCase() !== args.identityRegistryAddress.toLowerCase()) continue
56
+ try {
57
+ const decoded = decodeEventLog({
58
+ abi: [TRANSFER_EVENT],
59
+ topics: log.topics as [Hex, ...Hex[]],
60
+ data: log.data,
61
+ })
62
+ if (decoded.eventName !== 'Transfer') continue
63
+ const eventArgs = decoded.args as { from?: Address; to?: Address; tokenId?: bigint }
64
+ if (!eventArgs.from || !eventArgs.to || eventArgs.tokenId === undefined) continue
65
+ if (eventArgs.from.toLowerCase() !== ZERO_ADDRESS) continue
66
+ if (args.ownerAddress && eventArgs.to.toLowerCase() !== args.ownerAddress.toLowerCase()) continue
67
+ const agentURI = args.fallbackAgentURI ?? ''
68
+ return {
69
+ agentId: eventArgs.tokenId,
70
+ agentURI,
71
+ owner: getAddress(eventArgs.to),
72
+ }
73
+ } catch {
74
+ }
75
+ }
76
+ throw new Error('ERC-8004 registration event was not found in transaction receipt')
77
+ }