From 98c82bc2b975a6babc8ae0296f48cb47cd391093 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:14:44 +0100 Subject: [PATCH 01/16] added .vscode to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0c2a3e0..280ff42 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ rootstock-wallet.json rootstock-wallet-dev.json -#lock files +#lock files pnpm-lock.yaml bun.lockb # Logs @@ -164,6 +164,7 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +.vscode # yarn v2 From 2aabd5b578b0a66469690678af5bf430b10f050a Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:18:20 +0100 Subject: [PATCH 02/16] installed rns-sdk, ethers v5 --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 7bd95da..1f06947 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "dependencies": { "@openzeppelin/contracts": "^5.0.2", "@rsksmart/rns-resolver.js": "^1.1.0", + "@rsksmart/rns-sdk": "^1.0.0-beta.9", "@rsksmart/rsk-precompiled-abis": "^6.0.0-ARROWHEAD", "@types/fs-extra": "^11.0.4", "@types/zxcvbn": "^4.4.5", @@ -55,6 +56,7 @@ "cli-table3": "^0.6.5", "commander": "^13.1.0", "figlet": "^1.7.0", + "ethers": "^5.8.0", "fs-extra": "^11.2.0", "inquirer": "^12.1.0", "ora": "^8.0.1", From 7535e80d93977f6dfc7a49d7ea4c43bcbe66f8f4 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:26:17 +0100 Subject: [PATCH 03/16] added util to manage wallet connection with ethers; rns-sdk connects only with ethers v5 --- src/utils/ethersWallet.ts | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/utils/ethersWallet.ts diff --git a/src/utils/ethersWallet.ts b/src/utils/ethersWallet.ts new file mode 100644 index 0000000..65853c2 --- /dev/null +++ b/src/utils/ethersWallet.ts @@ -0,0 +1,65 @@ +import { existsSync, readFileSync } from "fs"; +import crypto from "crypto"; +import inquirer from "inquirer"; +import { ethers, Wallet } from "ethers"; +import { walletFilePath } from "./constants.js"; + +/** + * Decrypts the local wallet and returns an Ethers.js Wallet instance. + * @param walletName - The name of the wallet to load (optional, defaults to current). + * @param providerUrl - The RPC URL to connect to. + */ +export async function getEthersSigner( + walletName: string | undefined, + providerUrl: string +): Promise { + if (!existsSync(walletFilePath)) { + throw new Error("No wallet file found. Please create one first."); + } + + const walletsData = JSON.parse(readFileSync(walletFilePath, "utf8")); + + const name = walletName || walletsData.currentWallet; + + if (!walletsData.wallets || !walletsData.wallets[name]) { + throw new Error(`Wallet '${name}' not found.`); + } + + const walletData = walletsData.wallets[name]; + + const { password } = await inquirer.prompt([ + { + type: "password", + name: "password", + message: `Enter password for wallet '${name}':`, + mask: "*", + }, + ]); + + try { + const iv = Uint8Array.from(Buffer.from(walletData.iv, "hex")); + const key = crypto.scryptSync(password, iv, 32); + const decipher = crypto.createDecipheriv( + "aes-256-cbc", + Uint8Array.from(key), + iv + ); + + let decrypted = decipher.update( + walletData.encryptedPrivateKey, + "hex", + "utf8" + ); + decrypted += decipher.final("utf8"); + + const provider = new ethers.providers.JsonRpcProvider(providerUrl); + + const privateKey = decrypted.startsWith("0x") + ? decrypted + : `0x${decrypted}`; + + return new Wallet(privateKey, provider); + } catch (error) { + throw new Error("Incorrect password or corrupted wallet file."); + } +} From 8ca9a7883a9d8e8188860aabb9bb4122b11965ee Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:29:11 +0100 Subject: [PATCH 04/16] added constants for address used in the rns-sdk --- src/constants/explorer.ts | 6 ++++++ src/constants/rnsAddress.ts | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/constants/explorer.ts create mode 100644 src/constants/rnsAddress.ts diff --git a/src/constants/explorer.ts b/src/constants/explorer.ts new file mode 100644 index 0000000..4b80ed0 --- /dev/null +++ b/src/constants/explorer.ts @@ -0,0 +1,6 @@ +export const EXPLORER = { + BLOCKSCOUT : { + mainnet : "https://rootstock.blockscout.com", + testnet : "https://rootstock-testnet.blockscout.com" + } +} diff --git a/src/constants/rnsAddress.ts b/src/constants/rnsAddress.ts new file mode 100644 index 0000000..b1ed9b2 --- /dev/null +++ b/src/constants/rnsAddress.ts @@ -0,0 +1,23 @@ +import { Address } from "viem"; + +type Network = "mainnet" | "testnet"; + +type RnsAddressKey = + | "rnsRegistryAddress" + | "rskOwnerAddress" + | "fifsAddrRegistrarAddress"; + +export const RNSADDRESSES: Record> = { + rnsRegistryAddress: { + mainnet: "0xcb868aeabd31e2b66f74e9a55cf064abb31a4ad5", + testnet: "0x7d284aaac6e925aad802a53c0c69efe3764597b8", + }, + rskOwnerAddress: { + mainnet: "0x45d3e4fb311982a06ba52359d44cb4f5980e0ef1", + testnet: "0xca0a477e19bac7e0e172ccfd2e3c28a7200bdb71", + }, + fifsAddrRegistrarAddress: { + mainnet: "0xd9c79ced86ecf49f5e4a973594634c83197c35ab", + testnet: "0x90734bd6bf96250a7b262e2bc34284b0d47c1e8d", + }, +}; From 81a92507e49f2627f5f084a6b99a450f53fde743 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:31:38 +0100 Subject: [PATCH 05/16] added rns:register to /commands --- src/commands/rnsRegister.ts | 0 src/constants/tokenAdress.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/commands/rnsRegister.ts diff --git a/src/commands/rnsRegister.ts b/src/commands/rnsRegister.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/constants/tokenAdress.ts b/src/constants/tokenAdress.ts index d3d803d..e26cbcf 100644 --- a/src/constants/tokenAdress.ts +++ b/src/constants/tokenAdress.ts @@ -14,3 +14,16 @@ export const TOKENS: Record> = { testnet: "0xd37a3e5874be2dc6c732ad21c008a1e4032a6040", }, }; + +export const TOKENS_METADATA = { + RIF: { + mainnet: "RIF", + testnet: "tRIF", + faucet : {link : "https://faucet.rootstock.io/"} + }, + RBTC: { + mainnet: "rBTC", + testnet: "tRBTC", + faucet : {link : "https://faucet.rifos.org/"}, + }, +}; From f8921674822247eb4cd7dd662de0ba021474b10e Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:32:43 +0100 Subject: [PATCH 06/16] added rns:transfer command --- src/commands/rnsTransfer.ts | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/commands/rnsTransfer.ts diff --git a/src/commands/rnsTransfer.ts b/src/commands/rnsTransfer.ts new file mode 100644 index 0000000..2c34156 --- /dev/null +++ b/src/commands/rnsTransfer.ts @@ -0,0 +1,110 @@ +import chalk from "chalk"; +import { ethers } from "ethers"; +import { TOKENS_METADATA } from "../constants/tokenAdress.js"; +import { getEthersSigner } from "../utils/ethersWallet.js"; +import { + logInfo, + logError, + logSuccess, + logWarning, + logMessage, +} from "../utils/logger.js"; +import { rootstock, rootstockTestnet } from "viem/chains"; +import rnsSdk from "@rsksmart/rns-sdk"; +import { EXPLORER } from "../constants/explorer.js"; +const { PartnerRegistrar } = rnsSdk; + +interface RnsTransferOptions { + domain: string; + wallet: string; + testnet?: boolean; + recipient: string; + isExternal?: boolean; +} + +export async function rnsTransferCommand(options: RnsTransferOptions) { + const { domain, wallet, testnet, recipient, isExternal = false } = options; + const network = testnet ? "testnet" : "mainnet"; + const rpcUrl = testnet + ? rootstockTestnet.rpcUrls.default.http[0] + : rootstock.rpcUrls.default.http[0]; + + try { + const signer = await getEthersSigner(wallet, rpcUrl); + + const partnerRegistrar = new PartnerRegistrar(signer, network); + + const label = domain.replace(".rsk", ""); + const cleanRecipientAddress = ethers.utils.getAddress( + recipient.toLowerCase() + ); + + const isAvailable = await partnerRegistrar.available(label); + if (isAvailable) { + logError( + isExternal, + `❌ The domain '${domain}' is not registered yet. You can only transfer domains you already own.` + ); + return; + } + + const owner = await partnerRegistrar.ownerOf(label); + if (owner.toLowerCase() !== signer.address.toLowerCase()) { + logError( + isExternal, + `❌ You do not own '${domain}'. Current owner: ${owner}` + ); + return; + } + + if (owner.toLowerCase() !== cleanRecipientAddress) { + logError( + isExternal, + `❌ You already own '${domain}'. Can't transfer to Owner!` + ); + return; + } + + logInfo( + isExternal, + `Preparing to transfer '${domain}' to ${cleanRecipientAddress}...` + ); + + const rbtcBalance = await signer.getBalance(); + if (rbtcBalance.eq(0)) { + logError( + isExternal, + `❌ Insufficient ${TOKENS_METADATA.RBTC[network]} for gas.` + ); + if (network == "testnet") { + logMessage( + isExternal, + `💡 Get test rBTC here: ${TOKENS_METADATA.RBTC.faucet.link}`, + chalk.yellow + ); + } + return; + } + + logWarning(isExternal, `🔄 Transferring ownership...`); + + const transferTxHash = await partnerRegistrar.transfer( + label, + cleanRecipientAddress + ); + + logMessage( + isExternal, + `Tx: ${EXPLORER.BLOCKSCOUT[network]}/tx/${transferTxHash}`, + chalk.dim + ); + + logSuccess( + isExternal, + `✅ Success! '${domain}' has been transferred to ${cleanRecipientAddress}` + ); + } catch (error: any) { + const errorMessage = error.reason || error.message || error; + logError(isExternal, `Transfer Error: ${errorMessage}`); + } +} From 593590c418ebfd70dfcdec630ae11be648f265e1 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:33:25 +0100 Subject: [PATCH 07/16] added rns:update command --- src/commands/rnsUpdate.ts | 105 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/commands/rnsUpdate.ts diff --git a/src/commands/rnsUpdate.ts b/src/commands/rnsUpdate.ts new file mode 100644 index 0000000..92adcfd --- /dev/null +++ b/src/commands/rnsUpdate.ts @@ -0,0 +1,105 @@ +import chalk from "chalk"; +import { ethers } from "ethers"; +import { RNSADDRESSES } from "../constants/rnsAddress.js"; +import { TOKENS_METADATA } from "../constants/tokenAdress.js"; +import { getEthersSigner } from "../utils/ethersWallet.js"; +import { + logInfo, + logError, + logSuccess, + logWarning, + logMessage, +} from "../utils/logger.js"; +import { rootstock, rootstockTestnet } from "viem/chains"; +import rnsSdk from "@rsksmart/rns-sdk"; +import { EXPLORER } from "../constants/explorer.js"; + +const { AddrResolver, PartnerRegistrar } = rnsSdk; + +interface RnsUpdateOptions { + domain: string; + wallet: string; + address: string; + testnet?: boolean; + isExternal?: boolean; +} + +export async function rnsUpdateCommand(options: RnsUpdateOptions) { + const { domain, wallet, address, testnet, isExternal = false } = options; + const network = testnet ? "testnet" : "mainnet"; + const rpcUrl = testnet + ? rootstockTestnet.rpcUrls.default.http[0] + : rootstock.rpcUrls.default.http[0]; + + try { + const signer = await getEthersSigner(wallet, rpcUrl); + const registryAddress = ethers.utils.getAddress( + RNSADDRESSES.rnsRegistryAddress[network] + ); + const partnerRegistrar = new PartnerRegistrar(signer, network); + + const addrResolver = new AddrResolver(registryAddress, signer); + const cleanRecipientAddress = ethers.utils.getAddress( + address.toLowerCase() + ); + + logInfo(isExternal, `Preparing to update records for '${domain}'...`); + + const rbtcBalance = await signer.getBalance(); + if (rbtcBalance.eq(0)) { + logError( + isExternal, + `❌ Insufficient ${TOKENS_METADATA.RBTC[network]} for gas.` + ); + if (network == "testnet") { + logMessage( + isExternal, + `💡 Get test rBTC here: ${TOKENS_METADATA.RBTC.faucet.link}`, + chalk.yellow + ); + } + return; + } + + const label = domain.replace(".rsk", ""); + + const isAvailable = await partnerRegistrar.available(label); + if (isAvailable) { + logError( + isExternal, + `❌ The domain '${domain}' is not registered yet. You can only update domains you already own.` + ); + return; + } + + const owner = await partnerRegistrar.ownerOf(label); + if (owner.toLowerCase() !== signer.address.toLowerCase()) { + logError( + isExternal, + `❌ You do not own '${domain}' and can't update it. Current owner: ${owner}` + ); + return; + } + + logWarning( + isExternal, + `🔄 Setting resolution address to ${cleanRecipientAddress}...` + ); + + const updateTx = await addrResolver.setAddr(domain, cleanRecipientAddress); + logMessage( + isExternal, + `Tx: ${EXPLORER.BLOCKSCOUT[network]}/tx/${updateTx.hash}`, + chalk.dim + ); + await updateTx.wait(); + + logSuccess( + isExternal, + `✅ Success! '${domain}' now resolves to ${cleanRecipientAddress}` + ); + } catch (error: any) { + const errorMessage = error.reason || error.message || error; + logError(isExternal, `Transfer Error: ${errorMessage}`); + } +} From 32daf5630c64ca4dde639a4ec2e8374a1c27c926 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:35:32 +0100 Subject: [PATCH 08/16] added rns:register, rns:transfer, rns:update to bin/index.ts --- bin/index.ts | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/bin/index.ts b/bin/index.ts index 517c695..bde1aae 100644 --- a/bin/index.ts +++ b/bin/index.ts @@ -22,6 +22,9 @@ import { simulateCommand, TransactionSimulationOptions } from "../src/commands/s import { parseEther } from "viem"; import { resolveRNSToAddress } from "../src/utils/rnsHelper.js"; import { validateAndFormatAddressRSK } from "../src/utils/index.js"; +import { rnsUpdateCommand } from "../src/commands/rnsUpdate.js"; +import { rnsTransferCommand } from "../src/commands/rnsTransfer.js"; +import { rnsRegisterCommand } from "../src/commands/rnsRegister.js"; interface CommandOptions { testnet?: boolean; @@ -103,7 +106,7 @@ program } holderAddress = resolvedAddress; } - + await balanceCommand({ testnet: options.testnet, walletName: options.wallet!, @@ -485,4 +488,46 @@ program } }); + program + .command("rns:register ") + .description("Register a new RNS domain") + .option("-t, --testnet", "Use testnet") + .option("--wallet ", "Wallet to use") + .action(async (domain, options) => { + await rnsRegisterCommand({ + domain, + wallet: options.wallet, + testnet: !!options.testnet, + }); + }); + +program + .command("rns:transfer ") + .description("Transfer ownership of an RNS domain to another address") + .option("-t, --testnet", "Use testnet") + .option("--wallet ", "Wallet to use") + .action(async (domain, recipient, options) => { + await rnsTransferCommand({ + domain, + recipient, + wallet: options.wallet, + testnet: !!options.testnet, + }); + }); + +program + .command("rns:update ") + .description("Update resolver records for an RNS domain") + .option("-t, --testnet", "Use testnet") + .option("--wallet ", "Wallet to use") + .option("--address
", "New address to set in resolver") + .action(async (domain, options) => { + await rnsUpdateCommand({ + domain, + wallet: options.wallet, + testnet: !!options.testnet, + address: options.address, + }); + }); + program.parse(process.argv); From 7181246eebb6aff20fa3deef6f29494af154cd98 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 13:38:37 +0100 Subject: [PATCH 09/16] added table-of-contents, added docs for rns:register, rns-transfer, rns-update --- README.md | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2a94a7..f619764 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,28 @@ `rsk-cli` is a command-line tool for interacting with Rootstock blockchain +## Table of Contents + +- [Installation](#installation) +- [Configuration](#configuration) +- [Features](#features) + 1. [Manage Wallet](#1-manage-wallet) + 2. [Check Balance](#2-check-balance) + 3. [Transfer](#3-transfer-rbtc-and-erc20) + 4. [Check Transaction Status](#4-check-transaction-status) + 5. [Deploy Smart Contract](#5-deploy-smart-contract) + 6. [Verify Smart Contract](#6-verify-smart-contract) + 7. [Interact with Verified Contracts](#7-interact-with-verified-smart-contracts) + 8. [Interact with RSK Bridge](#8-interact-with-rsk-bridge-contract) + 9. [Fetch Wallet History](#9-fetch-wallet-history) + 10. [Batch Transfer](#10-batch-transfer) + 11. [Transaction Simulation](#11-transaction-simulation) + 12. [RNS Resolve](#12-rns-resolve) + 13. [RNS Register](#13-rns-register) + 14. [RNS Transfer](#14-rns-transfer) + 15. [RNS Update](#15-rns-update) +- [Contributing](#contributing) + ## Installation To install the CLI tool globally, use the following command: @@ -232,7 +254,7 @@ Use the `-t` or `--testnet` flag to check the balance on the Rootstock testnet. # Check balance on testnet rsk-cli balance -t -# Check balance using RNS domain on testnet +# Check balance using RNS domain on testnet rsk-cli balance -t --rns testing.rsk ``` @@ -907,11 +929,137 @@ Output example: 🌐 Network: Rootstock Testnet ``` -> **Note**: +> **Note**: > - The `.rsk` extension is automatically appended if not provided > - Both checksummed and non-checksummed addresses are supported > - The command will show appropriate error messages if the name or address cannot be resolved +### 13. RNS Register + +The `rns:register` command allows you to secure a `.rsk` domain name through the RIF Name Service. It implements a two-step commitment process to ensure secure registration and prevent front-running. + +**Requirements** + +- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. +- **Fees**: You must have sufficient **RIF/tRIF** balance (and allowance) for the registration price. +- **Patience**: The process involves a **1-minute wait** between steps to satisfy the RNS commitment maturity requirement. + +**Process Details** + +- **Sanity Checks**: The tool automatically checks domain availability, RIF balance, and rBTC balance. +- **Commitment**: Submits a commitment hash to the blockchain. +- **Maturity Wait**: The CLI monitors the chain until the commitment is ready to be revealed (approx. 60 seconds). +- **Registration**: Submits the final registration transaction to claim the domain. + +**Usage** + +##### Mainnet + +```bash +rsk-cli rns:register .rsk --wallet +``` + +##### Testnet + +```bash +rsk-cli rns:register .rsk --wallet --testnet +``` + +**Output example:** + +``` +🔍 Checking availability for 'mycoolname.rsk'... +Price: 2.0 tRIF +Step 1/2: Sending commitment... +✅ Commitment sent. +⏳ Waiting for commitment maturity (approx 1 min)... +......... +Step 2/2: Registering domain... +Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash + +✅ Success! 'mycoolname.rsk' is now registered to 0x123...FFf + +``` + +### 14. RNS Transfer + +The `rns:transfer` command allows you to transfer the ownership of an existing `.rsk` domain to another wallet address. This is a single-step transaction that updates the registrant of the domain on the Rootstock blockchain.. + +**Requirements** + +- **Ownership**: You must be the current owner of the domain you are trying to transfer. +- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. +- **Recipient**: A valid Rootstock address or RNS domain to receive the ownership. + +**Process Details** + +- **Ownership Validation**: The tool verifies that the selected wallet is the actual owner of the domain before attempting the transfer. +- **Address Checksumming**: Automatically handles Rootstock address checksums to ensure the domain is sent to the correct destination. +- **Maturity Wait**: The CLI monitors the chain until the commitment is ready to be revealed (approx. 60 seconds). +- **Single Transaction**: Unlike registration, a transfer is executed in a single transaction without a waiting period. + +**Usage** + +##### Mainnet + +```bash +rsk-cli rns:transfer .rsk --wallet +``` + +##### Testnet + +```bash +rsk-cli rns:transfer .rsk --wallet --testnet +``` + +**Output example:** + +``` +Preparing to transfer 'mycoolname.rsk' to 0x123...FFf +🔄 Transferring ownership... +Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash +✅ Success! 'mycoolname.rsk' has been transferred to 0x123...FFf + +``` + +### 15. RNS Update + +The `rns:update` command allows you to change the resolution address of a `.rsk` domain. This determines which wallet address or contract the domain "points to" when users send funds to it. + +**Requirements** + +- **Authority**: You must be the owner or controller of the domain you are trying to update records for. +- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. +- **Target**: A valid Rootstock address to set as the new destination. + +**Process Details** + +- **Ownership Check**: The CLI verifies that you have the authority to modify the domain records. +- **Registry Update**: It sends a transaction to the RNS Registry to update the `addr` record for your domain. + +**Usage** + +##### Mainnet + +```bash +rsk-cli rns:transfer .rsk --wallet +``` + +##### Testnet + +```bash +rsk-cli rns:update blessings.rsk --address --testnet +``` + +**Output example:** + +``` +Preparing to update records for 'mycoolname.rsk'... +🔄 Setting resolution address to 0x123...FFf +Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash +✅ Success! 'mycoolname.rsk' now resolves to 0x123...FFf +``` + ## Contributing We welcome contributions from the community. Please fork the repository and submit pull requests with your changes. Ensure your code adheres to the project's main objective. From 5382f325df3537b5e2791e04b4f245b979090785 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 5 Feb 2026 16:03:59 +0100 Subject: [PATCH 10/16] fixed missing import --- src/commands/rnsRegister.ts | 144 ++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/commands/rnsRegister.ts b/src/commands/rnsRegister.ts index e69de29..5925615 100644 --- a/src/commands/rnsRegister.ts +++ b/src/commands/rnsRegister.ts @@ -0,0 +1,144 @@ +import chalk from "chalk"; +import { BigNumber, ethers } from "ethers"; +import { RNSADDRESSES } from "../constants/rnsAddress.js"; +import { TOKENS, TOKENS_METADATA } from "../constants/tokenAdress.js"; +import { getEthersSigner } from "../utils/ethersWallet.js"; +import { + logInfo, + logError, + logSuccess, + logWarning, + logMessage, +} from "../utils/logger.js"; +import { rootstock, rootstockTestnet } from "viem/chains"; +import rnsSdk from "@rsksmart/rns-sdk"; +import { EXPLORER } from "../constants/explorer.js"; +const { RSKRegistrar } = rnsSdk; + +interface RnsRegisterOptions { + domain: string; + wallet: string; + testnet?: boolean; + isExternal?: boolean; +} + +export async function rnsRegisterCommand(options: RnsRegisterOptions) { + const { domain, wallet, testnet, isExternal = false } = options; + const network = testnet ? "testnet" : "mainnet"; + const rpcUrl = testnet + ? rootstockTestnet.rpcUrls.default.http[0] + : rootstock.rpcUrls.default.http[0]; + + try { + const signer = await getEthersSigner(wallet, rpcUrl); + const registrar = new RSKRegistrar( + ethers.utils.getAddress( + RNSADDRESSES.rskOwnerAddress[network].toLowerCase() + ), + ethers.utils.getAddress( + RNSADDRESSES.fifsAddrRegistrarAddress[network].toLowerCase() + ), + ethers.utils.getAddress(TOKENS["RIF"][network].toLowerCase()), + signer + ); + const label = domain.replace(".rsk", ""); + logInfo(isExternal, `🔍 Checking availability for '${label}.rsk'...`); + + const available = await registrar.available(label); + if (!available) { + logError(isExternal, `❌ Domain '${label}.rsk' is already taken.`); + return; + } + const duration = BigNumber.from(1); + const price = await registrar.price(label, duration as any); + logMessage( + isExternal, + `Price: ${ethers.utils.formatUnits(price, 18)} ${ + TOKENS_METADATA.RIF[network] + }`, + chalk.dim + ); + + const rbtcBalance = await signer.getBalance(); + if (rbtcBalance.eq(0)) { + logError( + isExternal, + `❌ Insufficient ${TOKENS_METADATA.RBTC[network]} balance for gas. Please fund your wallet.` + ); + if (network == "testnet") { + logMessage( + isExternal, + `💡 Get test rBTC here: ${TOKENS_METADATA.RBTC.faucet.link}`, + chalk.yellow + ); + } + return; + } + + const rifAddress = TOKENS["RIF"][network]; + const rifAbi = ["function balanceOf(address) view returns (uint256)"]; + const rifContract = new ethers.Contract(rifAddress, rifAbi, signer); + const userRifBalance = await rifContract.balanceOf(signer.address); + if (userRifBalance.lt(price)) { + logError( + isExternal, + `❌ Insufficient tRIF. Have: ${ethers.utils.formatUnits( + userRifBalance, + 18 + )} tRIF, Need: ${ethers.utils.formatUnits(price, 18)} ${ + TOKENS_METADATA.RIF[network] + }` + ); + if (network == "testnet") { + logMessage( + isExternal, + `💡 Get test tRIF here: ${TOKENS_METADATA.RIF.faucet.link}`, + chalk.yellow + ); + } + return; + } + + logMessage(isExternal, "Step 1/2: Sending commitment...", chalk.yellow); + const { makeCommitmentTransaction, secret, canReveal } = + await registrar.commitToRegister(label, signer.address); + + await makeCommitmentTransaction.wait(); + logSuccess(isExternal, "✅ Commitment sent."); + + logMessage( + isExternal, + "⏳ Waiting for commitment maturity (approx 1 min)...", + chalk.cyan + ); + + while (!(await canReveal())) { + await new Promise((r) => setTimeout(r, 5000)); + if (!isExternal) process.stdout.write("."); + } + if (!isExternal) console.log(""); + + logWarning(isExternal, "Step 2/2: Registering domain..."); + + const registerTx = await registrar.register( + label, + signer.address, + secret, + duration, + price + ); + logMessage( + isExternal, + `Tx: ${EXPLORER.BLOCKSCOUT[network]}/tx/${registerTx.hash}`, + chalk.dim + ); + await registerTx.wait(); + + logSuccess( + isExternal, + `✅ Success! '${domain}' is now registered to ${signer.address}` + ); + } catch (error: any) { + logError(isExternal, `Registration Error: ${error.message || error}`); + } +} From 37c25eb790bdb7a909956f949460f7c3fb1b2cb5 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 17:01:49 +0100 Subject: [PATCH 11/16] updated readMe --- README.md | 172 +++++++++++++++++++++--------------------------------- 1 file changed, 65 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index f619764..f95833f 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,7 @@ 9. [Fetch Wallet History](#9-fetch-wallet-history) 10. [Batch Transfer](#10-batch-transfer) 11. [Transaction Simulation](#11-transaction-simulation) - 12. [RNS Resolve](#12-rns-resolve) - 13. [RNS Register](#13-rns-register) - 14. [RNS Transfer](#14-rns-transfer) - 15. [RNS Update](#15-rns-update) + 12. [RNS Operations](#12-rns-operations) - [Contributing](#contributing) ## Installation @@ -873,193 +870,154 @@ The simulation provides comprehensive information: > **Note**: Simulation uses real blockchain state but does not execute transactions. It provides accurate estimates based on current network conditions. Gas prices may vary, so actual costs might differ slightly from simulation results. -### 12. RNS Resolve +### 12. RNS Operations +The `rns` command provides a unified interface to interact with the RIF Name Service (RNS). You can Register, Transfer, Update, and Resolve domains using specific flags. -The `resolve` command allows you to interact with the RIF Name Service (RNS) on the Rootstock blockchain. You can perform both forward resolution (domain to address) and reverse resolution (address to domain name). +#### 1. Register a Domain -#### Forward Resolution (Domain to Address) - -Convert an RNS domain name to its associated address: +Secure a `.rsk` domain name through a two-step commitment process. Requires a wallet with RBTC for gas and RIF for the registration fee. ##### Mainnet ```bash -rsk-cli resolve testing.rsk +rsk-cli rns --register .rsk --wallet ``` ##### Testnet ```bash -rsk-cli resolve testing.rsk --testnet +rsk-cli rns --register .rsk --wallet --testnet ``` -Output example: +##### Output example: ``` -🔍 Resolving testing.rsk... -✅ Domain resolved successfully! -🏷️ Domain: testing.rsk -📄 Address: 0x0000000000000000000000000000000001000006 -🌐 Network: Rootstock Mainnet +🔍 Checking availability for 'mycoolname.rsk'... +Price: 2.0 tRIF +Step 1/2: Sending commitment... +✅ Commitment sent. +⏳ Waiting for commitment maturity (approx 1 min)... +......... +Step 2/2: Registering domain... +Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash + +✅ Success! 'mycoolname.rsk' is now registered to 0x123...FFf + ``` -#### Reverse Resolution (Address to Domain) +#### 2. Transfer Ownership -Convert an address back to its RNS domain name: +Transfer the ownership of an existing domain to another address. Requires: `--recipient` flag. ##### Mainnet ```bash -rsk-cli resolve 0x123456789abcdef0123456789abcdef012345678 --reverse +rsk-cli rns --transfer .rsk --recipient --wallet ``` ##### Testnet ```bash -rsk-cli resolve 0x123456789abcdef0123456789abcdef012345678 --reverse --testnet +rsk-cli rns --transfer .rsk --wallet --testnet ``` -Output example: +##### Output example: ``` -🔍 Resolving address: 0x123456789abcdef0123456789abcdef012345678 -✅ Resolution successful! -📍 Address: 0x123456789abcdef0123456789abcdef012345678 -📌 Name: alice.rsk -🌐 Network: Rootstock Testnet -``` - -> **Note**: -> - The `.rsk` extension is automatically appended if not provided -> - Both checksummed and non-checksummed addresses are supported -> - The command will show appropriate error messages if the name or address cannot be resolved - -### 13. RNS Register - -The `rns:register` command allows you to secure a `.rsk` domain name through the RIF Name Service. It implements a two-step commitment process to ensure secure registration and prevent front-running. - -**Requirements** - -- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. -- **Fees**: You must have sufficient **RIF/tRIF** balance (and allowance) for the registration price. -- **Patience**: The process involves a **1-minute wait** between steps to satisfy the RNS commitment maturity requirement. +Preparing to transfer 'mycoolname.rsk' to 0x123...FFf +🔄 Transferring ownership... +Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash +✅ Success! 'mycoolname.rsk' has been transferred to 0x123...FFf -**Process Details** +``` -- **Sanity Checks**: The tool automatically checks domain availability, RIF balance, and rBTC balance. -- **Commitment**: Submits a commitment hash to the blockchain. -- **Maturity Wait**: The CLI monitors the chain until the commitment is ready to be revealed (approx. 60 seconds). -- **Registration**: Submits the final registration transaction to claim the domain. +#### 3. Update Resolver -**Usage** +Change the resolution address of a domain (where the domain "points" to). Requires: `--address` flag. ##### Mainnet ```bash -rsk-cli rns:register .rsk --wallet +rsk-cli rns --update blessings.rsk --address ``` ##### Testnet ```bash -rsk-cli rns:register .rsk --wallet --testnet +rsk-cli rns --update blessings.rsk --address --testnet ``` -**Output example:** +##### Output example: ``` -🔍 Checking availability for 'mycoolname.rsk'... -Price: 2.0 tRIF -Step 1/2: Sending commitment... -✅ Commitment sent. -⏳ Waiting for commitment maturity (approx 1 min)... -......... -Step 2/2: Registering domain... +Preparing to update records for 'mycoolname.rsk'... +🔄 Setting resolution address to 0x123...FFf Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash - -✅ Success! 'mycoolname.rsk' is now registered to 0x123...FFf - +✅ Success! 'mycoolname.rsk' now resolves to 0x123...FFf ``` -### 14. RNS Transfer - -The `rns:transfer` command allows you to transfer the ownership of an existing `.rsk` domain to another wallet address. This is a single-step transaction that updates the registrant of the domain on the Rootstock blockchain.. - -**Requirements** - -- **Ownership**: You must be the current owner of the domain you are trying to transfer. -- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. -- **Recipient**: A valid Rootstock address or RNS domain to receive the ownership. +#### 4. Resolve Domain -**Process Details** +Perform both forward resolution (domain to address) and reverse resolution (address to domain name). -- **Ownership Validation**: The tool verifies that the selected wallet is the actual owner of the domain before attempting the transfer. -- **Address Checksumming**: Automatically handles Rootstock address checksums to ensure the domain is sent to the correct destination. -- **Maturity Wait**: The CLI monitors the chain until the commitment is ready to be revealed (approx. 60 seconds). -- **Single Transaction**: Unlike registration, a transfer is executed in a single transaction without a waiting period. +**Forward Resolution (Domain to Address):** -**Usage** +Convert an RNS domain name to its associated address: ##### Mainnet ```bash -rsk-cli rns:transfer .rsk --wallet +rsk-cli rns --resolve testing.rsk ``` ##### Testnet ```bash -rsk-cli rns:transfer .rsk --wallet --testnet +rsk-cli rns --resolve testing.rsk --testnet ``` -**Output example:** +##### Output example: ``` -Preparing to transfer 'mycoolname.rsk' to 0x123...FFf -🔄 Transferring ownership... -Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash -✅ Success! 'mycoolname.rsk' has been transferred to 0x123...FFf - +🔍 Resolving testing.rsk... +✅ Domain resolved successfully! +🏷️ Domain: testing.rsk +📄 Address: 0x0000000000000000000000000000000001000006 +🌐 Network: Rootstock Mainnet ``` -### 15. RNS Update +**Reverse Resolution (Address to Domain):** -The `rns:update` command allows you to change the resolution address of a `.rsk` domain. This determines which wallet address or contract the domain "points to" when users send funds to it. - -**Requirements** - -- **Authority**: You must be the owner or controller of the domain you are trying to update records for. -- **Gas**: You must have a small amount of **RBTC/tRBTC** to cover transaction fees. -- **Target**: A valid Rootstock address to set as the new destination. - -**Process Details** - -- **Ownership Check**: The CLI verifies that you have the authority to modify the domain records. -- **Registry Update**: It sends a transaction to the RNS Registry to update the `addr` record for your domain. - -**Usage** +Convert an address back to its RNS domain name: ##### Mainnet ```bash -rsk-cli rns:transfer .rsk --wallet +rsk-cli rns --resolve 0x123456789abcdef0123456789abcdef012345678 --reverse ``` ##### Testnet ```bash -rsk-cli rns:update blessings.rsk --address --testnet +rsk-cli rns --resolve 0x123456789abcdef0123456789abcdef012345678 --reverse --testnet ``` -**Output example:** +Output example: ``` -Preparing to update records for 'mycoolname.rsk'... -🔄 Setting resolution address to 0x123...FFf -Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash -✅ Success! 'mycoolname.rsk' now resolves to 0x123...FFf +🔍 Resolving address: 0x123456789abcdef0123456789abcdef012345678 +✅ Resolution successful! +📍 Address: 0x123456789abcdef0123456789abcdef012345678 +📌 Name: alice.rsk +🌐 Network: Rootstock Testnet ``` +> **Note**: +> - The `.rsk` extension is automatically appended if not provided +> - Both checksummed and non-checksummed addresses are supported +> - The command will show appropriate error messages if the name or address cannot be resolved + + ## Contributing We welcome contributions from the community. Please fork the repository and submit pull requests with your changes. Ensure your code adheres to the project's main objective. From a8216eb1b3c30b8d835e68037994e5f9dc05fdae Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 17:06:39 +0100 Subject: [PATCH 12/16] fixed rns transfer logix bug --- src/commands/rnsTransfer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/rnsTransfer.ts b/src/commands/rnsTransfer.ts index 2c34156..df2caff 100644 --- a/src/commands/rnsTransfer.ts +++ b/src/commands/rnsTransfer.ts @@ -57,10 +57,10 @@ export async function rnsTransferCommand(options: RnsTransferOptions) { return; } - if (owner.toLowerCase() !== cleanRecipientAddress) { + if (owner.toLowerCase() === cleanRecipientAddress.toLowerCase()) { logError( isExternal, - `❌ You already own '${domain}'. Can't transfer to Owner!` + `❌ You already own '${domain}'. Can't transfer to yourself!` ); return; } From 3604ac34aff13e3e400a10089a5a4ee3dbafd425 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 17:10:15 +0100 Subject: [PATCH 13/16] updated readMe, fixed typo in rns update errormsg --- README.md | 2 +- src/commands/rnsUpdate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f95833f..e50fe68 100644 --- a/README.md +++ b/README.md @@ -918,7 +918,7 @@ rsk-cli rns --transfer .rsk --recipient --walle ##### Testnet ```bash -rsk-cli rns --transfer .rsk --wallet --testnet +rsk-cli rns --transfer .rsk --recipient --wallet --testnet ``` ##### Output example: diff --git a/src/commands/rnsUpdate.ts b/src/commands/rnsUpdate.ts index 92adcfd..ad6bb30 100644 --- a/src/commands/rnsUpdate.ts +++ b/src/commands/rnsUpdate.ts @@ -100,6 +100,6 @@ export async function rnsUpdateCommand(options: RnsUpdateOptions) { ); } catch (error: any) { const errorMessage = error.reason || error.message || error; - logError(isExternal, `Transfer Error: ${errorMessage}`); + logError(isExternal, `Update Error: ${errorMessage}`); } } From eccc28d4456256663e69cc3765f0126d3d8be47f Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 17:19:55 +0100 Subject: [PATCH 14/16] fixed wrong token in rnsRegister, updated readme --- README.md | 4 ++-- src/commands/rnsRegister.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e50fe68..46a7ea4 100644 --- a/README.md +++ b/README.md @@ -938,13 +938,13 @@ Change the resolution address of a domain (where the domain "points" to). Requir ##### Mainnet ```bash -rsk-cli rns --update blessings.rsk --address +rsk-cli rns --update blessings.rsk --address --wallet ``` ##### Testnet ```bash -rsk-cli rns --update blessings.rsk --address --testnet +rsk-cli rns --update blessings.rsk --address --wallet --testnet ``` ##### Output example: diff --git a/src/commands/rnsRegister.ts b/src/commands/rnsRegister.ts index 5925615..4a4a528 100644 --- a/src/commands/rnsRegister.ts +++ b/src/commands/rnsRegister.ts @@ -82,10 +82,10 @@ export async function rnsRegisterCommand(options: RnsRegisterOptions) { if (userRifBalance.lt(price)) { logError( isExternal, - `❌ Insufficient tRIF. Have: ${ethers.utils.formatUnits( + `❌ Insufficient ${TOKENS_METADATA.RIF[network]}. Have: ${ethers.utils.formatUnits( userRifBalance, 18 - )} tRIF, Need: ${ethers.utils.formatUnits(price, 18)} ${ + )} ${TOKENS_METADATA.RIF[network]}, Need: ${ethers.utils.formatUnits(price, 18)} ${ TOKENS_METADATA.RIF[network] }` ); From 6b97d65fa03eea2279bc3147609df2b0e066175a Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 17:48:16 +0100 Subject: [PATCH 15/16] updated readMe --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46a7ea4..1342983 100644 --- a/README.md +++ b/README.md @@ -907,7 +907,7 @@ Tx: https://rootstock-testnet.blockscout.com/tx/0x_transaction_hash #### 2. Transfer Ownership -Transfer the ownership of an existing domain to another address. Requires: `--recipient` flag. +Transfer the ownership of an existing domain to another address. Note: Recipient must be a raw address `(0x...)`, RNS names are not supported as recipient; use the `resolve` command first if needed. Requires: `--recipient` flag. ##### Mainnet From ab6a1e7c9c80854131bccc2c4cc57dbe1c62f591 Mon Sep 17 00:00:00 2001 From: youngancient Date: Thu, 12 Feb 2026 18:07:05 +0100 Subject: [PATCH 16/16] combined rns-register, rns-transfer, rns-update, rns-resolve into one command --- bin/index.ts | 131 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/bin/index.ts b/bin/index.ts index bde1aae..21999c6 100644 --- a/bin/index.ts +++ b/bin/index.ts @@ -336,19 +336,6 @@ program } }); -program - .command("resolve ") - .description("Resolve RNS names to addresses or reverse lookup addresses to names") - .option("-t, --testnet", "Use testnet (currently mainnet only)") - .option("-r, --reverse", "Reverse lookup: address to name") - .action(async (name: string, options: CommandOptions) => { - await resolveCommand({ - name, - testnet: !!options.testnet, - reverse: !!options.reverse - }); - }); - program .command("config") .description("Manage CLI configuration settings") @@ -488,46 +475,88 @@ program } }); - program - .command("rns:register ") - .description("Register a new RNS domain") - .option("-t, --testnet", "Use testnet") - .option("--wallet ", "Wallet to use") - .action(async (domain, options) => { - await rnsRegisterCommand({ - domain, - wallet: options.wallet, - testnet: !!options.testnet, - }); - }); - program - .command("rns:transfer ") - .description("Transfer ownership of an RNS domain to another address") - .option("-t, --testnet", "Use testnet") - .option("--wallet ", "Wallet to use") - .action(async (domain, recipient, options) => { - await rnsTransferCommand({ - domain, - recipient, - wallet: options.wallet, - testnet: !!options.testnet, - }); - }); + .command("rns") + .description("RNS Manager: Register, Transfer, Update, or Resolve domains") + .option("--register ", "Register a new RNS domain") + .option("--transfer ", "Transfer ownership of a domain") + .option("--update ", "Update resolver records for a domain") + .option("--resolve ", "Resolve a name to address (or address to name)") + + .option("-t, --testnet", "Use testnet network") + .option("-w, --wallet ", "Wallet name or private key to use") + .option("--recipient
", "Recipient address (required for --transfer)") + .option("--address
", "New address to set (required for --update)") + .option("-r, --reverse", "Perform reverse lookup (required for --resolve)") + + .action(async (options: any) => { + const actions = [ + options.register ? "register" : null, + options.transfer ? "transfer" : null, + options.update ? "update" : null, + options.resolve ? "resolve" : null, + ].filter(Boolean); + + if (actions.length === 0) { + console.error(chalk.red("❌ Error: You must specify an action.")); + console.log("Try: --register, --transfer, --update, or --resolve"); + process.exit(1); + } + if (actions.length > 1) { + console.error(chalk.red("❌ Error: Please specify only one action at a time.")); + process.exit(1); + } -program - .command("rns:update ") - .description("Update resolver records for an RNS domain") - .option("-t, --testnet", "Use testnet") - .option("--wallet ", "Wallet to use") - .option("--address
", "New address to set in resolver") - .action(async (domain, options) => { - await rnsUpdateCommand({ - domain, - wallet: options.wallet, - testnet: !!options.testnet, - address: options.address, - }); + const action = actions[0]; + + try { + switch (action) { + case "register": + await rnsRegisterCommand({ + domain: options.register, + wallet: options.wallet, + testnet: !!options.testnet, + }); + break; + + case "transfer": + if (!options.recipient) { + console.error(chalk.red("❌ Error: --recipient
is required for transfer.")); + process.exit(1); + } + await rnsTransferCommand({ + domain: options.transfer, + recipient: options.recipient, + wallet: options.wallet, + testnet: !!options.testnet, + }); + break; + + case "update": + if (!options.address) { + console.error(chalk.red("❌ Error: --address
is required for update.")); + process.exit(1); + } + await rnsUpdateCommand({ + domain: options.update, + address: options.address, + wallet: options.wallet, + testnet: !!options.testnet, + }); + break; + + case "resolve": + await resolveCommand({ + name: options.resolve, + testnet: !!options.testnet, + reverse: !!options.reverse, + }); + break; + } + } catch (error: any) { + console.error(chalk.red(`❌ Operation failed: ${error.message || error}`)); + process.exit(1); + } }); program.parse(process.argv);