This guide will walk you through creating a project that uses the AR.IO SDK to interact with ArNS names in a web environment. It provides all the steps and context needed to help you get up and running smoothly, allowing you to effectively use these technologies.
We will be using ARNext, a new framework based on Next.js, to simplify deployment to the Arweave permaweb. ARNext provides flexibility for deploying seamlessly to Arweave using an ArNS name, an Arweave transaction ID, or traditional services like Vercel—all without requiring major code modifications. This means you can deploy the same project across different environments with minimal effort.
The guide will focus on the following core functionalities of the AR.IO SDK:
Retrieving a List of All Active ArNS Names: Learn how to use the SDK to get and display a list of active ArNS names.
Querying Detailed Records for a Specific ArNS Name: Learn how to access detailed records for a specific ArNS name using its ANT (Arweave Name Token).
Updating and Creating Records on an ArNS Name: Learn how to modify and add records to an ArNS name, showcasing the capabilities of ANT for dynamic web content.
By the end of this guide, you will have a complete, functional project that not only demonstrates how to use the AR.IO SDK but also shows the ease and flexibility of deploying applications to the Arweave permaweb. Whether you are an experienced developer or just starting out, this guide will help you understand the key aspects of building and deploying on Arweave.
ARNext is a brand new framework that is still in development. It supports installation using npx, and you will need the proper Node version for the installation to be successful.
npxcreate-arnext-apparnext
You can then move your terminal into that newly created folder with:
cdarnext
or open the folder in an IDE like VSCode, and open a new terminal inside that IDE in order to complete the next steps.
Sanity Check
It is good practice when starting a new project to view it in localhost without any changes, to make sure everything is installed and working correctly. To do this, run:
npmrundev
or, if you prefer yarn:
yarndev
By default, the project will be served on port 3000, so you can access it by navigating to localhost:3000 in any browser. You should see something that looks like this:
With this complete, you are ready to move on to customizing for your own project.
Polyfills are used to provide missing functionality in certain environments. For example, browsers do not have direct access to a computer's file system, but many JavaScript libraries are designed to work in both browser and Node.js environments. These libraries might include references to fs, the module used by Node.js to interact with the file system. Since fs is not available in browsers, we need a polyfill to handle these references and ensure the application runs properly in a browser environment.
Polyfills are actually evil voodoo curse magic. No one understands what they are or how they work, but front end devs sell their souls to Bill Gates in exchange for their stuff working properly in browsers. The below polyfill instructions were stolen, at great personal cost, from one of these front end devs in order to save your soul. This is one of many convenient services offered by AR.IO
Installation
The below command will install several packages as development dependencies, which should be sufficient to handle most polyfill needs for projects that interact with Arweave.
With the polyfill packages installed, we need to tell our app how to use them. In NextJS, which ARNext is built on, this is done in the next.config.js file in the root of the project. The default config file will look like this:
This configuration allows the app to determine if it is being served via an Arweave transaction Id, or through a more traditional method. From here, we need to add in the additional configurations for resolving our polyfills. The updated next.config.js will look like this:
The first step in building your custom app is to remove the default content and create a clean slate. Follow these steps:
Update the Home Page
Navigate to pages > index.js, which serves as the main home page.
Delete everything in this file and replace it with the following placeholder:
exportdefaultfunctionHome() {}
Remove Unused Pages
The folder pages > posts > [id].js will not be used in this project. Delete the entire posts folder to keep the project organized and free of unnecessary files.
Create Header
Create a new components folder
Inside that, create a Header.js file, leave it blank for now.
Create Routes
Create a new file at components > ArweaveRoutes.js to handle routing between pages. Leave it simple for now.
Your project is now a blank slate, ready for your own custom design and functionality. This clean setup will make it easier to build and maintain your application as you move forward.
There are a few functions that we might end up wanting to use in multiple different pages in our finished product. So we can put these in a separate file and export them, so that other pages can import them to use. Start by creating a utils folder in the root of the project, then create 2 files inside of it:
auth.js: This will contain the functions required for connecting an Arweave wallet using ArConnect
/** * Connect to the Arweave wallet using ArConnect and request permissions. * @returns{Promise<string>} The active wallet address. */exportconstconnectWallet=async () => {awaitwindow.arweaveWallet.connect(["ACCESS_ADDRESS","SIGN_TRANSACTION","ACCESS_PUBLIC_KEY","SIGNATURE", ]);constaddress=awaitwindow.arweaveWallet.getActiveAddress();return address;};/** * Truncate a wallet address for display purposes. * @param{string} address - The wallet address to truncate. * @returns{string} The truncated address. */exportconsttruncateAddress= (address) => {return`${address.slice(0,3)}...${address.slice(-3)}`;};
arweave.js: This is where we will put most of our AR.IO SDK functions for interacting with Arweave
import { ARIO, ANT, ArconnectSigner } from"@ar.io/sdk/web";/** * Initialize ArIO and fetch all ArNS records. * @returns{Promise<Object>} All ArNS records. */exportconstfetchArNSRecords=async () => {constario=ARIO.init();let allRecords = [];let hasMore =true;let cursor;// Paginates through all records to get the full registry.while (hasMore) {constresponse=awaitario.getArNSRecords({ limit:1000,// You can adjust the limit as needed, max is 1000 sortBy:"name", sortOrder:"asc", cursor: cursor, }); allRecords = [...allRecords,...response.items]; cursor =response.nextCursor; hasMore =response.hasMore; }// console.log(allRecords);return allRecords;};/** * Initialize ANT with the given processId. * @param{string} processId - The processId. * @returns{Object} ANT instance. */exportconstinitANT= (processId) => {returnANT.init({ processId });};/** * Fetch detailed records, owner, and controllers for a given processId. * @param{string} contractTxId - The processId. * @returns{Promise<Object>} Detailed records, owner, and controllers. */exportconstfetchRecordDetails=async (processId) => {constant=initANT(processId);constdetailedRecords=awaitant.getRecords();constowner=awaitant.getOwner();constcontrollers=awaitant.getControllers();return { detailedRecords, owner, controllers };};/** * Set a new record in the ANT process. * @param{string} processId - The processId. * @param{string} subDomain - The subdomain for the record. * @param{string} transactionId - The transaction ID the record should resolve to. * @param{number} ttlSeconds - The Time To Live (TTL) in seconds. * @returns{Promise<Object>} Result of the record update. */exportconstsetANTRecord=async ( processId, name, transactionId, ttlSeconds) => {console.log(`Pid: ${processId}`);console.log(`name: ${name}`);console.log(`txId: ${transactionId}`);constbrowserSigner=newArconnectSigner(window.arweaveWallet);constant=ANT.init({ processId, signer: browserSigner });constresult=awaitant.setRecord({ undername: name, transactionId, ttlSeconds, });console.log(result);return result;};
We want the Header component to contain a button for users to connect their wallet to the site, and display their wallet address when Connected. To do this, we will use the functions we exported from the utils > auth.js file, and pass in a state and set state function from each page rendering the header:
import React from"react";import { connectWallet, truncateAddress } from"../utils/auth";/** * Header component for displaying the connect wallet button and navigation. * @param{Object} props - Component props. * @param{string} props.address - The connected wallet address. * @param{function} props.setAddress - Function to set the connected wallet address. */constHeader= ({ address, setAddress }) => {consthandleConnectWallet=async () => {try {constwalletAddress=awaitconnectWallet();setAddress(walletAddress); } catch (error) {console.error("Failed to connect wallet:", error); } };return (<div className="header"><button className="connect-wallet" onClick={handleConnectWallet}> {address ? `Connected: ${truncateAddress(address)}` : "Connect Wallet"}</button></div> );};exportdefault Header;
Our home page is going to fetch a list of all ArNS names and display them. To make this display cleaner and more organized, we are going to create a component to display the names as a grid.
Create a new file in components named RecordsGrid.js
import React from"react";import { Link } from"arnext";/** * RecordsGrid component for displaying a grid of record keys. * @param{Object} props - Component props. * @param{Array<string>} props.keys - Array of record keys to display. */constRecordsGrid= ({ keys }) => {return ( <divclassName="records-grid"> {keys.map((key, index) => ( <buttonkey={index}className="record-key"onClick={() => {console.log(`clicked on ${key}`); }} > {key} </button> ))} </div> );};exportdefault RecordsGrid;
This will take an individual ArNS record and display it as a button that logs the record name when clicked. We will update this later to make the button act as a link to the more detailed record page after we build that, which is why we are importing Link from arnext
Go back to pages > index.js and lets build out our home page. We want to fetch the list of ArNS names when the page loads, and then feed the list into the grid component we just created. Because there are so many names, we also want to include a simple search bar to filter out displayed names. We will also need several states in order to manage all of this info:
"use client";import { useEffect, useState } from"react";import Header from"@/components/Header";import { fetchArNSRecords } from"@/utils/arweave";import RecordsGrid from"@/components/RecordsGrid";exportdefaultfunctionHome() {const [arnsRecords,setArnsRecords] =useState(null); // State for storing all ArNS recordsconst [isProcessing,setIsProcessing] =useState(true); // State for processing indicatorconst [searchTerm,setSearchTerm] =useState("") // used to filter displayed results by search inputconst [address,setAddress] =useState(null); // State for wallet addressuseEffect(() => {constfetchRecords=async () => {constallRecords=awaitfetchArNSRecords();setArnsRecords(allRecords);setIsProcessing(false); };fetchRecords(); }, []);return ( <div> <Headeraddress={address} setAddress={setAddress} /> {isProcessing ? ("processing" ) : ( <div> <h2>Search</h2> <inputtype="text"value={searchTerm}className="search-bar"onChange= {(e) => {setSearchTerm(e.target.value)}} /> <RecordsGridkeys={arnsRecords.map((r) =>r.name).filter((key) =>key.toLowerCase().includes(searchTerm?.toLowerCase()))} /></div> )} </div> );}
NextJS, and ARNext by extension, supports dynamic routing, allowing us to create dedicated pages for any ArNS name without needing to use query strings, which makes the sharable urls much cleaner and more intuitive. We can do this by creating a page file with the naming convention [variable].js. Since we want to make a page for specific ArNS names we will create a new folder inside the pages folder named names, and then a new file pages > names > [name].js.
This will be our largest file so far, including different logic for the displayed content depending on if the connected wallet is authorized to make changes the the name. We also need to make the page see what the name being looked at is, based on the url. We can do this using the custom useParams function from ARNext.
When this page loads, it gets the name being queried by using useParams and our custom getStaticPaths and getStaticProps functions. It then uses the AR.IO sdk to get the process Id of the ANT that controls the name, and queries the ANT for its info and detailed records list.
Once the page has that info, it renders the ArNS name, its owner address, any addresses authorized to make changes, and every record that name contains. If the user has connected a wallet authorized to make changes, the page also renders input fields for each record for making those updates. It also provides the option to create an entirely new undername record.
Once your app is looking the way you want it, you can deploy it to the permaweb using Turbo. For this, you will need an Arweave wallet with some Turbo Credits. Make sure you don't place your keyfile for the wallet inside the project directory, or you risk it getting uploaded to Arweave by mistake.
In your terminal, run the command:
yarndeploy:turbo-w<path-to-your-wallet>
Make sure to replace <path-to-your-wallet> with the actual path to your Arweave wallet. This will create a static build of your entire project, upload it to Arweave, and print out in the terminal all of the details of the upload.
Find the section in the print out manifestResponse which will have a key named id. That will be the Arweave transaction id for your project.
You can view a permanently deployed version of your project at https://arweave.net/<transaction-id>