DEV Community

Cover image for Create an XRP Ledger Account with Web3Auth (Nextjs): A Step-by-Step Guide
Muhdsodiq Bolarinwa
Muhdsodiq Bolarinwa

Posted on

Create an XRP Ledger Account with Web3Auth (Nextjs): A Step-by-Step Guide

Introduction

XRP ledger is a decentralized blockchain that focus on cross-border payment, it is used to document financial transactions, and it is fast and low-cost effective. The XRP Ledger was created by the Ripple co-founder and CEO Chris Larsen. XRP Lede utilized XRP as its native cryptocurrency.

In this tutorial, we are implementing XRP Ledger with web3 auth which will generate a new account for us. This account can be used to perform transactions. It can be used to send tokens to any address on the XRP ledger. For users to get an XRPL account user need to authorize either their Google discord or other auth associated with the web3auth.

To get started with the project. We are going to be utilizing NextJs, Tailwind CSS and other web3 auth libraries.

*Open your terminal *

Navigate to the directory where you want your code to be

*Create a new Nextjs project *

npx create-next-app@latest my-project --typescript --eslint
Enter fullscreen mode Exit fullscreen mode

You should have something like this on your terminal

XRP ledger web3auth

After you successfully implement that you navigate to my-project

cd my-project

Enter fullscreen mode Exit fullscreen mode

When you cd my-project, You install tailwind css

npm install -D tailwindcss postcss autoprefixer
Enter fullscreen mode Exit fullscreen mode

XRP ledger web3auth

After it is successful you run this

npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

XRP ledger web3auth

You need to install some dependencies which we are going to utilize for our project

@web3auth/xrpl-provider - Web3Auth XRPL Provider can be used to interact with wallet or connected EVM compatible chain using RPC calls. refrence web3auth/xrpl-provider

@web3auth/openlogin-adapter - This adapter is a wrapper around the openlogin library from Web3Auth and enables the main social login features of Web3Auth. refrence web3auth/openlogin-adapter
@web3auth/modal - It provides the main class for using the default Web3Auth Modal.

@web3auth/base - Web3Auth Base contains the basic typescript types and interfaces for Web3Auth. This comes in handy by providing you with a standard way of importing the values you need to work with the SDKs. reference web3auth/base

@nice-xrpl/react-xrpl -React-xrpl is a set of hooks and components for creating applications using xrpl.js in React. such as getting wallet balance, sending XRP to another wallet address reference nice-xrpl/react-xrpl

You use yarn as your package installer or any package installer that you know such as npm and others.

yarn add @web3auth/xrpl-provider @web3auth/xrpl-provider @web3auth/openlogin-adapter @web3auth/openlogin-adapter @web3auth/modal @web3auth/base @nice-xrpl/react-xrpl
Enter fullscreen mode Exit fullscreen mode

After the installation, we can open our file directory in our vs code

You can start our development process

In your project directory, you need to create a utils folder and a components folder where we define our provider and other functions that you are going to call such as getting an account that was generated by the web3auth login with the XRP Provider Client.

In the root project directory if you are using src in your project structure you create utils folder if not create it in your root directory, not inside your app or pages folder.

Create a new folder name xrpLRPC.ts


//  You start by importing IProvider type from web3auth/base for our typescript
import { IProvider } from "@web3auth/base";

// You import some function which we will use to send input to xrp ledger 
import { convertStringToHex, Payment, xrpToDrops } from "xrpl"

// define  our class where we will put all our requests

export default class XrplRPC {
    // Declaring a private provider variable of type IProvider}
    private provider: IProvider;

     //You define a Constructor that initializes the provider variable with the provided argument
     constructor (provider: IProvider) {
        this.provider = provider
     }

     // Method to get accounts associated with the provider
     getAccounts = async (): Promise<any> => {
        try {
            // Request the accounts using the provider
            const accounts = await this.provider.request<never, string[]>({
                method: "xrpl_getAccounts", // Specify the method to get accounts
            });
            console.log(accounts, "accounts"); // Log the accounts for debugging purposes

            if (accounts) { // Check if any accounts were returned
                // Request account info for the first account in the list
                const accInfo = await this.provider.request({
                    method: "account_info", // Specify the method to get account info
                    params: [
                        {
                            account: accounts[0], // Use the first account
                            strict: false, // Non-strict mode allows for more lenient account info retrieval
                            ledger_index: "current", // Use the current ledger index
                            queue: true, // Include queued transactions
                        },
                    ],
                });
                return accInfo; // Return the account info
            } else {
                return "No account found, please report issues"; // Return an error message if no accounts are found
            }
        } catch (error) { // Handle any errors that occur
            console.error("Error", error); // Log the error
            return error; // Return the error
        }
     };


