Notus API
Authentication

Web3Auth

Web3Auth enables social login flows for Web3 using familiar credentials like Google, Apple ID, and email. It abstracts away private key management, making it easier to onboard mainstream users into decentralized applications.

Why combine Web3Auth with Notus API?
By integrating with Notus API, you can turn a Web3Auth login into a fully functional, gasless smart wallet experience:

  • Authenticate users with social login
  • Create and register ERC-4337 smart wallets
  • Eliminate seed phrases and manual wallet management
  • Sponsor gas fees with Paymasters or allow ERC-20 token payments

This allows users to sign in with a Google account and immediately use onchain features without buying tokens or installing wallets.

This guide will help you integrate Notus API with Web3Auth in a Next.js project. By the end of this tutorial, you will set up Web3Auth, retrieve the user's wallet, register a Smart Wallet, generate a swap quote, and execute a UserOperation.

Eager to get started? Check out our GitHub repository for a complete example and see how to integrate Notus API into your project.

How to Use Web3Auth with Notus API

Let’s walk through the full integration step by step.

Start a Next.js Project

First, create a new Next.js project. Run the following command in your terminal:

npx create-next-app@latest
pnpm dlx create-next-app@latest
yarn dlx create-next-app@latest
bun x create-next-app@latest

This will scaffold a new Next.js application, setting up a basic React-based environment for building your dApp.

Install viem

We’ll use viem, a library for blockchain interactions, to simplify our integration. Install it by running:

npm i viem
pnpm add viem
yarn add viem
bun add viem

Install web3Auth

npm i @web3auth/base @web3auth/ethereum-provider @web3auth/modal
pnpm add @web3auth/base @web3auth/ethereum-provider @web3auth/modal
yarn add @web3auth/base @web3auth/ethereum-provider @web3auth/modal
bun add @web3auth/base @web3auth/ethereum-provider @web3auth/modal

Simple Web3Auth Setup

Configure Web3Auth by initializing the required modules and settings. Replace clientId with your actual Web3Auth client ID.

import { CHAIN_NAMESPACES, WEB3AUTH_NETWORK } from "@web3auth/base";
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-provider";
import { Web3Auth, Web3AuthOptions } from "@web3auth/modal";

const clientId =
  "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ";

const chainConfig = {
  chainNamespace: CHAIN_NAMESPACES.EIP155,
  chainId: "0xaa36a7",
  rpcTarget: "https://rpc.ankr.com/eth_sepolia",
  ticker: "ETH",
  tickerName: "Ethereum",
  logo: "https://cryptologos.cc/logos/ethereum-eth-logo.png",
};

const privateKeyProvider = new EthereumPrivateKeyProvider({
  config: { chainConfig },
});

const web3AuthOptions: Web3AuthOptions = {
  clientId,
  web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
  privateKeyProvider,
};

const web3auth = new Web3Auth(web3AuthOptions);

export default function App() {
//...//
}

Initialize Web3Auth

Initialize Web3Auth to manage user sessions and connect to the blockchain.

import { useEffect, useState } from "react";
import { createWalletClient } from "viem";

export default function App() {
  const [account, setAccount] = useState<any | null>(null);
  const [loggedIn, setLoggedIn] = useState(false);
  const [externallyOwnedAccount, setExternallyOwnedAccount] = useState("");

  useEffect(() => {
    const init = async () => {
      try {
        await web3auth.initModal();
        if (!web3auth.provider) {
          return;
        }
        const account = createWalletClient({
          chain: polygon,
          transport: custom(web3auth.provider),
        });

        const [address] = await account.getAddresses();

        setExternallyOwnedAccount(address);

        setAccount(account);

        if (web3auth.connected) {
          setLoggedIn(true);
        }
      } catch (error) {
        console.error(error);
      }
    };

    init();
  }, []);

  //....//
}

Login with Web3Auth

Allow users to log in with Web3Auth and retrieve their externally owned account (EOA).

import { polygon } from "viem/chains";


const web3auth = new Web3Auth(web3AuthOptions);

export default function App() {
  const [account, setAccount] = useState<any | null>(null);
  const [loggedIn, setLoggedIn] = useState(false);
  const [externallyOwnedAccount, setExternallyOwnedAccount] = useState("");

  const login = async () => {
    const web3authProvider = await web3auth.connect();
    if (!web3authProvider) {
      return;
    }
    const account = createWalletClient({
      chain: polygon,
      transport: custom(web3authProvider),
    });
    const [address] = await account.getAddresses();

    setExternallyOwnedAccount(address);

    setAccount(account);
    if (web3auth.connected) {
      setLoggedIn(true);
    }
  };

  return <button onClick={login}>Login</button>;
}

Register or Query a Smart Wallet

Using the externally owned account (EOA) retrieved in the previous step, we can register or fetch the corresponding smart wallet details (Account Abstraction).

