Last active
October 2, 2025 21:27
-
-
Save f1lander/7e72e394059bde3f185467ea62cb3287 to your computer and use it in GitHub Desktop.
register name on sepolia using rhinestone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createPublicClient, http, parseEther, encodeFunctionData, keccak256, toHex } from 'viem' | |
import { privateKeyToAccount } from 'viem/accounts' | |
import { sepolia } from 'viem/chains' | |
import { createRhinestoneAccount } from '@rhinestone/sdk' | |
const RHINESTONE_API_KEY = '' | |
const OWNER_PRIVATE_KEY = '' as `0x${string}` | |
const chain = sepolia | |
// ENS Contracts on Sepolia | |
const ENS_CONTRACTS = { | |
REGISTRAR_CONTROLLER: '0xfb3cE5D01e0f33f41DbB39035dB9745962F1f968' as `0x${string}`, | |
PUBLIC_RESOLVER: '0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5' as `0x${string}`, | |
} | |
// ENS Registrar Controller ABI (key functions) | |
const ENS_ABI = [ | |
{ | |
inputs: [{ internalType: 'string', name: 'label', type: 'string' }], | |
name: 'available', | |
outputs: [{ internalType: 'bool', name: '', type: 'bool' }], | |
stateMutability: 'view', | |
type: 'function', | |
}, | |
{ | |
inputs: [ | |
{ internalType: 'string', name: 'label', type: 'string' }, | |
{ internalType: 'uint256', name: 'duration', type: 'uint256' }, | |
], | |
name: 'rentPrice', | |
outputs: [ | |
{ | |
components: [ | |
{ internalType: 'uint256', name: 'base', type: 'uint256' }, | |
{ internalType: 'uint256', name: 'premium', type: 'uint256' }, | |
], | |
internalType: 'struct IPriceOracle.Price', | |
name: 'price', | |
type: 'tuple', | |
}, | |
], | |
stateMutability: 'view', | |
type: 'function', | |
}, | |
{ | |
inputs: [ | |
{ | |
components: [ | |
{ internalType: 'string', name: 'label', type: 'string' }, | |
{ internalType: 'address', name: 'owner', type: 'address' }, | |
{ internalType: 'uint256', name: 'duration', type: 'uint256' }, | |
{ internalType: 'bytes32', name: 'secret', type: 'bytes32' }, | |
{ internalType: 'address', name: 'resolver', type: 'address' }, | |
{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }, | |
{ internalType: 'uint8', name: 'reverseRecord', type: 'uint8' }, | |
{ internalType: 'bytes32', name: 'referrer', type: 'bytes32' }, | |
], | |
internalType: 'struct IETHRegistrarController.Registration', | |
name: 'registration', | |
type: 'tuple', | |
}, | |
], | |
name: 'makeCommitment', | |
outputs: [{ internalType: 'bytes32', name: 'commitment', type: 'bytes32' }], | |
stateMutability: 'pure', | |
type: 'function', | |
}, | |
{ | |
inputs: [{ internalType: 'bytes32', name: 'commitment', type: 'bytes32' }], | |
name: 'commit', | |
outputs: [], | |
stateMutability: 'nonpayable', | |
type: 'function', | |
}, | |
{ | |
inputs: [ | |
{ | |
components: [ | |
{ internalType: 'string', name: 'label', type: 'string' }, | |
{ internalType: 'address', name: 'owner', type: 'address' }, | |
{ internalType: 'uint256', name: 'duration', type: 'uint256' }, | |
{ internalType: 'bytes32', name: 'secret', type: 'bytes32' }, | |
{ internalType: 'address', name: 'resolver', type: 'address' }, | |
{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }, | |
{ internalType: 'uint8', name: 'reverseRecord', type: 'uint8' }, | |
{ internalType: 'bytes32', name: 'referrer', type: 'bytes32' }, | |
], | |
internalType: 'struct IETHRegistrarController.Registration', | |
name: 'registration', | |
type: 'tuple', | |
}, | |
], | |
name: 'register', | |
outputs: [], | |
stateMutability: 'payable', | |
type: 'function', | |
}, | |
] as const | |
async function main() { | |
console.log('π Testing ENS Registration with Rhinestone on Sepolia...\n') | |
// Configuration | |
const ensName = 'testname' + Math.floor(Math.random() * 10000) // Random name to avoid conflicts | |
const duration = BigInt(31536000) // 1 year | |
console.log('π Configuration:') | |
console.log(' ENS Name:', ensName + '.eth') | |
console.log(' Duration: 1 year') | |
console.log('') | |
// Step 1: Create EOA from private key | |
const ownerAccount = privateKeyToAccount(OWNER_PRIVATE_KEY) | |
console.log('β EOA account:', ownerAccount.address) | |
// Step 2: Create public client | |
const publicClient = createPublicClient({ | |
chain, | |
transport: http('https://lb.drpc.org/ogrpc?network=sepolia&dkey=AnmpasF2C0JBqeAEzxVO8aRuvzLTrWcR75hmDonbV6cR') | |
}) | |
// Step 3: Check if name is available | |
console.log('\nπ Checking name availability...') | |
const isAvailable = await publicClient.readContract({ | |
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER, | |
abi: ENS_ABI, | |
functionName: 'available', | |
args: [ensName], | |
}) | |
if (!isAvailable) { | |
console.log('β Name is not available. Try a different name.') | |
process.exit(1) | |
} | |
console.log('β Name is available!') | |
// Step 4: Get pricing | |
console.log('\nπ° Getting price...') | |
const priceResult = await publicClient.readContract({ | |
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER, | |
abi: ENS_ABI, | |
functionName: 'rentPrice', | |
args: [ensName, duration], | |
}) | |
const price = priceResult as { base: bigint; premium: bigint } | |
const totalPrice = price.base + price.premium | |
console.log(' Base:', Number(price.base) / 1e18, 'ETH') | |
console.log(' Premium:', Number(price.premium) / 1e18, 'ETH') | |
console.log(' Total:', Number(totalPrice) / 1e18, 'ETH') | |
// Step 5: Create Rhinestone account | |
console.log('\nπ¦ Creating Rhinestone account...') | |
const rhinestoneAccount = await createRhinestoneAccount({ | |
owners: { | |
type: 'ecdsa', | |
accounts: [ownerAccount], | |
}, | |
rhinestoneApiKey: RHINESTONE_API_KEY, | |
}) | |
const smartAccountAddress = rhinestoneAccount.getAddress() | |
console.log('β Smart account address:', smartAccountAddress) | |
// Step 6: Check smart account balance | |
const smartAccountBalance = await publicClient.getBalance({ address: rhinestoneAccount.getAddress() }) | |
console.log('π° Smart account balance:', Number(smartAccountBalance) / 1e18, 'ETH') | |
if (smartAccountBalance === 0n) { | |
console.log('\nβ οΈ WARNING: Smart account has no balance.') | |
console.log(' Fund it at: https://sepolia.etherscan.io/address/' + smartAccountAddress) | |
process.exit(1) | |
} | |
// Step 7: Generate secret and create commitment | |
console.log('\nπ Generating commitment...') | |
const secret = keccak256(toHex(Math.random().toString())) | |
console.log(' Secret:', secret) | |
const registrationParams = { | |
label: ensName, | |
owner: smartAccountAddress, | |
duration: duration, | |
secret: secret, | |
resolver: ENS_CONTRACTS.PUBLIC_RESOLVER, | |
data: [] as `0x${string}`[], | |
reverseRecord: 1, // uint8 for REVERSE_RECORD_ETHEREUM_BIT | |
referrer: '0x0000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, | |
} | |
const commitment = await publicClient.readContract({ | |
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER, | |
abi: ENS_ABI, | |
functionName: 'makeCommitment', | |
args: [registrationParams], | |
}) | |
console.log('β Commitment hash:', commitment) | |
// Step 8: Commit (first transaction) | |
console.log('\nπ€ Step 1: Committing to register...') | |
const commitData = encodeFunctionData({ | |
abi: ENS_ABI, | |
functionName: 'commit', | |
args: [commitment], | |
}) | |
try { | |
const commitTx = await rhinestoneAccount.sendTransaction({ | |
sourceChain: chain, | |
targetChain: chain, | |
calls: [ | |
{ | |
to: ENS_CONTRACTS.REGISTRAR_CONTROLLER, | |
data: commitData, | |
value: 0n, | |
}, | |
], | |
tokenRequests: [], | |
}) | |
console.log('β Commit transaction submitted:', commitTx.fillTransactionHash || commitTx.transaction?.hash) | |
console.log('β³ Waiting for commit execution...') | |
await rhinestoneAccount.waitForExecution(commitTx) | |
console.log('β Commit transaction confirmed!') | |
} catch (error) { | |
console.error('\nβ Commit transaction failed:', error) | |
if (error instanceof Error && error.message.includes('AA13')) { | |
console.log('\nπ‘ AA13 Error detected.') | |
console.log(' This is the gas estimation issue with Rhinestone SDK.') | |
console.log(' The API key may lack proper bundler/paymaster permissions.') | |
} | |
process.exit(1) | |
} | |
// Step 9: Wait 60 seconds (ENS requirement) | |
console.log('\nβ° Waiting 60 seconds (ENS commit-reveal requirement)...') | |
await new Promise(resolve => setTimeout(resolve, 60000)) | |
console.log('β 60 seconds elapsed!') | |
// Step 10: Register (second transaction) | |
console.log('\nπ€ Step 2: Registering name...') | |
const registerData = encodeFunctionData({ | |
abi: ENS_ABI, | |
functionName: 'register', | |
args: [registrationParams], | |
}) | |
try { | |
const registerTx = await rhinestoneAccount.sendTransaction({ | |
sourceChain: chain, | |
targetChain: chain, | |
calls: [ | |
{ | |
to: ENS_CONTRACTS.REGISTRAR_CONTROLLER, | |
data: registerData, | |
value: totalPrice, | |
}, | |
], | |
tokenRequests: [], | |
}) | |
console.log('β Register transaction submitted:', registerTx.fillTransactionHash || registerTx.transaction?.hash) | |
console.log('β³ Waiting for register execution...') | |
const result = await rhinestoneAccount.waitForExecution(registerTx) | |
console.log('β Register transaction confirmed!') | |
console.log('\nπ SUCCESS! ENS name registered:') | |
console.log(' Name:', ensName + '.eth') | |
console.log(' Owner:', smartAccountAddress) | |
console.log(' View on Etherscan: https://sepolia.etherscan.io/tx/' + (result.transactionHash || registerTx.transaction?.hash)) | |
} catch (error) { | |
console.error('\nβ Register transaction failed:', error) | |
if (error instanceof Error) { | |
if (error.message.includes('AA13')) { | |
console.log('\nπ‘ AA13 Error detected.') | |
console.log(' This is the gas estimation issue with Rhinestone SDK.') | |
} | |
if (error.message.includes('CommitmentTooNew')) { | |
console.log('\nπ‘ Commitment is too new. Wait longer before registering.') | |
} | |
if (error.message.includes('InsufficientValue')) { | |
console.log('\nπ‘ Insufficient value sent. Price may have changed.') | |
} | |
} | |
process.exit(1) | |
} | |
} | |
main().catch((err) => { | |
console.error('\nπ₯ Fatal error:', err) | |
process.exit(1) | |
}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
updated for getting the hash correctly