Last active
December 4, 2025 19:05
-
-
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) | |
| }) |
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
| // #!/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) | |
| }) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
updated for getting the hash correctly