"use client";
//...//
export default function App() {
  const [accountAbstraction, setAccountAbstraction] = useState("");

  //.....//

  const getSmartWalletAddress = async () => {
    const FACTORY_ADDRESS = "0x0000000000400CdFef5E2714E63d8040b700BC24";
    let res = await fetch(`https://<baseUrl>/api/v1/wallets/register`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": "<api-key>",
      },
      body: JSON.stringify({
        externallyOwnedAccount: externallyOwnedAccount,
        factory: FACTORY_ADDRESS,
        salt: "0",
      }),
    });
    if (!res?.ok) {
      res = await fetch(
        `https://<baseUrl>/api/v1/wallets/address?externallyOwnedAccount=${externallyOwnedAccount}&factory=${FACTORY_ADDRESS}&salt=${0}`,
        {
          method: "GET",
          headers: {
            "x-api-key": "<api-key>",
          },
        }
      );
    }

    const data = await res.json();
    setAccountAbstraction(data.wallet.accountAbstraction);
  };

  return (
    <button onClick={() => getSmartWalletAddress()}>Get SmartWallet</button>
  );
}

A Factory is a smart contract responsible for creating smart wallets. It helps ensure that wallet addresses are consistent and predictable, even before the wallet is fully deployed. This simplifies wallet creation and management while maintaining compatibility with blockchain standards.

If you're new to the concept of factories, we recommend visiting the What is a Factory? page for a more detailed explanation.

Explanation:

This function interacts with the Notus API to either register or query a smart wallet based on the provided Externally Owned Account (EOA).

  • The salt is a unique value that, when combined with the EOA and factory, generates a deterministic smart wallet address. If no salt is provided, the default value is zero. Each distinct salt creates a new smart wallet for the same EOA and factory, allowing flexibility while ensuring consistent and predictable address generation.

  • The POST request is sent to register a smart wallet if it hasn’t been registered yet. The request uses the EOA, a factory address, and a salt value.

  • If the POST request fails (e.g., because the wallet is already registered), a fallback GET request retrieves the existing smart wallet details.

  • Once the smart wallet address is retrieved, it’s stored in the accountAbstraction state for further use.

Note: At this stage, the smart wallet address is not yet deployed onchain. Deployment happens automatically with the user's first onchain transaction (e.g., swap or transfer) via a UserOperation.

Generate a Swap Quote

Next, let’s request a swap quote for exchanging tokens:

"use client";

import { polygon } from "viem/chains";

//...//
export default function App() {
  const [accountAbstraction, setAccountAbstraction] = useState("");
  const [externallyOwnedAccount, setExternallyOwnedAccount] = useState("");

  const [quote, setQuote] = useState<any>();


  //...//

  const getSwapQuote = async () => {
    const swapParams = {
      payGasFeeToken: "<token-address>",
      tokenIn: "<token-address>",
      tokenOut: "<token-address>",
      amountIn: "5",
      walletAddress: accountAbstraction,
      toAddress: accountAbstraction,
      signerAddress: externallyOwnedAccount,
      chainIdIn: polygon.id,
      chainIdOut: polygon.id,
      gasFeePaymentMethod: "DEDUCT_FROM_AMOUNT",
    };
    const res = await fetch("https://<baseUrl>/api/v1/crypto/swap", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": "<api-key>",
      },
      body: JSON.stringify(swapParams),
    });

    if (!res.ok) return;

    const data = (await res.json()) as SwapQuote;


    setQuote(data);
  };

  return <button onClick={() => getSwapQuote()}>Swap Quote</button>;
}

If you are swapping from a native token (e.g., ETH, BNB, AVAX), use the following address as tokenIn: 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee. This ensures that the Notus API recognizes the asset as a native token.

Explanation:

The swapParams object contains details of the swap, including token addresses, wallet address, and fees.

The Notus API returns a swap quote, which is stored in the quote state.

Note: The payGasFeeToken field should contain the address of an ERC-20 token held in the smart wallet. This token will be used to pay both the partner’s transactionFeePercent and the gas fees for the UserOperation. In most cases, payGasFeeToken is the same as token, typically the token the user already holds in their wallet.

Sign and Execute the User Operation

Finally, let’s sign the swap quote and execute the User Operation:

A UserOperation is a pseudo-transaction used in Account Abstraction (ERC-4337). It defines actions for a smart contract account to execute and is sent to a dedicated mempool, where bundlers group and forward these operations to the EntryPoint contract for validation and execution. Unlike traditional transactions, it offers greater flexibility, such as paying fees in tokens or executing custom logic.

export default function App() {
  const [account, setAccount] = useState<any | null>(null);
  const [externallyOwnedAccount, setExternallyOwnedAccount] = useState("");

  const [quote, setQuote] = useState<any>();
  const [txHash, setTxHash] = useState("");


  const signingAndExecute = async () => {
    if (!quote?.swap.quoteId) return;

    const quoteId = quote.swap.quoteId;

    const signature = await account.signMessage({
      account: externallyOwnedAccount,
      message: {
        raw: quoteId,
      },
    });

    const res = await fetch("https://<baseUrl>/api/v1/crypto/execute-user-op", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": "<api-key>",
      },
      body: JSON.stringify({
        quoteId,
        signature,
      }),
    });

    if (!res.ok) return;

    const data = await res.json();

    setTxHash(data.userOpHash);
  };

  console.log(txHash);

  return (
    <button onClick={() => signingAndExecute()}>
      Signing user operation and execute
    </button>
  );
}

Explanation:

The quote is signed using the wallet’s private key.

The signed operation is sent to Notus for execution, and the resulting hash (userOpHash) confirms the transaction.

Signatures are validated on-chain, transactions won't be executed unless the EOA match