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,29 @@
1
+ export {
2
+ ENS_AUTOMATION_RESOLVER_ABI,
3
+ ENS_PUBLIC_RESOLVER_ADDRESS_MAINNET,
4
+ } from './ensAutomation/contracts.js'
5
+ export type {
6
+ EncodedEnsTransaction,
7
+ EnsAutomationReadClient,
8
+ EnsRegistryAction,
9
+ EnsRootPreflightArgs,
10
+ EnsRootPreflightResult,
11
+ EnsSetupBlockedPlan,
12
+ EnsSetupPlan,
13
+ EnsSetupPreflight,
14
+ EnsSetupPreflightArgs,
15
+ EnsSubdomainDeletePlan,
16
+ EnsSubdomainDeletePreflightArgs,
17
+ EnsSubdomainDeletePreflightResult,
18
+ OperatorSetDiff,
19
+ TokenOwnerReadClient,
20
+ } from './ensAutomation/types.js'
21
+ export { preflightEnsSetup } from './ensAutomation/setup.js'
22
+ export { preflightEnsRoot } from './ensAutomation/root.js'
23
+ export {
24
+ encodeEnsRecordsTransaction,
25
+ encodeEnsRegistryTransaction,
26
+ } from './ensAutomation/transactions.js'
27
+ export { isRootEthName } from './ensAutomation/names.js'
28
+ export { preflightDeleteSubdomain } from './ensAutomation/delete.js'
29
+ export { compareOperatorSets } from './ensAutomation/operators.js'
@@ -0,0 +1,43 @@
1
+ import { createPublicClient, fallback, http, type PublicClient } from 'viem'
2
+ import { mainnet } from 'viem/chains'
3
+ import { ENS_RPC_URLS, RPC_TIMEOUT_MS } from './constants.js'
4
+
5
+ export function createMainnetClient(): PublicClient {
6
+ const transports = ENS_RPC_URLS.map(url => http(url, { retryCount: 0, timeout: RPC_TIMEOUT_MS }))
7
+ return createPublicClient({
8
+ chain: mainnet,
9
+ transport: transports.length === 1 ? transports[0]! : fallback(transports, { retryCount: 0 }),
10
+ })
11
+ }
12
+
13
+ class AbortedError extends Error {
14
+ constructor() { super('Aborted') }
15
+ }
16
+
17
+ class TimedOutError extends Error {
18
+ constructor(label: string) { super(`${label} timed out`) }
19
+ }
20
+
21
+ export function withDeadline<T>(promise: Promise<T>, ms: number, label: string, signal?: AbortSignal): Promise<T> {
22
+ if (signal?.aborted) return Promise.reject(new AbortedError())
23
+ return new Promise<T>((resolve, reject) => {
24
+ const timer = setTimeout(() => reject(new TimedOutError(label)), ms)
25
+ const onAbort = () => {
26
+ clearTimeout(timer)
27
+ reject(new AbortedError())
28
+ }
29
+ signal?.addEventListener('abort', onAbort, { once: true })
30
+ promise.then(
31
+ value => {
32
+ clearTimeout(timer)
33
+ signal?.removeEventListener('abort', onAbort)
34
+ resolve(value)
35
+ },
36
+ err => {
37
+ clearTimeout(timer)
38
+ signal?.removeEventListener('abort', onAbort)
39
+ reject(err)
40
+ },
41
+ )
42
+ })
43
+ }
@@ -0,0 +1,26 @@
1
+ import { parseAbi, type Address } from 'viem'
2
+
3
+ export const RPC_TIMEOUT_MS = 8_000
4
+
5
+ export const ENS_REGISTRY_ADDRESS_MAINNET = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e' as Address
6
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as Address
7
+
8
+ export const ENS_RPC_URLS = [
9
+ 'https://ethereum.publicnode.com',
10
+ 'https://eth.llamarpc.com',
11
+ 'https://rpc.ankr.com/eth',
12
+ ] as const
13
+
14
+ export const ETH_NAME_PATTERN = /^([a-z0-9-]+.)+eth$/i
15
+
16
+ export const ENS_REGISTRY_ABI = parseAbi([
17
+ 'function owner(bytes32 node) view returns (address)',
18
+ 'function resolver(bytes32 node) view returns (address)',
19
+ ])
20
+
21
+ export const RESOLVER_ABI = parseAbi([
22
+ 'function addr(bytes32 node) view returns (address)',
23
+ 'function text(bytes32 node, string key) view returns (string)',
24
+ 'function setText(bytes32 node, string key, string value)',
25
+ 'function multicall(bytes[] data) returns (bytes[])',
26
+ ])
@@ -0,0 +1,70 @@
1
+ import type { Address } from 'viem'
2
+ import type { DiscoverOptions, EnsNameDiscoveryResult } from './types.js'
3
+ import { RPC_TIMEOUT_MS } from './constants.js'
4
+ import { createMainnetClient } from './client.js'
5
+ import { isEthDomain, normalizeEthDomain, splitSubdomainName } from './names.js'
6
+
7
+ export async function discoverOwnedEnsNames(
8
+ ownerAddress: Address,
9
+ opts: DiscoverOptions = {},
10
+ ): Promise<string[]> {
11
+ const result = await discoverOwnedEnsNameDetails(ownerAddress, opts)
12
+ return result.names
13
+ }
14
+
15
+ export async function discoverOwnedEnsNameDetails(
16
+ ownerAddress: Address,
17
+ opts: DiscoverOptions = {},
18
+ ): Promise<EnsNameDiscoveryResult> {
19
+ const rpcTimeoutMs = opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS
20
+ const controller = new AbortController()
21
+ const onParentAbort = () => controller.abort()
22
+ opts.signal?.addEventListener('abort', onParentAbort, { once: true })
23
+ const budgetTimer = setTimeout(() => controller.abort(), rpcTimeoutMs)
24
+ try {
25
+ const client = opts.publicClient ?? createMainnetClient()
26
+ if (controller.signal.aborted) {
27
+ return { status: 'error', names: [], sourcesChecked: [], errors: ['ENS name lookup was cancelled'] }
28
+ }
29
+ try {
30
+ const primary = await client.getEnsName({ address: ownerAddress })
31
+ const names = new Set<string>()
32
+ if (primary) {
33
+ const normalized = normalizeEthDomain(primary)
34
+ if (normalized && normalized.endsWith('.eth')) {
35
+ if (isRootEthName(normalized)) {
36
+ names.add(normalized)
37
+ } else {
38
+ const parts = splitSubdomainName(normalized)
39
+ if (parts && isRootEthName(parts.parent)) names.add(parts.parent)
40
+ }
41
+ }
42
+ }
43
+ return {
44
+ status: 'ok',
45
+ names: [...names].sort((a, b) => a.localeCompare(b)),
46
+ sourcesChecked: ['ENS reverse resolver'],
47
+ errors: [],
48
+ }
49
+ } catch (err) {
50
+ return {
51
+ status: 'error',
52
+ names: [],
53
+ sourcesChecked: [],
54
+ errors: [formatDiscoveryError(err)],
55
+ }
56
+ }
57
+ } finally {
58
+ clearTimeout(budgetTimer)
59
+ opts.signal?.removeEventListener('abort', onParentAbort)
60
+ }
61
+ }
62
+
63
+ function formatDiscoveryError(err: unknown): string {
64
+ return err instanceof Error ? err.message : String(err)
65
+ }
66
+
67
+ function isRootEthName(value: string): boolean {
68
+ const normalized = normalizeEthDomain(value)
69
+ return isEthDomain(normalized) && normalized.split('.').length === 2
70
+ }
@@ -0,0 +1,34 @@
1
+ import { ETH_NAME_PATTERN } from './constants.js'
2
+
3
+ export function isEthDomain(value: string): boolean {
4
+ const trimmed = value.trim().toLowerCase()
5
+ if (!trimmed.endsWith('.eth')) return false
6
+ if (trimmed === '.eth') return false
7
+ return ETH_NAME_PATTERN.test(trimmed)
8
+ }
9
+
10
+ export function normalizeEthDomain(value: string): string {
11
+ return value.trim().toLowerCase().replace(/\.+$/, '')
12
+ }
13
+
14
+ export function sanitizeSubdomainPrefix(value: string): string {
15
+ return value
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9-]/g, '-')
18
+ .replace(/-+/g, '-')
19
+ .replace(/^-|-$/g, '')
20
+ }
21
+
22
+ type SubdomainParts = { parent: string; label: string }
23
+
24
+ export function splitSubdomainName(fullName: string): SubdomainParts | null {
25
+ const trimmed = fullName.trim().toLowerCase()
26
+ if (!isEthDomain(trimmed)) return null
27
+ const dot = trimmed.indexOf('.')
28
+ if (dot <= 0 || dot === trimmed.length - 1) return null
29
+ const label = trimmed.slice(0, dot)
30
+ const parent = trimmed.slice(dot + 1)
31
+ if (!isEthDomain(parent)) return null
32
+ if (!/^[a-z0-9-]+$/.test(label)) return null
33
+ return { parent, label }
34
+ }
@@ -0,0 +1,45 @@
1
+ import { encodeFunctionData, namehash, type Address, type Hex } from 'viem'
2
+ import type { DiscoverOptions } from './types.js'
3
+ import { RESOLVER_ABI } from './constants.js'
4
+ import { splitSubdomainName } from './names.js'
5
+ import { readResolverAddress } from './resolve.js'
6
+
7
+ export type EncodedEnsRecordTransaction = {
8
+ resolverAddress: Address
9
+ data: Hex
10
+ multicall: boolean
11
+ calls: Hex[]
12
+ }
13
+
14
+ export async function encodeSetEthagentTextRecords(
15
+ fullName: string,
16
+ records: Record<string, string>,
17
+ opts: DiscoverOptions = {},
18
+ ): Promise<EncodedEnsRecordTransaction> {
19
+ if (!splitSubdomainName(fullName)) {
20
+ throw new Error('Agent ENS records must be written to a subdomain, not a root .eth name')
21
+ }
22
+ const resolver = await readResolverAddress(fullName, opts)
23
+ if (!resolver) {
24
+ throw new Error(`no resolver set on ${fullName} - set one in the official ENS app first`)
25
+ }
26
+ const node = namehash(fullName)
27
+ const entries = Object.entries(records)
28
+ if (entries.length === 0) {
29
+ throw new Error('No ENS records to update')
30
+ }
31
+ const calls: Hex[] = entries.map(([key, value]) => encodeFunctionData({
32
+ abi: RESOLVER_ABI,
33
+ functionName: 'setText',
34
+ args: [node, key, value],
35
+ }))
36
+ if (calls.length === 1) {
37
+ return { resolverAddress: resolver, data: calls[0]!, multicall: false, calls }
38
+ }
39
+ const data = encodeFunctionData({
40
+ abi: RESOLVER_ABI,
41
+ functionName: 'multicall',
42
+ args: [calls],
43
+ })
44
+ return { resolverAddress: resolver, data, multicall: true, calls }
45
+ }
@@ -0,0 +1,75 @@
1
+ import { getAddress, namehash, type Address } from 'viem'
2
+ import type { DiscoverOptions } from './types.js'
3
+ import { ENS_REGISTRY_ABI, ENS_REGISTRY_ADDRESS_MAINNET, RESOLVER_ABI, RPC_TIMEOUT_MS, ZERO_ADDRESS } from './constants.js'
4
+ import { createMainnetClient, withDeadline } from './client.js'
5
+ import { isEthDomain, normalizeEthDomain } from './names.js'
6
+
7
+ export async function resolveEnsAddress(name: string, opts: DiscoverOptions = {}): Promise<Address | null> {
8
+ const trimmed = normalizeEthDomain(name)
9
+ if (!isEthDomain(trimmed)) return null
10
+ const client = opts.publicClient ?? createMainnetClient()
11
+ try {
12
+ const addr = await withDeadline(
13
+ client.getEnsAddress({ name: trimmed }),
14
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
15
+ 'getEnsAddress',
16
+ opts.signal,
17
+ )
18
+ return addr ? getAddress(addr) : null
19
+ } catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ export async function readResolverAddress(fullName: string, opts: DiscoverOptions = {}): Promise<Address | null> {
25
+ if (!isEthDomain(fullName)) return null
26
+ const client = opts.publicClient ?? createMainnetClient()
27
+ try {
28
+ const node = namehash(fullName)
29
+ const resolver = await withDeadline(
30
+ client.readContract({
31
+ address: ENS_REGISTRY_ADDRESS_MAINNET,
32
+ abi: ENS_REGISTRY_ABI,
33
+ functionName: 'resolver',
34
+ args: [node],
35
+ }),
36
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
37
+ 'registry.resolver',
38
+ opts.signal,
39
+ ) as Address
40
+ return resolver === ZERO_ADDRESS ? null : resolver
41
+ } catch {
42
+ return null
43
+ }
44
+ }
45
+
46
+ export async function readEthagentTextRecords(
47
+ fullName: string,
48
+ keys: readonly string[],
49
+ opts: DiscoverOptions = {},
50
+ ): Promise<Record<string, string>> {
51
+ const out: Record<string, string> = {}
52
+ if (!isEthDomain(fullName)) return out
53
+ const resolver = await readResolverAddress(fullName, opts)
54
+ if (!resolver) return out
55
+ const client = opts.publicClient ?? createMainnetClient()
56
+ const node = namehash(fullName)
57
+ for (const key of keys) {
58
+ try {
59
+ const value = await withDeadline(
60
+ client.readContract({
61
+ address: resolver,
62
+ abi: RESOLVER_ABI,
63
+ functionName: 'text',
64
+ args: [node, key],
65
+ }),
66
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
67
+ `resolver.text(${key})`,
68
+ opts.signal,
69
+ ) as string
70
+ if (value) out[key] = value
71
+ } catch {
72
+ }
73
+ }
74
+ return out
75
+ }
@@ -0,0 +1,17 @@
1
+ import { getAddress, isAddress } from 'viem'
2
+ import type { EnsAgentTokenReference } from './types.js'
3
+
4
+ export function parseAgentTokenReference(value: string): Omit<EnsAgentTokenReference, 'node' | 'resolverAddress'> | null {
5
+ const match = value.trim().match(/^eip155:(\d+):(0x[0-9a-fA-F]{40}):(\d+)$/)
6
+ if (!match) return null
7
+ const chainId = Number(match[1])
8
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) return null
9
+ const registry = match[2]
10
+ const tokenId = match[3]
11
+ if (!registry || !isAddress(registry, { strict: false }) || !tokenId) return null
12
+ return {
13
+ chainId,
14
+ identityRegistryAddress: getAddress(registry),
15
+ agentId: BigInt(tokenId),
16
+ }
17
+ }
@@ -0,0 +1,38 @@
1
+ import type { Address, Hex, PublicClient } from 'viem'
2
+
3
+ export type EnsValidation =
4
+ | { ok: true; resolvedAddress: Address; resolverAddress: Address }
5
+ | {
6
+ ok: false
7
+ reason:
8
+ | 'no-owner'
9
+ | 'no-resolver'
10
+ | 'address-mismatch'
11
+ | 'lookup-failed'
12
+ | 'token-owner-mismatch'
13
+ | 'token-owner-lookup-failed'
14
+ detail?: string
15
+ }
16
+
17
+ export type DiscoverOptions = {
18
+ signal?: AbortSignal
19
+ budgetMs?: number
20
+ rpcTimeoutMs?: number
21
+ scanWindowBlocks?: bigint
22
+ publicClient?: PublicClient
23
+ }
24
+
25
+ export type EnsNameDiscoveryResult = {
26
+ status: 'ok' | 'partial' | 'error'
27
+ names: string[]
28
+ sourcesChecked: string[]
29
+ errors: string[]
30
+ }
31
+
32
+ export type EnsAgentTokenReference = {
33
+ chainId: number
34
+ identityRegistryAddress: Address
35
+ agentId: bigint
36
+ node: Hex
37
+ resolverAddress: Address
38
+ }
@@ -0,0 +1,72 @@
1
+ import { getAddress, namehash, type Address } from 'viem'
2
+ import type { DiscoverOptions, EnsValidation } from './types.js'
3
+ import { ENS_REGISTRY_ABI, ENS_REGISTRY_ADDRESS_MAINNET, RPC_TIMEOUT_MS, ZERO_ADDRESS } from './constants.js'
4
+ import { createMainnetClient, withDeadline } from './client.js'
5
+ import { isEthDomain, splitSubdomainName } from './names.js'
6
+
7
+ export async function validateAgentEnsLink(
8
+ fullName: string,
9
+ expectedOwner: Address,
10
+ opts: DiscoverOptions = {},
11
+ ): Promise<EnsValidation> {
12
+ if (!isEthDomain(fullName)) {
13
+ return { ok: false, reason: 'lookup-failed', detail: 'not a valid .eth name' }
14
+ }
15
+ if (!splitSubdomainName(fullName)) {
16
+ return { ok: false, reason: 'lookup-failed', detail: 'agent ENS name must be a subdomain, not a root .eth name' }
17
+ }
18
+ const client = opts.publicClient ?? createMainnetClient()
19
+ const node = namehash(fullName)
20
+ let resolver: Address
21
+ try {
22
+ const owner = await withDeadline(
23
+ client.readContract({
24
+ address: ENS_REGISTRY_ADDRESS_MAINNET,
25
+ abi: ENS_REGISTRY_ABI,
26
+ functionName: 'owner',
27
+ args: [node],
28
+ }),
29
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
30
+ 'registry.owner',
31
+ opts.signal,
32
+ ) as Address
33
+ if (owner === ZERO_ADDRESS) {
34
+ return { ok: false, reason: 'no-owner', detail: 'name does not exist on ENS' }
35
+ }
36
+ resolver = await withDeadline(
37
+ client.readContract({
38
+ address: ENS_REGISTRY_ADDRESS_MAINNET,
39
+ abi: ENS_REGISTRY_ABI,
40
+ functionName: 'resolver',
41
+ args: [node],
42
+ }),
43
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
44
+ 'registry.resolver',
45
+ opts.signal,
46
+ ) as Address
47
+ if (resolver === ZERO_ADDRESS) {
48
+ return { ok: false, reason: 'no-resolver', detail: 'name has no resolver set' }
49
+ }
50
+ } catch (err) {
51
+ return { ok: false, reason: 'lookup-failed', detail: err instanceof Error ? err.message : String(err) }
52
+ }
53
+ let resolved: Address | null
54
+ try {
55
+ resolved = await withDeadline(
56
+ client.getEnsAddress({ name: fullName }),
57
+ opts.rpcTimeoutMs ?? RPC_TIMEOUT_MS,
58
+ 'getEnsAddress',
59
+ opts.signal,
60
+ )
61
+ } catch (err) {
62
+ return { ok: false, reason: 'lookup-failed', detail: err instanceof Error ? err.message : String(err) }
63
+ }
64
+ if (!resolved) {
65
+ return { ok: false, reason: 'address-mismatch', detail: 'name resolves to no address' }
66
+ }
67
+ const checksumResolved = getAddress(resolved)
68
+ if (checksumResolved.toLowerCase() !== expectedOwner.toLowerCase()) {
69
+ return { ok: false, reason: 'address-mismatch', detail: `name resolves to ${checksumResolved}` }
70
+ }
71
+ return { ok: true, resolvedAddress: checksumResolved, resolverAddress: getAddress(resolver) }
72
+ }
@@ -0,0 +1,19 @@
1
+ export type {
2
+ DiscoverOptions,
3
+ EnsAgentTokenReference,
4
+ EnsNameDiscoveryResult,
5
+ EnsValidation,
6
+ } from './ensLookup/types.js'
7
+ export type { EncodedEnsRecordTransaction } from './ensLookup/records.js'
8
+ export {
9
+ isEthDomain,
10
+ normalizeEthDomain,
11
+ sanitizeSubdomainPrefix,
12
+ splitSubdomainName,
13
+ } from './ensLookup/names.js'
14
+ export { createMainnetClient } from './ensLookup/client.js'
15
+ export { resolveEnsAddress, readEthagentTextRecords, readResolverAddress } from './ensLookup/resolve.js'
16
+ export { parseAgentTokenReference } from './ensLookup/tokenReference.js'
17
+ export { discoverOwnedEnsNameDetails, discoverOwnedEnsNames } from './ensLookup/discovery.js'
18
+ export { validateAgentEnsLink } from './ensLookup/validation.js'
19
+ export { encodeSetEthagentTextRecords } from './ensLookup/records.js'
@@ -0,0 +1,199 @@
1
+ import {
2
+ encodeAbiParameters,
3
+ encodeFunctionData,
4
+ getAddress,
5
+ keccak256,
6
+ namehash,
7
+ parseAbi,
8
+ type Address,
9
+ type Hex,
10
+ type PublicClient,
11
+ } from 'viem'
12
+
13
+ export const ETH_REGISTRAR_CONTROLLER_MAINNET = '0x253553366Da8546fC250F225fe3d25d0C782303b' as Address
14
+ export const PUBLIC_RESOLVER_MAINNET = '0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63' as Address
15
+ export const MIN_COMMIT_AGE_SECONDS = 60
16
+ export const MAX_COMMIT_AGE_SECONDS = 86_400
17
+ export const ONE_YEAR_SECONDS = 31_557_600
18
+ export const REGISTER_VALUE_BUFFER_BIPS = 500n
19
+
20
+ const CONTROLLER_ABI = parseAbi([
21
+ 'function available(string name) view returns (bool)',
22
+ 'function rentPrice(string name, uint256 duration) view returns (uint256 base, uint256 premium)',
23
+ 'function commit(bytes32 commitment)',
24
+ 'function register(string name, address owner, uint256 duration, bytes32 secret, address resolver, bytes[] data, bool reverseRecord, uint16 ownerControlledFuses) payable',
25
+ ])
26
+
27
+ const RESOLVER_ABI = parseAbi([
28
+ 'function setAddr(bytes32 node, address a)',
29
+ ])
30
+
31
+ export type RegistrableNameValidation =
32
+ | { ok: true; label: string }
33
+ | { ok: false; reason: 'too-short' | 'too-long' | 'invalid-characters' | 'leading-hyphen' | 'trailing-hyphen' | 'contains-dot'; detail: string }
34
+
35
+ export function validateRegistrableName(value: string): RegistrableNameValidation {
36
+ const label = value.trim().toLowerCase()
37
+ if (label.includes('.')) {
38
+ return { ok: false, reason: 'contains-dot', detail: 'Enter only the label, not a full .eth name.' }
39
+ }
40
+ if (label.length < 3) {
41
+ return { ok: false, reason: 'too-short', detail: 'Names must be at least 3 characters.' }
42
+ }
43
+ if (label.length > 64) {
44
+ return { ok: false, reason: 'too-long', detail: 'Names must be 64 characters or fewer.' }
45
+ }
46
+ if (label.startsWith('-')) {
47
+ return { ok: false, reason: 'leading-hyphen', detail: 'Names cannot start with a hyphen.' }
48
+ }
49
+ if (label.endsWith('-')) {
50
+ return { ok: false, reason: 'trailing-hyphen', detail: 'Names cannot end with a hyphen.' }
51
+ }
52
+ if (!/^[a-z0-9-]+$/.test(label)) {
53
+ return { ok: false, reason: 'invalid-characters', detail: 'Names use lowercase letters, numbers, and hyphens only.' }
54
+ }
55
+ return { ok: true, label }
56
+ }
57
+
58
+ type RentPrice = {
59
+ base: bigint
60
+ premium: bigint
61
+ total: bigint
62
+ }
63
+
64
+ export type EnsRegistrationReadClient = Pick<PublicClient, 'readContract'>
65
+
66
+ export async function readRentPrice(
67
+ client: EnsRegistrationReadClient,
68
+ label: string,
69
+ durationSeconds: bigint,
70
+ ): Promise<RentPrice> {
71
+ const result = await client.readContract({
72
+ address: ETH_REGISTRAR_CONTROLLER_MAINNET,
73
+ abi: CONTROLLER_ABI,
74
+ functionName: 'rentPrice',
75
+ args: [label, durationSeconds],
76
+ }) as readonly [bigint, bigint]
77
+ const [base, premium] = result
78
+ return { base, premium, total: base + premium }
79
+ }
80
+
81
+ export async function readNameAvailable(
82
+ client: EnsRegistrationReadClient,
83
+ label: string,
84
+ ): Promise<boolean> {
85
+ return await client.readContract({
86
+ address: ETH_REGISTRAR_CONTROLLER_MAINNET,
87
+ abi: CONTROLLER_ABI,
88
+ functionName: 'available',
89
+ args: [label],
90
+ }) as boolean
91
+ }
92
+
93
+ type CommitmentArgs = {
94
+ label: string
95
+ owner: Address
96
+ durationSeconds: bigint
97
+ secret: Hex
98
+ resolver?: Address
99
+ setAddrToOwner?: boolean
100
+ reverseRecord?: boolean
101
+ ownerControlledFuses?: number
102
+ }
103
+
104
+ type CommitmentBuild = {
105
+ commitment: Hex
106
+ resolver: Address
107
+ data: Hex[]
108
+ reverseRecord: boolean
109
+ ownerControlledFuses: number
110
+ }
111
+
112
+ export function buildCommitment(args: CommitmentArgs): CommitmentBuild {
113
+ const owner = getAddress(args.owner)
114
+ const resolver = getAddress(args.resolver ?? PUBLIC_RESOLVER_MAINNET)
115
+ const reverseRecord = args.reverseRecord ?? false
116
+ const ownerControlledFuses = args.ownerControlledFuses ?? 0
117
+ const data: Hex[] = []
118
+ if (args.setAddrToOwner ?? true) {
119
+ data.push(encodeFunctionData({
120
+ abi: RESOLVER_ABI,
121
+ functionName: 'setAddr',
122
+ args: [namehash(`${args.label}.eth`), owner],
123
+ }))
124
+ }
125
+ const commitment = keccak256(encodeAbiParameters(
126
+ [
127
+ { type: 'string' },
128
+ { type: 'address' },
129
+ { type: 'uint256' },
130
+ { type: 'bytes32' },
131
+ { type: 'address' },
132
+ { type: 'bytes[]' },
133
+ { type: 'bool' },
134
+ { type: 'uint16' },
135
+ ],
136
+ [args.label, owner, args.durationSeconds, args.secret, resolver, data, reverseRecord, ownerControlledFuses],
137
+ ))
138
+ return { commitment, resolver, data, reverseRecord, ownerControlledFuses }
139
+ }
140
+
141
+ export function encodeCommitTransaction(commitment: Hex): { to: Address; data: Hex } {
142
+ return {
143
+ to: ETH_REGISTRAR_CONTROLLER_MAINNET,
144
+ data: encodeFunctionData({
145
+ abi: CONTROLLER_ABI,
146
+ functionName: 'commit',
147
+ args: [commitment],
148
+ }),
149
+ }
150
+ }
151
+
152
+ type RegisterTransactionArgs = {
153
+ label: string
154
+ owner: Address
155
+ durationSeconds: bigint
156
+ secret: Hex
157
+ rentPrice: RentPrice
158
+ resolver?: Address
159
+ setAddrToOwner?: boolean
160
+ reverseRecord?: boolean
161
+ ownerControlledFuses?: number
162
+ }
163
+
164
+ export function encodeRegisterTransaction(args: RegisterTransactionArgs): { to: Address; data: Hex; value: Hex } {
165
+ const owner = getAddress(args.owner)
166
+ const built = buildCommitment({
167
+ label: args.label,
168
+ owner,
169
+ durationSeconds: args.durationSeconds,
170
+ secret: args.secret,
171
+ ...(args.resolver !== undefined ? { resolver: args.resolver } : {}),
172
+ ...(args.setAddrToOwner !== undefined ? { setAddrToOwner: args.setAddrToOwner } : {}),
173
+ ...(args.reverseRecord !== undefined ? { reverseRecord: args.reverseRecord } : {}),
174
+ ...(args.ownerControlledFuses !== undefined ? { ownerControlledFuses: args.ownerControlledFuses } : {}),
175
+ })
176
+ const data = encodeFunctionData({
177
+ abi: CONTROLLER_ABI,
178
+ functionName: 'register',
179
+ args: [args.label, owner, args.durationSeconds, args.secret, built.resolver, built.data, built.reverseRecord, built.ownerControlledFuses],
180
+ })
181
+ const buffered = (args.rentPrice.total * (10000n + REGISTER_VALUE_BUFFER_BIPS)) / 10000n
182
+ return {
183
+ to: ETH_REGISTRAR_CONTROLLER_MAINNET,
184
+ data,
185
+ value: ('0x' + buffered.toString(16)) as Hex,
186
+ }
187
+ }
188
+
189
+ export function generateRegistrationSecret(): Hex {
190
+ const bytes = new Uint8Array(32)
191
+ if (typeof globalThis.crypto?.getRandomValues === 'function') {
192
+ globalThis.crypto.getRandomValues(bytes)
193
+ } else {
194
+ for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256)
195
+ }
196
+ let hex = '0x'
197
+ for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')
198
+ return hex as Hex
199
+ }