Skip to content

Instantly share code, notes, and snippets.

@f1lander
Last active December 4, 2025 19:05
Show Gist options
  • Select an option

  • Save f1lander/7e72e394059bde3f185467ea62cb3287 to your computer and use it in GitHub Desktop.

Select an option

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)
})
// #!/usr/bin / env tsx
/**
* Complete ENS Registration Test Suite with Rhinestone SDK v1.0.2
*
* This single file contains everything needed to test ENS registration:
* 1. Account creation and funding using Rhinestone SDK v1.0.2
* 2. Token minting and balance checking
* 3. Complete ENS registration flow using sponsored intents (not user operations)
* 4. Comprehensive testing and error handling
*
* Usage:
* 1. Set your RHINESTONE_API_KEY and PRIVATE_KEY environment variables
* 3. Run: npx tsx complete-ens-test.ts
*
* Features:
* - Uses Rhinestone SDK v1.0.2 with sponsored intents (intent-based transactions)
* - Uses FastTestETHRegistrar (no commit time)
* - Supports ERC20 token payments (USDC/DAI)
* - Automatic account funding with balance checking
* - Complete error handling and helpful messages
* - Uses sponsored transactions (gas paid by sponsor, not user operations)
* - Ready for integration into your app
*/
import { RhinestoneSDK } from '@rhinestone/sdk'
import {
createPublicClient,
createWalletClient,
encodeFunctionData,
formatUnits,
http,
keccak256,
parseUnits,
toHex,
zeroAddress,
zeroHash,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
// ============================================================================
// CONFIGURATION
// ============================================================================
// ENS Sepolia contract addresses (using FastTestETHRegistrar for 0 commitment time)
// Addresses match packages/transaction-manager/src/contracts/ens-sepolia.ts
const ENS_CONTRACTS = {
REGISTRY: '0x0f3eb298470639a96bd548cea4a648bc80b2cee2' as `0x${string}`, // ETHRegistry
REGISTRAR_CONTROLLER:
'0x72e64d2c221f42df55745e89c394d2db4f3b48f2' as `0x${string}`, // FastTestETHRegistrar
PUBLIC_RESOLVER:
'0x47c4055131c6fbeedb1357b6f4c7bf415d6c4b71' as `0x${string}`, // DedicatedResolverImpl
}
// Supported payment tokens on Sepolia ENS
const SUPPORTED_TOKENS = {
USDC: '0x9028ab8e872af36c30c959a105cb86d1038412ae' as `0x${string}`, // MockUSDC
DAI: '0x6630589c2e6364a96bb7acf0d9d64ac9c1dd3528' as `0x${string}`, // MockDAI
}
const PIMLICO_API_KEY = ''
const RHINESTONE_API_KEY = ''
const PRIVATE_KEY = '' as `0x${string}`
const SEPOLIA_RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com'
// FastTestETHRegistrar ABI (key functions) testname6208
const FAST_TEST_REGISTRAR_ABI = [
{
inputs: [{ name: 'commitment', type: 'bytes32' }],
name: 'commit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'commitment', type: 'bytes32' }],
name: 'commitmentAt',
outputs: [{ name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'name', type: 'string' },
{ name: 'owner', type: 'address' },
{ name: 'secret', type: 'bytes32' },
{ name: 'subregistry', type: 'address' },
{ name: 'resolver', type: 'address' },
{ name: 'duration', type: 'uint64' },
{ name: 'paymentToken', type: 'address' },
{ name: 'referrer', type: 'bytes32' },
],
name: 'register',
outputs: [{ name: 'tokenId', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'name', type: 'string' },
{ name: 'owner', type: 'address' },
{ name: 'secret', type: 'bytes32' },
{ name: 'subregistry', type: 'address' },
{ name: 'resolver', type: 'address' },
{ name: 'duration', type: 'uint64' },
{ name: 'referrer', type: 'bytes32' },
],
name: 'makeCommitment',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [],
name: 'MIN_COMMITMENT_AGE',
outputs: [{ name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'maxCommitmentAge',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'name', type: 'string' }],
name: 'isAvailable',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'name', type: 'string' },
{ name: 'owner', type: 'address' },
{ name: 'duration', type: 'uint64' },
{ name: 'paymentToken', type: 'address' },
],
name: 'rentPrice',
outputs: [
{ name: 'base', type: 'uint256' },
{ name: 'premium', type: 'uint256' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'token', type: 'address' }],
name: 'isPaymentToken',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
] as const
// ERC20 ABI for token interactions
const ERC20_ABI = [
{
inputs: [{ name: '_owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: 'balance', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
],
name: 'allowance',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'decimals',
outputs: [{ name: '', type: 'uint8' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'symbol',
outputs: [{ name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
name: 'approve',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
name: 'mint',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const
// Create clients
const publicClient = createPublicClient({
chain: sepolia,
transport: http(SEPOLIA_RPC_URL),
})
const walletClient = createWalletClient({
chain: sepolia,
transport: http(SEPOLIA_RPC_URL),
})
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Check ETH balance of an address
*/
async function checkEthBalance(address: `0x${string}`): Promise<bigint> {
const balance = await publicClient.getBalance({ address })
console.log(`πŸ’° ETH Balance: ${formatUnits(balance, 18)} ETH`)
return balance
}
/**
* Check token balance of an address
*/
async function checkTokenBalance(
tokenAddress: `0x${string}`,
ownerAddress: `0x${string}`,
tokenName: string,
): Promise<bigint> {
try {
const balance = await publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [ownerAddress],
})
const decimals = await publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'decimals',
})
console.log(
`πŸ’° ${tokenName} Balance: ${formatUnits(balance, decimals)} ${tokenName}`,
)
return balance
} catch (error) {
console.log(`❌ Failed to check ${tokenName} balance:`, error)
return 0n
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* Send ETH to an address
*/
async function sendEth(
fromAccount: any,
toAddress: `0x${string}`,
amountEth: string,
): Promise<string> {
const amountWei = parseUnits(amountEth, 18)
console.log(`πŸ“€ Sending ${amountEth} ETH to ${toAddress}...`)
const hash = await walletClient.sendTransaction({
account: fromAccount,
to: toAddress,
value: amountWei,
})
console.log(`βœ… ETH transaction sent: ${hash}`)
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log(`βœ… ETH transaction confirmed in block ${receipt.blockNumber}`)
return hash
}
/**
* Mint tokens to an address
*/
async function mintTokens(
fromAccount: any,
tokenAddress: `0x${string}`,
toAddress: `0x${string}`,
amount: string,
tokenName: string,
): Promise<string> {
try {
// Get token decimals
const decimals = await publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'decimals',
})
const amountWei = parseUnits(amount, decimals)
console.log(`πŸ“€ Minting ${amount} ${tokenName} to ${toAddress}...`)
const hash = await walletClient.sendTransaction({
account: fromAccount,
to: tokenAddress,
data: encodeFunctionData({
abi: ERC20_ABI,
functionName: 'mint',
args: [toAddress, amountWei],
}),
})
console.log(`βœ… ${tokenName} mint transaction sent: ${hash}`)
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log(
`βœ… ${tokenName} mint transaction confirmed in block ${receipt.blockNumber}`,
)
return hash
} catch (error) {
console.error(`❌ Failed to mint ${tokenName}:`, error)
throw error
}
}
// ============================================================================
// RHINESTONE ACCOUNT MANAGEMENT
// ============================================================================
/**
* Create Rhinestone account and get its address using SDK v1.0.2
*/
async function createRhinestoneAccountAndGetAddress(): Promise<string> {
console.log('🦏 Creating Rhinestone account...')
if (!RHINESTONE_API_KEY || !PRIVATE_KEY) {
throw new Error(
'❌ RHINESTONE_API_KEY and PRIVATE_KEY environment variables are required',
)
}
const ownerAccount = privateKeyToAccount(PRIVATE_KEY)
console.log('πŸ‘€ EOA Account:', ownerAccount.address)
try {
console.log('πŸ”§ Initializing Rhinestone SDK...')
// Initialize SDK instance with Pimlico bundler
const sdk = new RhinestoneSDK({
apiKey: RHINESTONE_API_KEY,
bundler: {
type: 'pimlico',
apiKey: PIMLICO_API_KEY,
},
})
console.log('βœ… SDK initialized successfully')
// Create Rhinestone account using the SDK
const rhinestoneAccount = await sdk.createAccount({
owners: {
type: 'ecdsa',
accounts: [ownerAccount],
},
})
console.log('🦏 Rhinestone account object received:', rhinestoneAccount)
// Validate the account was created properly
if (!rhinestoneAccount.getAddress || !rhinestoneAccount.sendTransaction) {
throw new Error(
'❌ Rhinestone account creation failed.\n\n' +
'The account object was created but is missing required methods.\n' +
'This indicates the API key is invalid or account creation failed.\n\n' +
'Please verify your API key and try again.',
)
}
const smartAccountAddress = rhinestoneAccount.getAddress()
if (!smartAccountAddress) {
throw new Error(
'❌ Failed to get account address.\n\n' +
'Account creation appeared to succeed but returned no address.\n' +
'This usually indicates an API key or configuration issue.',
)
}
console.log('βœ… Rhinestone account created successfully!')
console.log('πŸ“ Account address:', smartAccountAddress)
console.log(
'πŸ—οΈ Account type:',
rhinestoneAccount.config?.account?.type || 'default',
)
console.log('πŸ’‘ Make sure to fund THIS address with Sepolia ETH')
return smartAccountAddress
} catch (error) {
console.error('Failed to create Rhinestone account:', error)
if (error instanceof Error) {
if (
error.message.includes('Invalid API key') ||
error.message.includes('401') ||
error.message.includes('authentication')
) {
throw new Error(
'❌ INVALID RHINESTONE API KEY\n\n' +
"The API key you're using is invalid or expired.\n\n" +
'To fix this:\n' +
'1. Get a valid API key from: https://docs.rhinestone.dev\n' +
'2. Set it in environment: RHINESTONE_API_KEY=your_key\n' +
'3. Make sure it starts with "rs_"\n\n' +
'Note: Without a valid API key, you CANNOT use Rhinestone SDK.',
)
}
}
throw error
}
}
/**
* Fund Rhinestone account with ETH and tokens
*/
async function fundRhinestoneAccount(smartAccountAddress: string) {
console.log('πŸ’° Funding Rhinestone account...')
if (!PRIVATE_KEY) {
throw new Error('❌ PRIVATE_KEY environment variable is required')
}
// Create account from private key
const account = privateKeyToAccount(PRIVATE_KEY)
// Check EOA ETH balance first
console.log('πŸ” Checking EOA ETH balance...')
const eoaEthBalance = await checkEthBalance(account.address)
if (eoaEthBalance < parseUnits('0.01', 18)) {
console.log(
'⚠️ EOA has low ETH balance. You may need to fund it from a faucet.',
)
console.log(' Sepolia Faucet: https://sepoliafaucet.com/')
console.log('')
}
// Check Rhinestone account balances
console.log('πŸ” Checking Rhinestone account balances...')
const rhinestoneEthBalance = await checkEthBalance(
smartAccountAddress as `0x${string}`,
)
const rhinestoneUsdcBalance = await checkTokenBalance(
SUPPORTED_TOKENS.USDC,
smartAccountAddress as `0x${string}`,
'USDC',
)
const rhinestoneDaiBalance = await checkTokenBalance(
SUPPORTED_TOKENS.DAI,
smartAccountAddress as `0x${string}`,
'DAI',
)
console.log('')
// Fund with ETH if needed (minimum 0.01 ETH for gas)
const minEthRequired = parseUnits('0.01', 18)
if (rhinestoneEthBalance < minEthRequired) {
console.log('πŸ’° Funding Rhinestone account with ETH...')
await sendEth(account, smartAccountAddress as `0x${string}`, '0.01')
console.log('')
} else {
console.log('βœ… Rhinestone account has sufficient ETH balance')
}
// Fund with USDC if needed (minimum 100 USDC for testing)
const minUsdcRequired = parseUnits('100', 6) // USDC has 6 decimals
if (rhinestoneUsdcBalance < minUsdcRequired) {
console.log('πŸ’° Funding Rhinestone account with USDC...')
await mintTokens(
account,
SUPPORTED_TOKENS.USDC,
smartAccountAddress as `0x${string}`,
'1000',
'USDC',
)
console.log('')
} else {
console.log('βœ… Rhinestone account has sufficient USDC balance')
}
// Fund with DAI if needed (minimum 100 DAI for testing)
const minDaiRequired = parseUnits('100', 18) // DAI has 18 decimals
if (rhinestoneDaiBalance < minDaiRequired) {
console.log('πŸ’° Funding Rhinestone account with DAI...')
await mintTokens(
account,
SUPPORTED_TOKENS.DAI,
smartAccountAddress as `0x${string}`,
'1000',
'DAI',
)
console.log('')
} else {
console.log('βœ… Rhinestone account has sufficient DAI balance')
}
// Final balance check
console.log('πŸŽ‰ Funding Complete! Final balances:')
console.log('=====================================')
await checkEthBalance(smartAccountAddress as `0x${string}`)
await checkTokenBalance(
SUPPORTED_TOKENS.USDC,
smartAccountAddress as `0x${string}`,
'USDC',
)
await checkTokenBalance(
SUPPORTED_TOKENS.DAI,
smartAccountAddress as `0x${string}`,
'DAI',
)
}
// ============================================================================
// RHINESTONE INTENT-BASED TRANSACTION HELPER
// ============================================================================
/**
* Submit a sponsored transaction using Rhinestone intents
* This uses the intent-based API instead of user operations
*/
async function submitSponsoredTransaction(
rhinestoneAccount: any,
chain: any,
calls: Array<{ to: `0x${string}`; data: `0x${string}`; value: bigint }>,
): Promise<string | null> {
console.log('πŸ’° Preparing sponsored transaction (intent-based)...', {
chain: chain.name,
chainId: chain.id,
callCount: calls.length,
})
// Step 1: Prepare the transaction (creates intent with sponsorship)
const transactionData = await rhinestoneAccount.prepareTransaction({
sourceChains: [chain],
targetChain: chain,
calls: calls,
sponsored: true,
})
console.log('πŸ” Prepared Transaction Data:', {
intentRoute: transactionData.intentRoute,
transaction: transactionData.transaction,
})
// Step 2: Sign the prepared transaction (includes intentRoute)
const signedTransaction = await rhinestoneAccount.signTransaction(transactionData)
console.log('βœ… Transaction signed:', {
signedTransaction,
})
// Step 3: Submit the signed transaction with authorizations
const transactionResult = await rhinestoneAccount.submitTransaction(
signedTransaction,
[],
)
console.log('βœ… Transaction submitted:', transactionResult)
// Extract transaction hash/ID from result
// TransactionResult has type 'intent' with id property
const txHash = transactionResult?.hash || transactionResult?.id
if (!txHash) {
throw new Error('No transaction hash or ID returned from Rhinestone SDK')
}
// Convert bigint to hex string if needed
const hashAsHex =
typeof txHash === 'bigint'
? (`0x${txHash.toString(16).padStart(64, '0')}` as `0x${string}`)
: (txHash as string)
console.log('βœ… Transaction hash/id:', hashAsHex)
return hashAsHex
}
// ============================================================================
// ENS REGISTRATION CORE FUNCTION
// ============================================================================
/**
* Register an ENS domain using Rhinestone smart account with FastTestETHRegistrar
* This version uses ERC20 tokens for payment and has no commit time requirement
*/
async function registerEnsDomain(
ensName: string,
duration: number = 1, // years
paymentToken: `0x${string}` = SUPPORTED_TOKENS.USDC,
options: {
rhinestoneApiKey?: string
privateKey?: `0x${string}`
rpcUrl?: string
} = {},
) {
console.log(
'πŸ” Starting ENS Registration with Rhinestone (FastTestETHRegistrar)...\n',
)
// Configuration
const apiKey = options.rhinestoneApiKey || RHINESTONE_API_KEY
const privateKey = options.privateKey || PRIVATE_KEY
const rpcUrl = options.rpcUrl || SEPOLIA_RPC_URL
if (!apiKey || !privateKey) {
throw new Error('❌ RHINESTONE_API_KEY and PRIVATE_KEY are required')
}
const durationInSeconds = BigInt(duration * 365 * 24 * 60 * 60) // Convert years to seconds
const cleanName = ensName.replace('.eth', '')
console.log('πŸ“ Configuration:')
console.log(' ENS Name:', `${cleanName}.eth`)
console.log(' Duration:', duration, 'year(s)')
console.log(' Payment Token:', paymentToken)
console.log('')
// Step 1: Create EOA from private key
const ownerAccount = privateKeyToAccount(privateKey)
console.log('βœ… EOA account:', ownerAccount.address)
console.log('πŸ” Working Snippet Account Inspection:')
console.log(' - ownerAccount:', ownerAccount)
console.log(' - ownerAccount.type:', ownerAccount.type)
console.log(' - ownerAccount.source:', ownerAccount.source)
console.log(' - ownerAccount.address:', ownerAccount.address)
console.log(' - ownerAccount.keys:', Object.keys(ownerAccount))
// Step 2: Create public client
const publicClient = createPublicClient({
chain: sepolia,
transport: http(rpcUrl),
})
// Step 3: Check if name is available
console.log('\nπŸ” Checking name availability...')
const isAvailable = await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'isAvailable',
args: [cleanName],
})
if (!isAvailable) {
throw new Error('❌ Name is not available. Try a different name.')
}
console.log('βœ… Name is available!')
// Step 4: Check if payment token is supported
console.log('\nπŸ” Checking payment token support...')
const isTokenSupported = await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'isPaymentToken',
args: [paymentToken],
})
if (!isTokenSupported) {
throw new Error('❌ Payment token is not supported by the registrar.')
}
console.log('βœ… Payment token is supported!')
// Step 5: Get pricing
console.log('\nπŸ’° Getting price...')
const priceResult = await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'rentPrice',
args: [cleanName, ownerAccount.address, durationInSeconds, paymentToken],
})
const priceArray = priceResult as [bigint, bigint]
const basePrice = priceArray[0]
const premium = priceArray[1]
const totalPrice = basePrice + premium
console.log(' Base:', basePrice.toString())
console.log(' Premium:', premium.toString())
console.log(' Total:', totalPrice.toString())
// Step 6: Create Rhinestone account
console.log('\n🦏 Creating Rhinestone account...')
// Initialize SDK instance with Pimlico bundler
const sdk = new RhinestoneSDK({
apiKey: apiKey,
})
// Create Rhinestone account using the SDK
const rhinestoneAccount = await sdk.createAccount({
owners: {
type: 'ecdsa',
accounts: [ownerAccount],
},
})
console.log(
'🦏 Working Snippet Rhinestone account object received:',
rhinestoneAccount,
)
console.log('πŸ” Working Snippet RhinestoneAccount Inspection:')
console.log(' - rhinestoneAccount:', rhinestoneAccount)
console.log(' - rhinestoneAccount.keys:', Object.keys(rhinestoneAccount))
console.log(' - rhinestoneAccount.config:', rhinestoneAccount.config)
console.log(
' - rhinestoneAccount.config?.account:',
rhinestoneAccount.config?.account,
)
console.log(
' - rhinestoneAccount.config?.account?.type:',
rhinestoneAccount.config?.account?.type,
)
const smartAccountAddress = rhinestoneAccount.getAddress()
console.log('βœ… Smart account address:', smartAccountAddress)
// Step 7: Check token balance
console.log('\nπŸ’° Checking token balance...')
const tokenBalance = await publicClient.readContract({
address: paymentToken,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [smartAccountAddress],
})
// Get token decimals for proper formatting
const tokenDecimals = await publicClient.readContract({
address: paymentToken,
abi: ERC20_ABI,
functionName: 'decimals',
})
const tokenSymbol = await publicClient.readContract({
address: paymentToken,
abi: ERC20_ABI,
functionName: 'symbol',
})
console.log(
`πŸ’° ${tokenSymbol} balance: ${formatUnits(tokenBalance, tokenDecimals)} ${tokenSymbol}`,
)
console.log(
`πŸ’° Required: ${formatUnits(totalPrice, tokenDecimals)} ${tokenSymbol}`,
)
if (tokenBalance < totalPrice) {
console.log('\nπŸ’‘ Insufficient token balance!')
console.log(' Run: npx tsx complete-ens-test.ts --fund-only')
console.log(
` Or manually mint ${formatUnits(totalPrice, tokenDecimals)} ${tokenSymbol} to ${smartAccountAddress}`,
)
throw new Error(
`❌ Insufficient token balance. Need ${formatUnits(totalPrice, tokenDecimals)} ${tokenSymbol}, have ${formatUnits(tokenBalance, tokenDecimals)} ${tokenSymbol}`,
)
}
// Step 8: Generate secret and create commitment
console.log('\nπŸ” Generating commitment...')
const secret = keccak256(toHex(Math.random().toString()))
console.log(' Secret:', secret)
const commitment = await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'makeCommitment',
args: [
cleanName,
smartAccountAddress,
secret,
ENS_CONTRACTS.REGISTRY,
ENS_CONTRACTS.PUBLIC_RESOLVER,
durationInSeconds,
zeroHash, // referrer
],
})
console.log('βœ… Commitment hash:', commitment)
// Step 9: Commit (first transaction) - FastTestETHRegistrar has 0 commit time
console.log('\nπŸ“€ Step 1: Committing to register...')
const commitData = encodeFunctionData({
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'commit',
args: [commitment],
})
console.log('Commit transaction config:', {
chain: sepolia.name,
chainId: sepolia.id,
to: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
data: commitData,
value: '0',
})
try {
// Use intent-based sponsored transaction
const commitTxHash = await submitSponsoredTransaction(
rhinestoneAccount,
sepolia,
[
{
to: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
data: commitData,
value: 0n,
},
],
)
console.log('βœ… Commit transaction submitted!')
console.log(' Transaction hash:', commitTxHash || 'N/A')
console.log('⏳ Waiting for commit to be processed...')
await sleep(3000) // Wait a bit for the intent to be processed
try {
const minAge = (await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'MIN_COMMITMENT_AGE',
})) as bigint
const committedAt = (await publicClient.readContract({
address: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'commitmentAt',
args: [commitment],
})) as bigint
if (committedAt === 0n) {
console.log('⚠️ Commitment timestamp not yet recorded; waiting 3s...')
await sleep(3000)
}
const latestBlock = await publicClient.getBlock()
const nowTs = latestBlock.timestamp as bigint
const elapsed = nowTs - committedAt
if (minAge > 0n && elapsed < minAge) {
const waitSeconds = Number(minAge - elapsed)
console.log(
`⏳ Waiting ${waitSeconds}s for MIN_COMMITMENT_AGE before registering...`,
)
await sleep(waitSeconds * 1000)
}
} catch (ageErr) {
console.log('ℹ️ Skipping commitment age wait (could not fetch):', ageErr)
}
} 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.',
)
}
throw error
}
// Step 10: Approve token (if not ETH)
console.log('\nπŸ“€ Step 2: Approving token...')
if (paymentToken !== zeroAddress) {
const approveData = encodeFunctionData({
abi: ERC20_ABI,
functionName: 'approve',
args: [ENS_CONTRACTS.REGISTRAR_CONTROLLER, totalPrice],
})
try {
// Use intent-based sponsored transaction
const approveResult = await submitSponsoredTransaction(
rhinestoneAccount,
sepolia,
[
{
to: paymentToken,
data: approveData,
value: 0n,
},
],
)
console.log('βœ… Approve transaction submitted!')
console.log(' Transaction hash:', approveResult || 'N/A')
// Wait a bit for the approval to be processed
console.log('⏳ Waiting for approval to be processed...')
await sleep(2000)
// Verify allowance before proceeding
try {
const currentAllowance = await publicClient.readContract({
address: paymentToken,
abi: ERC20_ABI,
functionName: 'allowance',
args: [smartAccountAddress, ENS_CONTRACTS.REGISTRAR_CONTROLLER],
})
console.log(
`πŸ”Ž Allowance: ${currentAllowance.toString()} (required ${totalPrice.toString()})`,
)
if (currentAllowance < totalPrice) {
console.log('⚠️ Allowance below required amount, re-approving...')
// Re-approve with higher amount
const reApproveData = encodeFunctionData({
abi: ERC20_ABI,
functionName: 'approve',
args: [ENS_CONTRACTS.REGISTRAR_CONTROLLER, totalPrice * 2n], // Approve double the amount
})
await submitSponsoredTransaction(
rhinestoneAccount,
sepolia,
[
{
to: paymentToken,
data: reApproveData,
value: 0n,
},
],
)
console.log('βœ… Re-approval completed!')
}
} catch (e) {
console.log('⚠️ Failed to fetch allowance after approve:', e)
}
} catch (error) {
console.error('\n❌ Approve transaction failed:', error)
throw error
}
} else {
console.log('⚑ Using ETH - no approval needed')
}
// Step 11: Register (third transaction) - No wait time needed with FastTestETHRegistrar
console.log('\nπŸ“€ Step 3: Registering name...')
const registerData = encodeFunctionData({
abi: FAST_TEST_REGISTRAR_ABI,
functionName: 'register',
args: [
cleanName,
smartAccountAddress,
secret,
ENS_CONTRACTS.REGISTRY,
ENS_CONTRACTS.PUBLIC_RESOLVER,
durationInSeconds,
paymentToken,
zeroHash, // referrer
],
})
console.log('πŸ” Register transaction details:')
console.log(' Target contract:', ENS_CONTRACTS.REGISTRAR_CONTROLLER)
console.log(' Function data:', registerData)
console.log(' Payment token:', paymentToken)
console.log(' Duration:', durationInSeconds.toString())
try {
// Preflight simulation against RPC to surface revert reasons early
// try {
// const preflightGas = await publicClient.estimateGas({
// account: smartAccountAddress as `0x${string}`,
// to: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
// data: registerData,
// value: 0n,
// })
// console.log('πŸ”Ž Preflight register estimateGas:', preflightGas.toString())
// } catch (preflightError) {
// console.error('❌ Preflight estimateGas for register failed:', preflightError)
// }
// Use intent-based sponsored transaction
const registerTxHash = await submitSponsoredTransaction(
rhinestoneAccount,
sepolia,
[
{
to: ENS_CONTRACTS.REGISTRAR_CONTROLLER,
data: registerData,
value: 0n, // No ETH value needed when using payment tokens
},
],
)
console.log('βœ… Register transaction submitted!')
console.log(' Transaction hash:', registerTxHash || 'N/A')
console.log('\nπŸŽ‰ SUCCESS! ENS name registered:')
console.log(' Name:', `${cleanName}.eth`)
console.log(' Owner:', smartAccountAddress)
console.log(' Payment Token:', paymentToken)
console.log(' Transaction:', registerTxHash || 'N/A')
return {
name: `${cleanName}.eth`,
owner: smartAccountAddress,
transactionHash: registerTxHash,
price: totalPrice,
paymentToken,
}
} 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.')
}
}
try {
const ctx = (error as any)?._context
if (ctx) {
console.log('πŸ”Ž Orchestrator context id:', ctx.id)
console.dir(ctx.error, { depth: null })
console.log('πŸ”Ž traceId:', ctx.traceId)
}
} catch (_) { }
throw error
}
}
// ============================================================================
// MAIN EXECUTION FUNCTIONS
// ============================================================================
/**
* Complete test setup - creates account, funds it, and tests registration
*/
async function runCompleteTestSetup() {
console.log('πŸš€ Starting Complete ENS Registration Test Setup')
console.log('===============================================')
console.log('')
try {
// Step 1: Create Rhinestone account
console.log('πŸ“‹ Step 1: Creating Rhinestone Account')
console.log('-------------------------------------')
const smartAccountAddress = await createRhinestoneAccountAndGetAddress()
console.log('')
// Step 2: Fund the account
console.log('πŸ“‹ Step 2: Funding Rhinestone Account')
console.log('-------------------------------------')
await fundRhinestoneAccount(smartAccountAddress)
console.log('')
// Step 3: Verify balances
console.log('πŸ“‹ Step 3: Verifying Account Balances')
console.log('-------------------------------------')
await checkEthBalance(smartAccountAddress as `0x${string}`)
await checkTokenBalance(
SUPPORTED_TOKENS.USDC,
smartAccountAddress as `0x${string}`,
'USDC',
)
await checkTokenBalance(
SUPPORTED_TOKENS.DAI,
smartAccountAddress as `0x${string}`,
'DAI',
)
console.log('')
// Step 4: Test ENS registration
console.log('πŸ“‹ Step 4: Testing ENS Registration')
console.log('-----------------------------------')
const registrationSuccess = await testEnsRegistration()
// Final summary
console.log('\nπŸ“Š Test Setup Summary')
console.log('====================')
console.log('βœ… Rhinestone Account Created:', smartAccountAddress)
console.log('βœ… Account Funded with ETH and Tokens')
console.log(
registrationSuccess
? 'βœ… ENS Registration Test Passed'
: '❌ ENS Registration Test Failed',
)
if (registrationSuccess) {
console.log('\nπŸŽ‰ Your ENS registration setup is working perfectly!')
console.log('')
console.log('πŸ“ You can now:')
console.log('1. Use the registerEnsDomain function in your app')
console.log(
'2. Run individual tests with: npx tsx complete-ens-test.ts --test-only',
)
console.log(
'3. Fund additional accounts with: npx tsx complete-ens-test.ts --fund-only',
)
} else {
console.log('\n⚠️ There was an issue with the registration test.')
console.log(' Check the error messages above and try again.')
}
} catch (error) {
console.error('\nπŸ’₯ Test setup failed:', error)
console.error('')
console.error('πŸ” Troubleshooting tips:')
console.error('1. Make sure your RHINESTONE_API_KEY is valid')
console.error('2. Ensure your PRIVATE_KEY has sufficient Sepolia ETH')
console.error('3. Check that the Sepolia RPC endpoint is working')
console.error('4. Verify the contract addresses are correct')
process.exit(1)
}
}
/**
* Test ENS registration with a random domain name
*/
async function testEnsRegistration() {
console.log('\nπŸ§ͺ Testing ENS Registration...')
// Generate a random name to avoid conflicts
try {
const result = await registerEnsDomain(
'ucles11790',
1, // 1 year duration
SUPPORTED_TOKENS.USDC, // Use USDC for payment
{
rhinestoneApiKey: RHINESTONE_API_KEY,
privateKey: PRIVATE_KEY,
},
)
console.log('\nπŸŽ‰ ENS Registration Test SUCCESSFUL!')
console.log('=====================================')
console.log('Name:', result.name)
console.log('Owner:', result.owner)
console.log('Price:', result.price.toString())
console.log('Payment Token:', result.paymentToken)
console.log('Transaction:', result.transactionHash)
return true
} catch (error) {
console.error('\n❌ ENS Registration Test FAILED!')
console.error('================================')
console.error('Error:', error)
return false
}
}
async function main() {
await runCompleteTestSetup()
}
// Run if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error('\nπŸ’₯ Fatal error:', error)
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