ethagent 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +127 -29
- package/package.json +16 -9
- package/src/app/FirstRun.tsx +192 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +43 -18
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +11 -17
- package/src/chat/ConversationStack.tsx +3 -0
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/TranscriptView.tsx +6 -0
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +5 -3
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -815
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +166 -101
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +21 -9
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +3 -3
- package/src/ui/TextInput.tsx +191 -29
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -291
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /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
|
+
}
|