Skip to main content
Time to read: 1 min

Sample dApp RNS Integration

This guide walks you through building a React dApp that integrates with the Rootstock Name Service (RNS) using the @rsksmart/rns-sdk. You'll create a functional application that allows users to:

  • Resolve domain names to addresses
  • Check domain availability
  • Check subdomain availability
  • Get domain owner information
  • Get resolver contract addresses

By the end of this guide, you'll have a working dApp that demonstrates core RNS operations using the official SDK.

Prerequisites

Before starting, ensure you have the following:

  • Node.js and npm installed
  • Basic understanding of React and JavaScript
  • Familiarity with blockchain concepts
note

All code snippets in this guide will be added inside your App.js file.

1. Project Setup

a. Create a new React app and navigate to the project directory:

npx create-react-app rns-dapp
cd rns-dapp

b. Install the required dependencies:

npm install @rsksmart/rns-sdk @ethersproject/providers ethers react-dom react-scripts 

npm install -D buffer

After installation, your package.json should include these dependencies (versions may vary based on the latest releases):

{
"dependencies": {
"@rsksmart/rns-sdk": "^1.0.0",
"@ethersproject/providers": "^5.7.2",
"ethers": "^5.7.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1"
}
},

"devDependencies": {
"buffer": "^6.0.3"
},

c. Go to index.js and change it to the following code:

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';

// Polyfill Buffer for browser environment
window.Buffer = window.Buffer || require('buffer').Buffer;

const root = createRoot(document.getElementById('root'));
root.render(<App />);

2. Import Required Modules

Open your App.js file and add the following imports:

import React, { useState, useCallback, useMemo } from "react";
import { JsonRpcProvider } from "@ethersproject/providers";
import { RNS, AddrResolver, RSKRegistrar } from "@rsksmart/rns-sdk";

What each import does:

ImportPurpose
React, useState, useCallback, useMemoReact hooks for state management
JsonRpcProviderConnect to RSK network via RPC
RNSDomain management (owner, resolver, subdomains)
AddrResolverResolve domains to addresses
RSKRegistrarCheck domain availability

3. Configure Network and Contract Addresses

Add the configuration for connecting to Rootstock Testnet:

const ROOTSTOCK_RPC_NODE = 'https://rpc.testnet.rootstock.io/${APIKEY}';

// Contract addresses for Rootstock Testnet
// See: https://github.com/rsksmart/rns-sdk
const ADDRESSES = {
// RNS Registry (testnet)
registry: "0x7d284aaac6e925aad802a53c0c69efe3764597b8",
// RSK Owner - ERC-721 .rsk domains token (testnet)
rskOwner: "0xca0a477e19bac7e0e172ccfd2e3c28a7200bdb71",
// FIFS Addr Registrar - .rsk domains registrar (testnet)
fifsAddrRegistrar: "0x90734bd6bf96250a7b262e2bc34284b0d47c1e8d",
// RIF Token (testnet)
rifToken: "0x19f64674d8a5b4e652319f5e239efd3bc969a1fe",
};
For Mainnet, use the following addresses instead:
ContractMainnet Address
Registry0xcb868aeabd31e2b66f74e9a55cf064abb31a4ad5
RSK Owner0x45d3e4fb311982a06ba52359d44cb4f5980e0ef1
FIFS Addr Registrar0xd9c79ced86ecf49f5e4a973594634c83197c35ab
RIF Token0x2acc95758f8b5f583470ba265eb685a8f45fc9d5

4. Initialize the Provider

Create a JSON-RPC provider to connect to the Rootstock network:

const provider = new JsonRpcProvider(ROOTSTOCK_RPC_NODE);

5. Add UI Styles

Add styling for the application interface:

const styles = {
container: {
minHeight: "100vh",
backgroundColor: "#f5f6fa",
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "20px",
fontFamily: "Arial, sans-serif",
},
card: {
backgroundColor: "#fff",
padding: "25px",
borderRadius: "10px",
maxWidth: "520px",
width: "100%",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
},
heading: {
textAlign: "center",
marginBottom: "20px",
fontSize: "22px",
},
input: {
padding: "10px",
width: "100%",
borderRadius: "6px",
border: "1px solid #ccc",
marginBottom: "10px",
boxSizing: "border-box",
},
buttonGroup: {
display: "flex",
gap: "10px",
marginBottom: "10px",
},
button: {
flex: 1,
padding: "10px",
backgroundColor: "#388e3c",
color: "#fff",
border: "none",
borderRadius: "6px",
cursor: "pointer",
},
buttonAlt: {
flex: 1,
padding: "10px",
backgroundColor: "#1976d2",
color: "#fff",
border: "none",
borderRadius: "6px",
cursor: "pointer",
},
buttonWide: {
width: "100%",
padding: "10px",
backgroundColor: "#5e35b1",
color: "#fff",
border: "none",
borderRadius: "6px",
marginTop: "10px",
marginBottom: "5px",
cursor: "pointer",
},
ownerBox: {
padding: "10px",
backgroundColor: "#e8f5e9",
borderRadius: "6px",
border: "1px solid #c8e6c9",
marginTop: "10px",
},
divider: {
margin: "20px 0",
borderTop: "1px solid #ddd",
},
resultBox: {
marginTop: "10px",
padding: "10px",
backgroundColor: "#f9f9f9",
border: "1px solid #ddd",
borderRadius: "6px",
},
};

6. Create the RNS Custom Hook

The custom hook useRns encapsulates all RNS SDK operations:

const useRns = () => {
// Initialize SDK instances
// For read-only operations, we pass the provider
// For write operations, you'd need a proper Signer

const rns = useMemo(() => new RNS(ADDRESSES.registry, provider), []);

const addrResolver = useMemo(
() => new AddrResolver(ADDRESSES.registry, provider),
[]
);

const rskRegistrar = useMemo(
() =>
new RSKRegistrar(
ADDRESSES.rskOwner,
ADDRESSES.fifsAddrRegistrar,
ADDRESSES.rifToken,
provider
),
[]
);

a. Get RSK address Using AddrResolver for a domain

  const getAddressByRns = useCallback(
async (domain) => {
try {
const address = await addrResolver.addr(domain);
if (
!address ||
address === "0x0000000000000000000000000000000000000000"
) {
return null;
}
return address.toLowerCase();
} catch (e) {
console.error("getAddressByRns error:", e);
return null;
}
},
[addrResolver]
);

b. Get a domain resolver contract address

 const getResolverAddress = useCallback(
async (domain) => {
try {
const resolverAddr = await rns.getResolver(domain);
return resolverAddr;
} catch (e) {
console.error("getResolverAddress error:", e);
return null;
}
},
[rns]
);

c. Get the domain's owner

  const getOwner = useCallback(
async (domain) => {
try {
const owner = await rns.getOwner(domain);
if (!owner || owner === "0x0000000000000000000000000000000000000000") {
return null;
}
return owner.toLowerCase();
} catch (e) {
console.error("getOwner error:", e);
return null;
}
},
[rns]
);

d. Check if a .rsk domain is available for registration

  const checkAvailability = useCallback(
async (domain) => {
try {
// Extract the label from the domain (remove .rsk suffix)
const label = domain.replace(/\.rsk$/i, "");
const available = await rskRegistrar.available(label);
return available;
} catch (e) {
console.error("checkAvailability error:", e);
return false;
}
},
[rskRegistrar]
);

e. Check if a subdomain is available

  const checkSubdomain = useCallback(
async (domain, subdomain) => {
try {
const available = await rns.getSubdomainAvailability(domain, subdomain);
return available;
} catch (e) {
console.error("checkSubdomain error:", e);
return false;
}
},
[rns]
);

f. Return the hook's public API

  return useMemo(
() => ({
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
// Expose SDK instances for advanced usage
rns,
addrResolver,
rskRegistrar,
}),
[
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
rns,
addrResolver,
rskRegistrar,
]
);
};

7. Create the Main Component

Add the main App component with the user interface:

// ============================================================================
// MAIN COMPONENT
// ============================================================================

