Skip to content

Instantly share code, notes, and snippets.

@f1lander
Last active October 2, 2025 21:27
Show Gist options
  • Save f1lander/7e72e394059bde3f185467ea62cb3287 to your computer and use it in GitHub Desktop.
Save f1lander/7e72e394059bde3f185467ea62cb3287 to your computer and use it in GitHub Desktop.
register name on sepolia using rhinestone
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)
})
@AmanRaj1608
Copy link

updated for getting the hash correctly

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,
    account: {
      type: 'safe',
    },
  })

  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: [],
    })
    if (commitTx.type === 'bundle') {
      console.log('βœ… Commit transaction submitted with bundle ID:', commitTx.id.toString())
    } else {
      console.log('βœ… Commit transaction submitted with UserOp hash:', commitTx.hash)
    }
    
    console.log('⏳ Waiting for commit execution...')
    const commitResult = await rhinestoneAccount.waitForExecution(commitTx)
    if ('fillTransactionHash' in commitResult && commitResult.fillTransactionHash) {
      console.log('βœ… Commit transaction confirmed! Hash:', commitResult.fillTransactionHash)
    } else if ('receipt' in commitResult) {
      console.log('βœ… Commit transaction confirmed! Hash:', commitResult.receipt.transactionHash)
    } else {
      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: [],
    })
    if (registerTx.type === 'bundle') {
      console.log('βœ… Register transaction submitted with bundle ID:', registerTx.id.toString())
    } else {
      console.log('βœ… Register transaction submitted with UserOp hash:', registerTx.hash)
    }
    
    console.log('⏳ Waiting for register execution...')
    const result = await rhinestoneAccount.waitForExecution(registerTx)
    
    let txHash: `0x${string}` | undefined = undefined
    if ('fillTransactionHash' in result && result.fillTransactionHash) {
      txHash = result.fillTransactionHash
    } else if ('receipt' in result && result.receipt?.transactionHash) {
      txHash = result.receipt.transactionHash
    }
    
    console.log('βœ… Register transaction confirmed!' + (txHash ? ` Hash: ${txHash}` : ''))
    
    console.log('\nπŸŽ‰ SUCCESS! ENS name registered:')
    console.log('   Name:', ensName + '.eth')
    console.log('   Owner:', smartAccountAddress)
    if (txHash) {
      console.log('   View on Etherscan: https://sepolia.etherscan.io/tx/' + txHash)
    }
  } 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)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment