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,114 @@
1
+ import {
2
+ createPublicClient,
3
+ fallback,
4
+ getAddress,
5
+ http,
6
+ type Address,
7
+ type Hex,
8
+ type PublicClient,
9
+ } from 'viem'
10
+ import { mainnet } from 'viem/chains'
11
+ import {
12
+ AGENT_RECORD_READ_KEY_LIST,
13
+ recordsFromTextMap,
14
+ type AgentEnsRecordState,
15
+ } from '../agentRecords.js'
16
+ import {
17
+ ENS_AUTOMATION_NAME_WRAPPER_ABI,
18
+ ENS_AUTOMATION_REGISTRY_ABI,
19
+ ENS_AUTOMATION_RESOLVER_ABI,
20
+ ENS_NAME_WRAPPER_ADDRESS_MAINNET,
21
+ ENS_REGISTRY_ADDRESS_MAINNET,
22
+ ENS_RPC_URLS,
23
+ ZERO_ADDRESS,
24
+ } from './contracts.js'
25
+ import type { EnsAutomationReadClient } from './types.js'
26
+
27
+ export function shortHex(value: string): string {
28
+ if (value.length <= 14) return value
29
+ return `${value.slice(0, 6)}...${value.slice(-4)}`
30
+ }
31
+
32
+ export function createEnsAutomationClient(): PublicClient {
33
+ const transports = ENS_RPC_URLS.map(url => http(url, { retryCount: 0, timeout: 8_000 }))
34
+ return createPublicClient({
35
+ chain: mainnet,
36
+ transport: transports.length === 1 ? transports[0]! : fallback(transports, { retryCount: 0 }),
37
+ })
38
+ }
39
+
40
+ export async function readOwner(client: EnsAutomationReadClient, node: Hex): Promise<Address> {
41
+ const owner = await client.readContract({
42
+ address: ENS_REGISTRY_ADDRESS_MAINNET,
43
+ abi: ENS_AUTOMATION_REGISTRY_ABI,
44
+ functionName: 'owner',
45
+ args: [node],
46
+ }) as Address
47
+ return getAddress(owner)
48
+ }
49
+
50
+ export async function readResolver(client: EnsAutomationReadClient, node: Hex): Promise<Address> {
51
+ const resolver = await client.readContract({
52
+ address: ENS_REGISTRY_ADDRESS_MAINNET,
53
+ abi: ENS_AUTOMATION_REGISTRY_ABI,
54
+ functionName: 'resolver',
55
+ args: [node],
56
+ }) as Address
57
+ return getAddress(resolver)
58
+ }
59
+
60
+ export async function readWrappedOwner(client: EnsAutomationReadClient, node: Hex): Promise<Address> {
61
+ const owner = await client.readContract({
62
+ address: ENS_NAME_WRAPPER_ADDRESS_MAINNET,
63
+ abi: ENS_AUTOMATION_NAME_WRAPPER_ABI,
64
+ functionName: 'ownerOf',
65
+ args: [BigInt(node)],
66
+ }) as Address
67
+ return getAddress(owner)
68
+ }
69
+
70
+ export async function readAddressRecord(client: EnsAutomationReadClient, resolverAddress: Address, node: Hex): Promise<Address | null> {
71
+ try {
72
+ const addr = await client.readContract({
73
+ address: resolverAddress,
74
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
75
+ functionName: 'addr',
76
+ args: [node],
77
+ }) as Address
78
+ return isZero(addr) ? null : getAddress(addr)
79
+ } catch {
80
+ return null
81
+ }
82
+ }
83
+
84
+ export async function readTextRecords(client: EnsAutomationReadClient, resolverAddress: Address, node: Hex): Promise<AgentEnsRecordState> {
85
+ const text: Record<string, string> = {}
86
+ for (const key of AGENT_RECORD_READ_KEY_LIST) {
87
+ try {
88
+ const value = await client.readContract({
89
+ address: resolverAddress,
90
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
91
+ functionName: 'text',
92
+ args: [node, key],
93
+ }) as string
94
+ if (value) text[key] = value
95
+ } catch {
96
+ }
97
+ }
98
+ return recordsFromTextMap(text)
99
+ }
100
+
101
+ export function isZero(address: Address): boolean {
102
+ return address.toLowerCase() === ZERO_ADDRESS
103
+ }
104
+
105
+ export function sameAddress(a: Address, b: Address): boolean {
106
+ return a.toLowerCase() === b.toLowerCase()
107
+ }
108
+
109
+ export function normalizeAgentRecords(records: AgentEnsRecordState): AgentEnsRecordState {
110
+ return {
111
+ ...records,
112
+ ...(records.token ? { token: records.token.toLowerCase() } : {}),
113
+ }
114
+ }
@@ -0,0 +1,63 @@
1
+ import { getAddress, namehash, type Address } from 'viem'
2
+ import { normalizeEthDomain } from '../ensLookup.js'
3
+ import { validateErc8004TokenOwner } from '../../registry/erc8004.js'
4
+ import { ENS_NAME_WRAPPER_ADDRESS_MAINNET } from './contracts.js'
5
+ import { isRootEthName } from './names.js'
6
+ import {
7
+ createEnsAutomationClient,
8
+ isZero,
9
+ readOwner,
10
+ readWrappedOwner,
11
+ sameAddress,
12
+ shortHex,
13
+ } from './read.js'
14
+ import type {
15
+ EnsRootPreflightArgs,
16
+ EnsRootPreflightResult,
17
+ } from './types.js'
18
+
19
+ export async function preflightEnsRoot(args: EnsRootPreflightArgs): Promise<EnsRootPreflightResult> {
20
+ const rootName = normalizeEthDomain(args.rootName)
21
+ if (!isRootEthName(rootName)) {
22
+ return { ok: false, reason: 'invalid-root', detail: 'Enter the parent .eth name, e.g. name.eth' }
23
+ }
24
+ if (args.agentId === undefined || args.agentId === '') {
25
+ return { ok: false, reason: 'missing-token-id', detail: 'this identity is missing an ERC-8004 token id' }
26
+ }
27
+ const expectedOwnerAddress = getAddress(args.expectedOwnerAddress)
28
+ const client = args.ensClient ?? createEnsAutomationClient()
29
+ const rootNode = namehash(rootName)
30
+ let rootOwner: Address
31
+ try {
32
+ rootOwner = await readOwner(client, rootNode)
33
+ } catch (err: unknown) {
34
+ return { ok: false, reason: 'lookup-failed', detail: err instanceof Error ? err.message : String(err) }
35
+ }
36
+ if (isZero(rootOwner)) {
37
+ return { ok: false, reason: 'root-not-owned', detail: `${rootName} does not have an ENS manager on Ethereum mainnet` }
38
+ }
39
+ const parentWrapped = sameAddress(rootOwner, ENS_NAME_WRAPPER_ADDRESS_MAINNET)
40
+ let ownerAddress: Address
41
+ try {
42
+ ownerAddress = parentWrapped ? await readWrappedOwner(client, rootNode) : getAddress(rootOwner)
43
+ } catch (err: unknown) {
44
+ return { ok: false, reason: 'wrapped-parent', detail: err instanceof Error ? err.message : String(err) }
45
+ }
46
+ if (!sameAddress(ownerAddress, expectedOwnerAddress)) {
47
+ return {
48
+ ok: false,
49
+ reason: 'root-owner-mismatch',
50
+ detail: `${rootName} is managed by ${shortHex(ownerAddress)}, not the connected wallet ${shortHex(expectedOwnerAddress)}`,
51
+ }
52
+ }
53
+ const tokenOwner = await validateErc8004TokenOwner({
54
+ ...args.registry,
55
+ agentId: typeof args.agentId === 'bigint' ? args.agentId : BigInt(args.agentId),
56
+ expectedOwner: ownerAddress,
57
+ publicClient: args.tokenPublicClient,
58
+ })
59
+ if (!tokenOwner.ok) {
60
+ return { ok: false, reason: tokenOwner.reason, detail: tokenOwner.detail }
61
+ }
62
+ return { ok: true, ownerAddress }
63
+ }
@@ -0,0 +1,284 @@
1
+ import { getAddress, namehash, type Address } from 'viem'
2
+ import {
3
+ buildAgentEnsRecords,
4
+ diffRecords,
5
+ } from '../agentRecords.js'
6
+ import { normalizeEthDomain, splitSubdomainName } from '../ensLookup.js'
7
+ import { validateErc8004TokenOwner } from '../../registry/erc8004.js'
8
+ import {
9
+ ENS_NAME_WRAPPER_ADDRESS_MAINNET,
10
+ ENS_PUBLIC_RESOLVER_ADDRESS_MAINNET,
11
+ } from './contracts.js'
12
+ import {
13
+ createEnsAutomationClient,
14
+ isZero,
15
+ normalizeAgentRecords,
16
+ readAddressRecord,
17
+ readOwner,
18
+ readResolver,
19
+ readTextRecords,
20
+ readWrappedOwner,
21
+ sameAddress,
22
+ shortHex,
23
+ } from './read.js'
24
+ import {
25
+ hasValidSubdomainParts,
26
+ isRootEthName,
27
+ isValidSubdomainLabel,
28
+ } from './names.js'
29
+ import type {
30
+ EnsSetupBlockedPlan,
31
+ EnsSetupPreflight,
32
+ EnsSetupPreflightArgs,
33
+ EnsRegistryAction,
34
+ } from './types.js'
35
+
36
+ export async function preflightEnsSetup(args: EnsSetupPreflightArgs): Promise<EnsSetupPreflight> {
37
+ const rootName = normalizeEthDomain(args.rootName)
38
+ const label = args.label.trim().toLowerCase()
39
+ const operatorAddress = getAddress(args.operatorAddress)
40
+ const mode = args.mode ?? 'advanced'
41
+ const fullName = `${label}.${rootName}`
42
+
43
+ if (!isRootEthName(rootName)) {
44
+ return manual(args, {
45
+ rootName,
46
+ label,
47
+ fullName,
48
+ operatorAddress,
49
+ reason: 'invalid-root',
50
+ detail: 'Enter the parent .eth name, e.g. name.eth',
51
+ })
52
+ }
53
+ if (!isValidSubdomainLabel(label) || !hasValidSubdomainParts(fullName)) {
54
+ return manual(args, {
55
+ rootName,
56
+ label,
57
+ fullName,
58
+ operatorAddress,
59
+ reason: 'invalid-label',
60
+ detail: 'Agent subdomain can use lowercase letters, numbers, and hyphens only',
61
+ })
62
+ }
63
+ if (args.agentId === undefined || args.agentId === '') {
64
+ return manual(args, {
65
+ rootName,
66
+ label,
67
+ fullName,
68
+ operatorAddress,
69
+ reason: 'missing-token-id',
70
+ detail: 'This identity is missing an ERC-8004 token id',
71
+ })
72
+ }
73
+
74
+ const client = args.ensClient ?? createEnsAutomationClient()
75
+ const rootNode = namehash(rootName)
76
+ const fullNode = namehash(fullName)
77
+
78
+ let rootOwner: Address
79
+ try {
80
+ rootOwner = await readOwner(client, rootNode)
81
+ } catch (err: unknown) {
82
+ return manual(args, {
83
+ rootName,
84
+ label,
85
+ fullName,
86
+ operatorAddress,
87
+ reason: 'lookup-failed',
88
+ detail: err instanceof Error ? err.message : String(err),
89
+ })
90
+ }
91
+
92
+ if (isZero(rootOwner)) {
93
+ return manual(args, {
94
+ rootName,
95
+ label,
96
+ fullName,
97
+ operatorAddress,
98
+ reason: 'root-not-owned',
99
+ detail: `${rootName} does not have an ENS manager on Ethereum mainnet`,
100
+ })
101
+ }
102
+ const parentWrapped = sameAddress(rootOwner, ENS_NAME_WRAPPER_ADDRESS_MAINNET)
103
+ let ownerAddress: Address
104
+ try {
105
+ ownerAddress = parentWrapped
106
+ ? await readWrappedOwner(client, rootNode)
107
+ : getAddress(rootOwner)
108
+ } catch (err: unknown) {
109
+ return manual(args, {
110
+ rootName,
111
+ label,
112
+ fullName,
113
+ operatorAddress,
114
+ reason: 'wrapped-parent',
115
+ detail: err instanceof Error ? err.message : String(err),
116
+ })
117
+ }
118
+ const expectedOwnerAddress = args.expectedOwnerAddress ? getAddress(args.expectedOwnerAddress) : null
119
+ if (expectedOwnerAddress && !sameAddress(ownerAddress, expectedOwnerAddress)) {
120
+ return manual(args, {
121
+ rootName,
122
+ label,
123
+ fullName,
124
+ operatorAddress,
125
+ ownerAddress,
126
+ reason: 'root-owner-mismatch',
127
+ detail: `${rootName} is managed by ${shortHex(ownerAddress)}, not the connected wallet ${shortHex(expectedOwnerAddress)}`,
128
+ })
129
+ }
130
+ if (!args.allowSameOwnerOperator && sameAddress(ownerAddress, operatorAddress)) {
131
+ return manual(args, {
132
+ rootName,
133
+ label,
134
+ fullName,
135
+ operatorAddress,
136
+ ownerAddress,
137
+ reason: 'operator-matches-owner',
138
+ detail: 'Operator wallet must be different from the owner wallet that owns the ERC-8004 token and parent ENS name',
139
+ })
140
+ }
141
+
142
+ const tokenOwner = await validateErc8004TokenOwner({
143
+ ...args.registry,
144
+ agentId: typeof args.agentId === 'bigint' ? args.agentId : BigInt(args.agentId),
145
+ expectedOwner: ownerAddress,
146
+ publicClient: args.tokenPublicClient,
147
+ })
148
+ if (!tokenOwner.ok) {
149
+ return manual(args, {
150
+ rootName,
151
+ label,
152
+ fullName,
153
+ operatorAddress,
154
+ ownerAddress,
155
+ reason: tokenOwner.reason,
156
+ detail: tokenOwner.detail,
157
+ })
158
+ }
159
+
160
+ let childOwner: Address
161
+ let childResolver: Address
162
+ try {
163
+ childOwner = await readOwner(client, fullNode)
164
+ childResolver = await readResolver(client, fullNode)
165
+ } catch (err: unknown) {
166
+ return manual(args, {
167
+ rootName,
168
+ label,
169
+ fullName,
170
+ operatorAddress,
171
+ ownerAddress,
172
+ reason: 'lookup-failed',
173
+ detail: err instanceof Error ? err.message : String(err),
174
+ })
175
+ }
176
+
177
+ let registryAction: EnsRegistryAction = 'none'
178
+ let resolverAddress = getAddress(childResolver)
179
+ if (isZero(childOwner)) {
180
+ registryAction = parentWrapped ? 'create-wrapped-subdomain' : 'create-subdomain'
181
+ resolverAddress = ENS_PUBLIC_RESOLVER_ADDRESS_MAINNET
182
+ } else if (sameAddress(childOwner, ENS_NAME_WRAPPER_ADDRESS_MAINNET)) {
183
+ let wrappedChildOwner: Address
184
+ try {
185
+ wrappedChildOwner = await readWrappedOwner(client, fullNode)
186
+ } catch (err: unknown) {
187
+ return manual(args, {
188
+ rootName,
189
+ label,
190
+ fullName,
191
+ operatorAddress,
192
+ ownerAddress,
193
+ reason: 'subdomain-wrapped',
194
+ detail: err instanceof Error ? err.message : String(err),
195
+ })
196
+ }
197
+ if (!sameAddress(wrappedChildOwner, ownerAddress)) {
198
+ return manual(args, {
199
+ rootName,
200
+ label,
201
+ fullName,
202
+ operatorAddress,
203
+ ownerAddress,
204
+ reason: 'subdomain-owned-by-other',
205
+ detail: `${fullName} is wrapped and owned by ${getAddress(wrappedChildOwner)}`,
206
+ })
207
+ }
208
+ if (isZero(childResolver)) {
209
+ registryAction = 'set-wrapped-resolver'
210
+ resolverAddress = ENS_PUBLIC_RESOLVER_ADDRESS_MAINNET
211
+ }
212
+ } else if (!sameAddress(childOwner, ownerAddress)) {
213
+ return manual(args, {
214
+ rootName,
215
+ label,
216
+ fullName,
217
+ operatorAddress,
218
+ ownerAddress,
219
+ reason: 'subdomain-owned-by-other',
220
+ detail: `${fullName} is managed by ${shortHex(getAddress(childOwner))}`,
221
+ })
222
+ } else if (isZero(childResolver)) {
223
+ registryAction = 'set-resolver'
224
+ resolverAddress = ENS_PUBLIC_RESOLVER_ADDRESS_MAINNET
225
+ }
226
+
227
+ const currentAddress = isZero(childResolver) ? null : await readAddressRecord(client, resolverAddress, fullNode)
228
+ const currentRecords = normalizeAgentRecords(isZero(childResolver) ? {} : await readTextRecords(client, resolverAddress, fullNode))
229
+ const nextRecords = buildAgentEnsRecords({
230
+ chainId: args.registry.chainId,
231
+ identityRegistryAddress: args.registry.identityRegistryAddress,
232
+ agentId: String(args.agentId),
233
+ agentCardCid: args.agentCardCid,
234
+ })
235
+ if (currentRecords.token && nextRecords.token && currentRecords.token !== nextRecords.token) {
236
+ return manual(args, {
237
+ rootName,
238
+ label,
239
+ fullName,
240
+ operatorAddress,
241
+ ownerAddress,
242
+ resolverAddress,
243
+ reason: 'token-record-collision',
244
+ detail: `${fullName} already points to another ERC-8004 token`,
245
+ currentRecords,
246
+ nextRecords,
247
+ })
248
+ }
249
+
250
+ const recordDiffs = diffRecords(currentRecords, nextRecords)
251
+ const addressChanged = !currentAddress || !sameAddress(currentAddress, ownerAddress)
252
+ const needsRegistryTx = registryAction !== 'none'
253
+ const needsRecordsTx = addressChanged || recordDiffs.some(diff => diff.changed)
254
+ return {
255
+ ok: true,
256
+ setup: {
257
+ mode,
258
+ rootName,
259
+ label,
260
+ fullName,
261
+ ownerAddress,
262
+ operatorAddress,
263
+ resolverAddress,
264
+ registryAction,
265
+ addressRecord: {
266
+ current: currentAddress,
267
+ next: ownerAddress,
268
+ changed: addressChanged,
269
+ },
270
+ currentRecords,
271
+ nextRecords,
272
+ recordDiffs,
273
+ txCount: (needsRegistryTx ? 1 : 0) + (needsRecordsTx ? 1 : 0),
274
+ warnings: [],
275
+ },
276
+ }
277
+ }
278
+
279
+ function manual(
280
+ args: EnsSetupPreflightArgs,
281
+ fallback: Omit<EnsSetupBlockedPlan, 'mode'> & Partial<Pick<EnsSetupBlockedPlan, 'mode'>>,
282
+ ): EnsSetupPreflight {
283
+ return { ok: false, fallback: { ...fallback, mode: args.mode ?? 'advanced' } }
284
+ }
@@ -0,0 +1,107 @@
1
+ import {
2
+ encodeFunctionData,
3
+ labelhash,
4
+ namehash,
5
+ } from 'viem'
6
+ import {
7
+ DEFAULT_EXPIRY,
8
+ DEFAULT_FUSES,
9
+ DEFAULT_TTL,
10
+ ENS_AUTOMATION_NAME_WRAPPER_ABI,
11
+ ENS_AUTOMATION_REGISTRY_ABI,
12
+ ENS_AUTOMATION_RESOLVER_ABI,
13
+ ENS_NAME_WRAPPER_ADDRESS_MAINNET,
14
+ ENS_REGISTRY_ADDRESS_MAINNET,
15
+ ZERO_ADDRESS,
16
+ } from './contracts.js'
17
+ import type {
18
+ EncodedEnsTransaction,
19
+ EnsSetupPlan,
20
+ } from './types.js'
21
+
22
+ export function encodeEnsRegistryTransaction(setup: EnsSetupPlan): EncodedEnsTransaction | null {
23
+ const rootNode = namehash(setup.rootName)
24
+ const fullNode = namehash(setup.fullName)
25
+ if (setup.registryAction === 'create-subdomain') {
26
+ const data = encodeFunctionData({
27
+ abi: ENS_AUTOMATION_REGISTRY_ABI,
28
+ functionName: 'setSubnodeRecord',
29
+ args: [rootNode, labelhash(setup.label), setup.ownerAddress, setup.resolverAddress, DEFAULT_TTL],
30
+ })
31
+ return { to: ENS_REGISTRY_ADDRESS_MAINNET, data, calls: [data] }
32
+ }
33
+ if (setup.registryAction === 'create-wrapped-subdomain') {
34
+ const data = encodeFunctionData({
35
+ abi: ENS_AUTOMATION_NAME_WRAPPER_ABI,
36
+ functionName: 'setSubnodeRecord',
37
+ args: [rootNode, setup.label, setup.ownerAddress, setup.resolverAddress, DEFAULT_TTL, DEFAULT_FUSES, DEFAULT_EXPIRY],
38
+ })
39
+ return { to: ENS_NAME_WRAPPER_ADDRESS_MAINNET, data, calls: [data] }
40
+ }
41
+ if (setup.registryAction === 'set-resolver') {
42
+ const data = encodeFunctionData({
43
+ abi: ENS_AUTOMATION_REGISTRY_ABI,
44
+ functionName: 'setResolver',
45
+ args: [fullNode, setup.resolverAddress],
46
+ })
47
+ return { to: ENS_REGISTRY_ADDRESS_MAINNET, data, calls: [data] }
48
+ }
49
+ if (setup.registryAction === 'set-wrapped-resolver') {
50
+ const data = encodeFunctionData({
51
+ abi: ENS_AUTOMATION_NAME_WRAPPER_ABI,
52
+ functionName: 'setResolver',
53
+ args: [fullNode, setup.resolverAddress],
54
+ })
55
+ return { to: ENS_NAME_WRAPPER_ADDRESS_MAINNET, data, calls: [data] }
56
+ }
57
+ return null
58
+ }
59
+
60
+ export function encodeEnsRecordsTransaction(setup: EnsSetupPlan): EncodedEnsTransaction | null {
61
+ const node = namehash(setup.fullName)
62
+ const calls: `0x${string}`[] = []
63
+ if (setup.addressRecord.changed) {
64
+ calls.push(encodeFunctionData({
65
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
66
+ functionName: 'setAddr',
67
+ args: [node, setup.addressRecord.next],
68
+ }))
69
+ }
70
+ for (const diff of setup.recordDiffs) {
71
+ if (!diff.changed) continue
72
+ calls.push(encodeFunctionData({
73
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
74
+ functionName: 'setText',
75
+ args: [node, diff.key, diff.next],
76
+ }))
77
+ }
78
+ if (calls.length === 0) return null
79
+ const data = calls.length === 1
80
+ ? calls[0]!
81
+ : encodeFunctionData({
82
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
83
+ functionName: 'multicall',
84
+ args: [calls],
85
+ })
86
+ return { to: setup.resolverAddress, data, calls }
87
+ }
88
+
89
+ export function encodeDeleteSubnodeRegistry(parentName: string, label: string): EncodedEnsTransaction {
90
+ const parentNode = namehash(parentName)
91
+ const data = encodeFunctionData({
92
+ abi: ENS_AUTOMATION_REGISTRY_ABI,
93
+ functionName: 'setSubnodeRecord',
94
+ args: [parentNode, labelhash(label), ZERO_ADDRESS, ZERO_ADDRESS, DEFAULT_TTL],
95
+ })
96
+ return { to: ENS_REGISTRY_ADDRESS_MAINNET, data, calls: [data] }
97
+ }
98
+
99
+ export function encodeDeleteSubnodeWrapped(parentName: string, label: string): EncodedEnsTransaction {
100
+ const parentNode = namehash(parentName)
101
+ const data = encodeFunctionData({
102
+ abi: ENS_AUTOMATION_NAME_WRAPPER_ABI,
103
+ functionName: 'setSubnodeRecord',
104
+ args: [parentNode, label, ZERO_ADDRESS, ZERO_ADDRESS, DEFAULT_TTL, DEFAULT_FUSES, DEFAULT_EXPIRY],
105
+ })
106
+ return { to: ENS_NAME_WRAPPER_ADDRESS_MAINNET, data, calls: [data] }
107
+ }
@@ -0,0 +1,126 @@
1
+ import type { Address, Hex, PublicClient } from 'viem'
2
+ import type {
3
+ AgentEnsRecordState,
4
+ AgentEnsRecords,
5
+ AgentRecordDiff,
6
+ } from '../agentRecords.js'
7
+ import {
8
+ validateErc8004TokenOwner,
9
+ type Erc8004RegistryConfig,
10
+ } from '../../registry/erc8004.js'
11
+
12
+ export type EnsAutomationReadClient = Pick<PublicClient, 'readContract'>
13
+ export type TokenOwnerReadClient = Parameters<typeof validateErc8004TokenOwner>[0]['publicClient']
14
+
15
+ export type EnsRegistryAction = 'create-subdomain' | 'set-resolver' | 'create-wrapped-subdomain' | 'set-wrapped-resolver' | 'none'
16
+
17
+ export type EnsSetupPlan = {
18
+ mode: 'simple' | 'advanced'
19
+ rootName: string
20
+ label: string
21
+ fullName: string
22
+ ownerAddress: Address
23
+ operatorAddress: Address
24
+ resolverAddress: Address
25
+ registryAction: EnsRegistryAction
26
+ addressRecord: {
27
+ current: Address | null
28
+ next: Address
29
+ changed: boolean
30
+ }
31
+ currentRecords: AgentEnsRecordState
32
+ nextRecords: AgentEnsRecords
33
+ recordDiffs: AgentRecordDiff[]
34
+ txCount: number
35
+ warnings: string[]
36
+ }
37
+
38
+ export type EnsSetupBlockedPlan = {
39
+ mode: 'simple' | 'advanced'
40
+ rootName: string
41
+ label: string
42
+ fullName: string
43
+ operatorAddress: Address
44
+ ownerAddress?: Address
45
+ resolverAddress?: Address
46
+ reason:
47
+ | 'invalid-root'
48
+ | 'invalid-label'
49
+ | 'missing-token-id'
50
+ | 'root-not-owned'
51
+ | 'root-owner-mismatch'
52
+ | 'wrapped-parent'
53
+ | 'parent-missing-resolver'
54
+ | 'subdomain-owned-by-other'
55
+ | 'subdomain-wrapped'
56
+ | 'operator-matches-owner'
57
+ | 'token-owner-mismatch'
58
+ | 'token-owner-lookup-failed'
59
+ | 'token-record-collision'
60
+ | 'lookup-failed'
61
+ detail: string
62
+ currentRecords?: AgentEnsRecordState
63
+ nextRecords?: AgentEnsRecords
64
+ }
65
+
66
+ export type EnsSetupPreflight =
67
+ | { ok: true; setup: EnsSetupPlan }
68
+ | { ok: false; fallback: EnsSetupBlockedPlan }
69
+
70
+ export type EncodedEnsTransaction = {
71
+ to: Address
72
+ data: Hex
73
+ calls: Hex[]
74
+ }
75
+
76
+ export type EnsSetupPreflightArgs = {
77
+ rootName: string
78
+ label: string
79
+ operatorAddress: Address
80
+ mode?: 'simple' | 'advanced'
81
+ expectedOwnerAddress?: Address
82
+ allowSameOwnerOperator?: boolean
83
+ registry: Erc8004RegistryConfig
84
+ agentId: string | bigint | undefined
85
+ agentCardCid?: string
86
+ ensClient?: EnsAutomationReadClient
87
+ tokenPublicClient?: TokenOwnerReadClient
88
+ }
89
+
90
+ export type EnsRootPreflightArgs = {
91
+ rootName: string
92
+ expectedOwnerAddress: Address
93
+ registry: Erc8004RegistryConfig
94
+ agentId: string | bigint | undefined
95
+ ensClient?: EnsAutomationReadClient
96
+ tokenPublicClient?: TokenOwnerReadClient
97
+ }
98
+
99
+ export type EnsRootPreflightResult =
100
+ | { ok: true; ownerAddress: Address }
101
+ | { ok: false; reason: 'invalid-root' | 'missing-token-id' | 'root-not-owned' | 'wrapped-parent' | 'root-owner-mismatch' | 'token-owner-mismatch' | 'token-owner-lookup-failed' | 'lookup-failed'; detail: string }
102
+
103
+ export type EnsSubdomainDeletePlan = {
104
+ fullName: string
105
+ parentName: string
106
+ label: string
107
+ parentWrapped: boolean
108
+ parentOwnerAddress: Address
109
+ transaction: EncodedEnsTransaction
110
+ }
111
+
112
+ export type EnsSubdomainDeletePreflightArgs = {
113
+ fullName: string
114
+ expectedOwnerAddress: Address
115
+ ensClient?: EnsAutomationReadClient
116
+ }
117
+
118
+ export type EnsSubdomainDeletePreflightResult =
119
+ | { ok: true; plan: EnsSubdomainDeletePlan }
120
+ | { ok: false; reason: 'invalid-name' | 'not-a-subdomain' | 'lookup-failed' | 'parent-not-owned' | 'parent-owner-mismatch' | 'subdomain-missing'; detail: string }
121
+
122
+ export type OperatorSetDiff = {
123
+ inSync: boolean
124
+ metadataOnly: Address[]
125
+ resolverOnly: Address[]
126
+ }