export default function App() {
const {
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
} = useRns();

const [domain, setDomain] = useState("");
const [subdomain, setSubdomain] = useState("");
const [result, setResult] = useState("");
const [owner, setOwner] = useState("");
const [loading, setLoading] = useState(false);

const wrap = async (fn) => {
setLoading(true);
setResult("");
setOwner("");
try {
await fn();
} finally {
setLoading(false);
}
};

return (
<div style={styles.container}>
<div style={styles.card}>
<h1 style={styles.heading}>Rootstock Name Service Lookup</h1>

<input
style={styles.input}
value={domain}
onChange={(e) => setDomain(e.target.value)}
placeholder="Enter domain like testing.rsk"
/>

<div style={styles.buttonGroup}>
<button
style={styles.button}
disabled={loading}
onClick={() =>
wrap(async () => {
const addr = await getAddressByRns(domain);
setResult(addr || "No RSK address found");
})
}
>
Resolve RSK
</button>

<button
style={styles.buttonAlt}
disabled={loading}
onClick={() =>
wrap(async () => {
const addr = await getResolverAddress(domain);
setResult(addr || "No resolver found");
})
}
>
Get Resolver
</button>
</div>

<button
style={styles.buttonWide}
disabled={loading}
onClick={() =>
wrap(async () => {
const available = await checkAvailability(domain);
setResult(
available ? "✅ Domain is available" : "❌ Domain is taken"
);
})
}
>
Check Availability
</button>

<div style={{ display: "flex", gap: "8px" }}>
<input
style={{ ...styles.input, flex: 1 }}
value={subdomain}
onChange={(e) => setSubdomain(e.target.value)}
placeholder="Enter subdomain"
/>
<button
style={styles.buttonAlt}
disabled={loading}
onClick={() =>
wrap(async () => {
const available = await checkSubdomain(domain, subdomain);
setResult(
available
? "✅ Subdomain is available"
: "❌ Subdomain is taken"
);
})
}
>
Check Subdomain
</button>
</div>

<button
style={styles.buttonWide}
disabled={loading}
onClick={() =>
wrap(async () => {
const ownerAddr = await getOwner(domain);
if (ownerAddr) {
setOwner(ownerAddr);
setResult("Owner found");
} else {
setResult("No owner found");
}
})
}
>
Get Owner
</button>

{owner && (
<div style={styles.ownerBox}>
<strong>Owner:</strong> {owner}
</div>
)}

<div style={styles.resultBox}>{loading ? "Loading..." : result}</div>
</div>
</div>
);
}

8. Complete App.js Code

Here's the complete App.js file with all the code combined:

Click to expand full App.js code
import React, { useState, useCallback, useMemo } from "react";
import { JsonRpcProvider } from "@ethersproject/providers";
import { RNS, AddrResolver, RSKRegistrar } from "@rsksmart/rns-sdk";

// ============================================================================
// CONFIGURATION
// ============================================================================

const ROOTSTOCK_RPC_NODE = "https://public-node.testnet.rsk.co";

// Contract addresses for RSK Testnet
const ADDRESSES = {
registry: "0x7d284aaac6e925aad802a53c0c69efe3764597b8",
rskOwner: "0xca0a477e19bac7e0e172ccfd2e3c28a7200bdb71",
fifsAddrRegistrar: "0x90734bd6bf96250a7b262e2bc34284b0d47c1e8d",
rifToken: "0x19f64674d8a5b4e652319f5e239efd3bc969a1fe",
};

// ============================================================================
// PROVIDER SETUP
// ============================================================================

const provider = new JsonRpcProvider(ROOTSTOCK_RPC_NODE);

// ============================================================================
// STYLES
// ============================================================================

const styles = {
container: {
minHeight: "100vh",
backgroundColor: "#f5f6fa",
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "20px",
fontFamily: "Arial, sans-serif",
},
card: {
backgroundColor: "#fff",
padding: "25px",
borderRadius: "10px",
maxWidth: "520px",
width: "100%",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
},
heading: {
textAlign: "center",
marginBottom: "20px",
fontSize: "22px",
},
input: {
padding: "10px",
width: "100%",
borderRadius: "6px",
border: "1px solid #ccc",
marginBottom: "10px",
boxSizing: "border-box",
},
buttonGroup: {
display: "flex",
gap: "10px",
marginBottom: "10px",
},
button: {
flex: 1,
padding: "10px",
backgroundColor: "#388e3c",
color: "#fff",
border: "none",
borderRadius: "6px",
cursor: "pointer",
},
buttonAlt: {
flex: 1,
padding: "10px",
backgroundColor: "#1976d2",
color: "#fff",
border: "none",
borderRadius: "6px",
cursor: "pointer",
},
buttonWide: {
width: "100%",
padding: "10px",
backgroundColor: "#5e35b1",
color: "#fff",
border: "none",
borderRadius: "6px",
marginTop: "10px",
marginBottom: "5px",
cursor: "pointer",
},
ownerBox: {
padding: "10px",
backgroundColor: "#e8f5e9",
borderRadius: "6px",
border: "1px solid #c8e6c9",
marginTop: "10px",
},
divider: {
margin: "20px 0",
borderTop: "1px solid #ddd",
},
resultBox: {
marginTop: "10px",
padding: "10px",
backgroundColor: "#f9f9f9",
border: "1px solid #ddd",
borderRadius: "6px",
},
};

// ============================================================================
// RNS HOOK - Using @rsksmart/rns-sdk
// ============================================================================

const useRns = () => {
const rns = useMemo(() => new RNS(ADDRESSES.registry, provider), []);

const addrResolver = useMemo(
() => new AddrResolver(ADDRESSES.registry, provider),
[]
);

const rskRegistrar = useMemo(
() =>
new RSKRegistrar(
ADDRESSES.rskOwner,
ADDRESSES.fifsAddrRegistrar,
ADDRESSES.rifToken,
provider
),
[]
);

const getAddressByRns = useCallback(
async (domain) => {
try {
const address = await addrResolver.addr(domain);
if (
!address ||
address === "0x0000000000000000000000000000000000000000"
) {
return null;
}
return address.toLowerCase();
} catch (e) {
console.error("getAddressByRns error:", e);
return null;
}
},
[addrResolver]
);

const getResolverAddress = useCallback(
async (domain) => {
try {
const resolverAddr = await rns.getResolver(domain);
return resolverAddr;
} catch (e) {
console.error("getResolverAddress error:", e);
return null;
}
},
[rns]
);

const getOwner = useCallback(
async (domain) => {
try {
const owner = await rns.getOwner(domain);
if (!owner || owner === "0x0000000000000000000000000000000000000000") {
return null;
}
return owner.toLowerCase();
} catch (e) {
console.error("getOwner error:", e);
return null;
}
},
[rns]
);

const checkAvailability = useCallback(
async (domain) => {
try {
const label = domain.replace(/\.rsk$/i, "");
const available = await rskRegistrar.available(label);
return available;
} catch (e) {
console.error("checkAvailability error:", e);
return false;
}
},
[rskRegistrar]
);

const checkSubdomain = useCallback(
async (domain, subdomain) => {
try {
const available = await rns.getSubdomainAvailability(domain, subdomain);
return available;
} catch (e) {
console.error("checkSubdomain error:", e);
return false;
}
},
[rns]
);

return useMemo(
() => ({
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
rns,
addrResolver,
rskRegistrar,
}),
[
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
rns,
addrResolver,
rskRegistrar,
]
);
};

// ============================================================================
// MAIN COMPONENT
// ============================================================================

export default function App() {
const {
getAddressByRns,
getResolverAddress,
getOwner,
checkAvailability,
checkSubdomain,
} = useRns();

const [domain, setDomain] = useState("");
const [subdomain, setSubdomain] = useState("");
const [result, setResult] = useState("");
const [owner, setOwner] = useState("");
const [loading, setLoading] = useState(false);

const wrap = async (fn) => {
setLoading(true);
setResult("");
setOwner("");
try {
await fn();
} finally {
setLoading(false);
}
};

return (
<div style={styles.container}>
<div style={styles.card}>
<h1 style={styles.heading}>Rootstock Name Service Lookup</h1>

<input
style={styles.input}
value={domain}
onChange={(e) => setDomain(e.target.value)}
placeholder="Enter domain like testing.rsk"
/>

<div style={styles.buttonGroup}>
<button
style={styles.button}
disabled={loading}
onClick={() =>
wrap(async () => {
const addr = await getAddressByRns(domain);
setResult(addr || "No RSK address found");
})
}
>
Resolve RSK
</button>

<button
style={styles.buttonAlt}
disabled={loading}
onClick={() =>
wrap(async () => {
const addr = await getResolverAddress(domain);
setResult(addr || "No resolver found");
})
}
>
Get Resolver
</button>
</div>

<button
style={styles.buttonWide}
disabled={loading}
onClick={() =>
wrap(async () => {
const available = await checkAvailability(domain);
setResult(
available ? "✅ Domain is available" : "❌ Domain is taken"
);
})
}
>
Check Availability
</button>

<div style={{ display: "flex", gap: "8px" }}>
<input
style={{ ...styles.input, flex: 1 }}
value={subdomain}
onChange={(e) => setSubdomain(e.target.value)}
placeholder="Enter subdomain"
/>
<button
style={styles.buttonAlt}
disabled={loading}
onClick={() =>
wrap(async () => {
const available = await checkSubdomain(domain, subdomain);
setResult(
available
? "✅ Subdomain is available"
: "❌ Subdomain is taken"
);
})
}
>
Check Subdomain
</button>
</div>

<button
style={styles.buttonWide}
disabled={loading}
onClick={() =>
wrap(async () => {
const ownerAddr = await getOwner(domain);
if (ownerAddr) {
setOwner(ownerAddr);
setResult("Owner found");
} else {
setResult("No owner found");
}
})
}
>
Get Owner
</button>

{owner && (
<div style={styles.ownerBox}>
<strong>Owner:</strong> {owner}
</div>
)}

<div style={styles.resultBox}>{loading ? "Loading..." : result}</div>
</div>
</div>
);
}

9. Run the Application

Start the development server:

npm start

Your application should now be running at:

http://localhost:3000.

This is how your UI should look:

RNS dApp

It should also function properly, as shown in the demo below:

Troubleshooting

if you experience buffer error like this in your browser:

getAddressByRns error: ReferenceError: Buffer is not defined
at hash (bundle.js:2:1)
at e.hashDomain (bundle.js:2:1)
at t.<anonymous> (bundle.js:2:1)
at bundle.js:2:1
at Object.next (bundle.js:2:1)
at bundle.js:2:1
at new Promise (<anonymous>)
at n (bundle.js:2:1)
at t.addr (bundle.js:2:1)
at App.js:177:1

This mean the library uses a dependency that requires Buffer to be available globally. If you are using in a browser environment, you need to the following:

  1. Install the dependency:
npm install -D buffer
  1. If you have a webpack.config.js file in your project root, add this configuration to it:
const webpackConfig = {
resolve: {
fallback: {
buffer: require.resolve('buffer/'),
},
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
],
};

OR add this to your index.js file

window.Buffer = window.Buffer || require('buffer/').Buffer;

OR if you're using Next.js, add it to next.config.js:

module.exports = {
webpack: (config) => {
config.resolve.fallback = { buffer: require.resolve('buffer/') };
return config;
},
};

Extending the dApp

Adding Write Operations

To enable write operations (registering domains, setting addresses), you'll need to:

  1. Connect a wallet (MetaMask)
  2. Use a Signer instead of Provider
import { ethers } from 'ethers';

// Connect wallet
const web3Provider = new ethers.providers.Web3Provider(window.ethereum);
await web3Provider.send("eth_requestAccounts", []);
const signer = web3Provider.getSigner();

// Initialize SDK with signer for write operations
const rns = new RNS(ADDRESSES.registry, signer);
const addrResolver = new AddrResolver(ADDRESSES.registry, signer);

Adding Domain Registration

See the RNS SDK documentation for complete examples of domain registration using RSKRegistrar.commitToRegister() and RSKRegistrar.register().

Conclusion

You've built a functional RNS lookup dApp using @rsksmart/rns-sdk. This demonstrates:

  • Initializing SDK classes (RNS, AddrResolver, RSKRegistrar)
  • Creating a custom React hook for RNS operations
  • Resolving domains to addresses
  • Checking domain and subdomain availability
  • Querying domain ownership and resolver information

Resources

Last updated on by Owanate Amachree