     // Method to get the balance of the first account
    getBalance = async (): Promise<any> => {
        try {
            // Request the accounts using the provider
            const accounts = await this.provider.request<string[], never>({
                method: "xrpl_getAccounts", // Specify the method to get accounts
            });

            if (accounts) { // Check if any accounts were returned
                // Request account info for the first account in the list
                const accInfo = (await this.provider.request({
                    method: "account_info", // Specify the method to get account info
                    params: [
                        {
                            account: accounts[0], // Use the first account
                            strict: true, // Strict mode ensures accurate account info
                            ledger_index: "current", // Use the current ledger index
                            queue: true, // Include queued transactions
                        },
                    ],
                })) as Record<string, Record<string, string>>;
                return accInfo.account_data?.Balance; // Return the account balance
            } else {
                return "No accounts found, please report this issue."; // Return an error message if no accounts are found
            }
        } catch (error) { // Handle any errors that occur
            console.error("Error", error); // Log the error
            return error; // Return the error
        }
    };


    // Method to get the address of the first account
    getAccountAddress = async (): Promise<any> => {
        try {
            // Request the accounts using the provider
            const accounts = await this.provider.request<string[], never>({
                method: "xrpl_getAccounts", // Specify the method to get accounts
            });

            if (accounts) { // Check if any accounts were returned
                // Request account info for the first account in the list
                const accInfo = (await this.provider.request({
                    method: "account_info", // Specify the method to get account info
                    params: [
                        {
                            account: accounts[0], // Use the first account
                            strict: true, // Strict mode ensures accurate account info
                            ledger_index: "current", // Use the current ledger index
                            queue: true, // Include queued transactions
                        },
                    ],
                })) as Record<string, Record<string, string>>;
                return accInfo?.account; // Return the account address
            } else {
                return "No accounts found, please report this issue."; // Return an error message if no accounts are found
            }
        } catch (error) { // Handle any errors that occur
            console.error("Error", error); // Log the error
            return error; // Return the error
        }
    }

    // Method to sign a message
    signMessage = async (): Promise<any> => {
        try {
            const msg = "Hello world this is tutorial on XRPL by Amityclev"; // Define the message to sign
            const hexMsg = convertStringToHex(msg); // Convert the message to a hexadecimal string
            const txSign = await this.provider.request<{ signature: string }, never>({
                method: "xrpl_signMessage", // Specify the method to sign a message
                params: {
                    signature: hexMsg, // Provide the hexadecimal message to be signed
                },
            });
            return txSign; // Return the signed message
        } catch (error) { // Handle any errors that occur
            console.log("error", error); // Log the error
            return error; // Return the error
        }
    };

    // Method to sign and send a transaction
    signAndSendTransaction = async (): Promise<any> => {
        try {
            // Request the accounts using the provider
            const accounts = await this.provider.request<never, string[]>({
                method: "xrpl_getAccounts", // Specify the method to get accounts
            });

            if (accounts && accounts.length > 0) { // Check if any accounts were returned and the list is not empty
                // Create the payment transaction object
                const tx: Payment = {
                    TransactionType: "Payment", // Specify the transaction type
                    Account: accounts[0] as string, // Use the first account as the sender
                    Amount: xrpToDrops(50), // Specify the amount to send, converting XRP to drops
                    Destination: "rM9uB4xzDadhBTNG17KHmn3DLdenZmJwTy", // Specify the destination address
                };
                // Request to submit the transaction
                const txSign = await this.provider.request({
                    method: "xrpl_submitTransaction", // Specify the method to submit a transaction
                    params: {
                        transaction: tx, // Provide the transaction object
                    },
                });
                return txSign; // Return the transaction signature
            } else {
                return "failed to fetch accounts"; // Return an error message if no accounts are found
            }
        } catch (error) { // Handle any errors that occur
            console.log("error", error); // Log the error
            return error; // Return the error
        }
    };

}
Enter fullscreen mode Exit fullscreen mode

You create a component folder if you have not created already.

Navigate to your component folder

create a file called Web3Authentication.tsx inside compoenent let utilize the various functions we already define in in xrpLRPC.ts

// 'use client' ensures that the following code is executed on the client side
'use client'
// Import necessary modules and components from various packages
import { XrplPrivateKeyProvider } from "@web3auth/xrpl-provider"; // Provides a private key provider for XRPL
import { Web3Auth } from "@web3auth/modal"; // Main Web3Auth module for authentication
import { getXrplChainConfig } from "@web3auth/base"; // Utility function to get XRPL chain configuration
import { CHAIN_NAMESPACES, UX_MODE, WEB3AUTH_NETWORK, IProvider } from "@web3auth/base"; // Various constants and interfaces for Web3Auth
import RPC from "../utils/xrpLRPC"; // Custom RPC class for XRPL interactions
import { OpenloginAdapter } from "@web3auth/openlogin-adapter"; // Adapter for Openlogin
import React, { useEffect, useState } from 'react'; // React hooks for managing state and side effects

// Client ID for Web3Auth which you can get from the web3 auth dashboard https://dashboard.web3auth.io/
const clientId = "BKVW17ohm6Mt-A6O_A633ECD5fOYUEkyOwmU5sdoEhtQFj0PiS28wxLO5DkmbqaAEsCgsd_BzqxhYjabDihcjLc";

// Main component for Web3 authentication
const Web3Authentication = () => {
  // Define state variables
  const [web3auth, setWeb3auth] = useState<Web3Auth | null>(null); // State for Web3Auth instance
  const [provider, setProvider] = useState<IProvider | null>(null); // State for provider instance
  const [loggedIn, setLoggedIn] = useState(false); // State to track if the user is logged in

  // Configuration for the XRPL chain
  const chainConfig = {
    chainNamespace: CHAIN_NAMESPACES.XRPL,
    chainId: "0x2",
    rpcTarget: "https://s.altnet.rippletest.net:51234/", // RPC endpoint for XRPL testnet
    wsTarget: "wss://s.altnet.rippletest.net:51233/", // WebSocket endpoint for XRPL testnet
    ticker: "XRP",
    tickerName: "XRPL",
    displayName: "xrpl testnet",
    blockExplorerUrl: "https://devnet.xrpl.org/", // Block explorer URL for XRPL testnet
  };

  // useEffect hook to initialize Web3Auth when the component mounts
  useEffect(() => {
    const init = async () => {
      try {
        // Initialize XRPL private key provider
        const xrplProvider = new XrplPrivateKeyProvider({
          config: {
            chainConfig: getXrplChainConfig(0x2), // Get XRPL chain configuration
          },
        });

        console.log(xrplProvider.config, "xrplProvider.config"); // Log provider configuration for debugging

        // Initialize Web3Auth with the specified configurations
        const web3auth = new Web3Auth({
          clientId,
          uiConfig: { // UI customization options
            appName: "W3A",
            theme: {
              primary: "red",
            },
            mode: "dark",
            logoLight: "https://web3auth.io/images/web3authlog.png",
            logoDark: "https://web3auth.io/images/web3authlogodark.png",
            defaultLanguage: "en",
            loginGridCol: 3,
            primaryButton: "externalLogin",
            uxMode: UX_MODE.REDIRECT,
          },
          web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET, // Web3Auth network configuration
          privateKeyProvider: xrplProvider, // XRPL private key provider
        });

        // Initialize and configure Openlogin adapter
        const openloginAdapter = new OpenloginAdapter({
          loginSettings: {
            mfaLevel: "optional",
          },
          adapterSettings: {
            uxMode: "redirect",
            whiteLabel: {
              logoLight: "https://web3auth.io/images/web3authlog.png",
              logoDark: "https://web3auth.io/images/web3authlogodark.png",
              defaultLanguage: "en",
            },
            mfaSettings: {
              deviceShareFactor: {
                enable: true,
                priority: 1,
                mandatory: true,
              },
              backUpShareFactor: {
                enable: true,
                priority: 2,
                mandatory: true,
              },
              socialBackupFactor: {
                enable: true,
                priority: 3,
                mandatory: true,
              },
              passwordFactor: {
                enable: true,
                priority: 4,
                mandatory: true,
              },
            },
          },
        });
        web3auth.configureAdapter(openloginAdapter); // Configure Web3Auth with the Openlogin adapter

        setWeb3auth(web3auth); // Set the Web3Auth instance in state

        await web3auth.initModal(); // Initialize the Web3Auth modal

        if (web3auth.connected) { // Check if already connected
          setProvider(web3auth?.provider); // Set the provider instance in state
          setLoggedIn(true); // Set logged in state to true
        }
      } catch (error) {
        console.error(error); // Log any errors that occur during initialization
      }
    };

    init(); // Call the init function
  }, []);

  // Function to handle user login
const login = async () => {
    if (!web3auth) {
      // If Web3Auth is not initialized, display a message in the UI console
      uiConsole("web3auth not initialized yet");
      return;
    }
    // Connect to Web3Auth and set the provider
    const webauthProvider = await web3auth.connect();
    setProvider(webauthProvider);
    setLoggedIn(true); // Update the state to indicate the user is logged in
  };

  // Function to authenticate the user
  const authenticateUser = async () => {
    if (!web3auth) {
      // If Web3Auth is not initialized, display a message in the UI console
      uiConsole("web3auth not initialized yet");
      return;
    }
    // Authenticate the user and get an ID token
    const idToken = await web3auth.authenticateUser();
    // Display the ID token in the UI console
    uiConsole(idToken);
  };

  // Function to get user information
  const getUserInfo = async () => {
    if (!web3auth) {
      // If Web3Auth is not initialized, display a message in the UI console
      uiConsole("web3auth not initialized yet");
      return;
    }
    // Retrieve user information from Web3Auth
    const user = await web3auth.getUserInfo();
    // Display the user information in the UI console
    uiConsole(user);
  };

  // Function to handle user logout
  const logout = async () => {
    if (!web3auth) {
      // If Web3Auth is not initialized, display a message in the UI console
      uiConsole("web3auth not initialized yet");
      return;
    }
    // Log out the user from Web3Auth
    await web3auth.logout();
    setProvider(null); // Reset the provider state
    setLoggedIn(false); // Update the state to indicate the user is logged out
  };

  // Function to get the accounts associated with the provider
  const getAccounts = async () => {
    if (!provider) {
      // If the provider is not initialized, display a message in the UI console
      uiConsole("provider not initialized yet");
      return;
    }
    // Create a new RPC instance with the provider
    const rpc = new RPC(provider);
    // Get the user accounts from the RPC instance
    const userAccount = await rpc.getAccounts();
    // Display the account information in the UI console
    uiConsole("Account info: ", userAccount);
  };

  // Function to get the balance of the user account
  const getBalance = async () => {
    if (!provider) {
      // If the provider is not initialized, display a message in the UI console
      uiConsole("provider not initialized yet");
      return;
    }
    // Create a new RPC instance with the provider
    const rpc = new RPC(provider);
    // Get the balance from the RPC instance
    const balance = await rpc.getBalance();
    // Display the balance in the UI console
    uiConsole("Balance", balance);
  };

  // Function to send a transaction
  const sendTransaction = async () => {
    if (!provider) {
      // If the provider is not initialized, display a message in the UI console
      uiConsole("provider not initialized yet");
      return;
    }
    // Create a new RPC instance with the provider
    const rpc = new RPC(provider);
    // Sign and send the transaction
    const result = await rpc.signAndSendTransaction();
    // Display the result of the transaction in the UI console
    uiConsole(result);
  };

  // Function to sign a message
  const signMessage = async () => {
    if (!provider) {
      // If the provider is not initialized, display a message in the UI console
      uiConsole("provider not initialized yet");
      return;
    }
    // Create a new RPC instance with the provider
    const rpc = new RPC(provider);
    // Sign the message
    const result = await rpc.signMessage();
    // Display the signed message in the UI console
    uiConsole(result);
  };

  // Function to get the account address
  const getAccountAddress = async () => {
    if (!provider) {
      // If the provider is not initialized, display a message in the UI console
      uiConsole("provider not initialized yet");
      return;
    }
    // Create a new RPC instance with the provider
    const rpc = new RPC(provider);
    // Get the account address
    const result = await rpc.getAccountAddress();
    // Display the account address in the UI console
    uiConsole(result);
  };

  // Function to get the wallet seed
//   const getWalletSeed = async () => {
//     if (!provider) {
//       // If the provider is not initialized, display a message in the UI console
//       uiConsole("provider not initialized yet");
//       return;
//     }
//     // Create a new RPC instance with the provider
//     const rpc = new RPC(provider);
//     // Get the wallet seed
//     const result = await rpc.getWalletSeed();
//     // Log the result in the console for debugging
//     console.log(result, "result: ");
//     // Display the wallet seed in the UI console
//     uiConsole(result);
//   };

  // Function to display messages in the UI console
  function uiConsole(...args) {
    // Find the HTML element to display the messages
    const el = document.querySelector("#console>p");
    if (el) {
      // Display the messages in the HTML element as a formatted JSON string
      el.innerHTML = JSON.stringify(args || {}, null, 2);
    }
  }

  // Component to display the view when the user is logged in
const loggedInView = (
    <>
      <div className="flex-container">
        <div>
          {/* Button to get user information */}
          <button onClick={getUserInfo} className="card">
            Get User Info
          </button>
        </div>
        <div>
          {/* Button to authenticate user and get ID token */}
          <button onClick={authenticateUser} className="card">
            Get ID Token
          </button>
        </div>
        <div>
          {/* Button to get the account address */}
          <button onClick={getAccountAddress} className="card">
            Address
          </button>
        </div>
        {/* <div>
          Button to get the wallet seed
          <button onClick={getWalletSeed} className="card">
            Seed
          </button>
        </div> */}
        <div>
          {/* Button to get user accounts */}
          <button onClick={getAccounts} className="card">
            Get Accounts
          </button>
        </div>
        <div>
          {/* Button to get the balance of the account */}
          <button onClick={getBalance} className="card">
            Get Balance
          </button>
        </div>
        <div>
          {/* Button to sign a message */}
          <button onClick={signMessage} className="card">
            Sign Message
          </button>
        </div>
        <div>
          {/* Button to send a transaction */}
          <button onClick={sendTransaction} className="card">
            Send Transaction
          </button>
        </div>
        <div>
          {/* Button to log out the user */}
          <button onClick={logout} className="card">
            Log Out
          </button>
        </div>
      </div>
      {/* Console to display messages */}
      <div id="console" style={{ whiteSpace: "pre-line" }}>
        <p style={{ whiteSpace: "pre-line" }}></p>
      </div>
    </>
  );

  // Component to display the view when the user is not logged in
  const unloggedInView = (
    <button onClick={login} className="card">
      Login
    </button>
  );

  // Main component to handle the Web3Auth authentication process
  return (
    <div>
      <div className="container">
        <h1 className="title">
          <a target="_blank" href="https://web3auth.io/docs/sdk/pnp/web/modal" rel="noreferrer">
            Web3Auth{" "}
          </a>
          & ReactJS XRPL Example
        </h1>

        {/* Display the loggedInView if logged in, otherwise display unloggedInView */}
        <div className="grid">{loggedIn ? loggedInView : unloggedInView}</div>

        <footer className="footer">
          {/* Link to the source code */}
          <a
            href="https://github.com/Web3Auth/web3auth-pnp-examples/tree/main/web-modal-sdk/blockchain-connection-examples/xrpl-modal-example"
            target="_blank"
            rel="noopener noreferrer"
          >
            Source code
          </a>
          {/* Button to deploy with Vercel */}
          <a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FWeb3Auth%2Fweb3auth-pnp-examples%2Ftree%2Fmain%2Fweb-modal-sdk%2Fblockchain-connection-examples%2Fxrpl-modal-example&project-name=w3a-xrpl-modal&repository-name=w3a-xrpl-modal">
            <img src="https://vercel.com/button" alt="Deploy with Vercel" />
          </a>
        </footer>
      </div>
    </div>
  );

}

export default Web3Authentication;
Enter fullscreen mode Exit fullscreen mode

Open your terminal
Make sure you are in the project folder in your terminal
run

yarn run dev
Enter fullscreen mode Exit fullscreen mode

When you login with web3auth in the project, it generate you new a xrpl account, for you.
Noted the account is not activate until you send a token of 15 xrp to the address.

You can check this Demo video on how to send xrp the generated account

Top comments (3)

Collapse
 
fyodorio profile image
Fyodor

Hey Muhdsodiq, that's a nice one, but you mentioned a demo video — can you add it please? Would be cool to see it in action.

Also you can add syntax highlighting to your code snipets by adding typescript or ts (I believe) after the first three backticks 🎨 (or sh for terminal command snippets)

Collapse
 
amity808 profile image
Muhdsodiq Bolarinwa

Thanks for the feedback, sorry I did not understand what you meant by "Also you can add syntax highlighting to your code snipets by adding typescript or ts (I believe) after the first three backticks 🎨 (or sh for terminal command snippets)"

I will provide the link to the video demo working.

Collapse
 
fyodorio profile image
Fyodor

Check out this advice — dev.to/hoverbaum/how-to-add-code-h...