Invoice Dashboard

A dashboard for viewing and paying invoices in Request Network

Screenshot of @requestnetwork/invoice-dashboard 0.3.0

The Invoice Dashboard component allows end-users to view and pay an invoice in Request Network. It is built using Svelte but compiled to a Web Component, making it usable in any web environment, regardless of the framework.

Installation

To install the component, use npm:

npm install @requestnetwork/invoice-dashboard

Usage

Usage in React and Next.js

Follow the instructions below to add the Invoice Dashboard to a React or Next.js app.

Configure the invoice dashboard web component by creating a reference to it, setting its properties, and passing the reference as a prop.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/pages/index.tsx
import Head from "next/head";
import dynamic from "next/dynamic";
import { config } from "@/utils/config";
import { useAppContext } from "@/utils/context";
import { currencies } from "@/utils/currencies";
import { rainbowKitConfig as wagmiConfig } from "@/utils/wagmiConfig";
import { Spinner } from "@/components/ui";

const InvoiceDashboard = dynamic(
  () => import("@requestnetwork/invoice-dashboard/react"),
  { ssr: false, loading: () => <Spinner /> }
);

export default function InvoiceDashboardPage() {
  const { requestNetwork } = useAppContext();
  return (
    <>
      <Head>
        <title>Request Invoicing</title>
      </Head>
      <div className="container m-auto  w-[100%]">
        <InvoiceDashboard
          config={config}
          currencies={currencies}
          requestNetwork={requestNetwork}
          wagmiConfig={wagmiConfig}
        />
      </div>
    </>
  );
}

The web component supports any wallet connector module built on top of wagmi. This provides the flexibility to use any wagmi-compatible wallet connector, such as RainbowKit.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/wagmiConfig.ts
import {
  bsc,
  celo,
  base,
  fuse,
  zksync,
  fantom,
  coreDao,
  polygon,
  mainnet,
  sepolia,
  arbitrum,
  moonbeam,
  optimism,
  avalanche,
  gnosis,
} from "wagmi/chains";
import { http } from "wagmi";
import {
  coinbaseWallet,
  injectedWallet,
  ledgerWallet,
  metaMaskWallet,
  safeWallet,
  trustWallet,
  walletConnectWallet,
} from "@rainbow-me/rainbowkit/wallets";
import { getDefaultConfig } from "@rainbow-me/rainbowkit";

export const rainbowKitConfig = getDefaultConfig({
  appName: "Request Invoicing",
  projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID as string,
  chains: [
    bsc,
    celo,
    base,
    fuse,
    zksync,
    gnosis,
    fantom,
    coreDao,
    polygon,
    mainnet,
    sepolia,
    arbitrum,
    moonbeam,
    optimism,
    avalanche,
  ],
  transports: {
    [arbitrum.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_ARBITRUM_ONE ||
        "https://arbitrum.llamarpc.com"
    ),
    [avalanche.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_AVALANCHE || "https://avalanche.drpc.org"
    ),
    [base.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_BASE || "https://base.llamarpc.com"
    ),
    [bsc.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_BSC || "https://bsc.llamarpc.com"
    ),
    [celo.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_CELO || "https://forno.celo.org"
    ),
    [coreDao.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_CORE || "https://rpc.coredao.org"
    ),
    [fantom.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_FANTOM ||
        "https://endpoints.omniatech.io/v1/fantom/mainnet/public"
    ),
    [fuse.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_FUSE || "https://fuse.drpc.org"
    ),
    [mainnet.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_ETHEREUM || "https://eth.llamarpc.com"
    ),
    [polygon.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_POLYGON || "https://1rpc.io/matic"
    ),
    [moonbeam.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_MOONBEAM ||
        "https://moonbeam-rpc.publicnode.com"
    ),
    [optimism.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_OPTIMISM ||
        "https://optimism.llamarpc.com"
    ),
    [sepolia.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_SEPOLIA || "https://sepolia.drpc.org"
    ),
    [gnosis.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_XDAI || "https://gnosis.drpc.org"
    ),
    [zksync.id]: http(
      process.env.NEXT_PUBLIC_RPC_URL_ZKSYNCERA ||
        "https://mainnet.era.zksync.io"
    ),
  },
  wallets: [
    {
      groupName: "Recommended",
      wallets: [injectedWallet, metaMaskWallet, walletConnectWallet],
    },
    {
      groupName: "Others",
      wallets: [safeWallet, coinbaseWallet, ledgerWallet, trustWallet],
    },
  ],
  ssr: true,
});

Initialize the RequestNetwork object using an Ethers Signer or Viem WalletClient.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/initializeRN.ts
import { RequestNetwork } from "@requestnetwork/request-client.js";
import { Web3SignatureProvider } from "@requestnetwork/web3-signature";
import { getTheGraphClient } from "@requestnetwork/payment-detection";

export const initializeRequestNetwork = (setter: any, walletClient: any) => {
  try {
    const web3SignatureProvider = new Web3SignatureProvider(walletClient);

    const requestNetwork = new RequestNetwork({
      nodeConnectionConfig: {
        baseURL: "https://gnosis.gateway.request.network/",
      },
      signatureProvider: web3SignatureProvider,
      httpConfig: {
        getConfirmationMaxRetry: 120,
      },
      paymentOptions: {
        getSubgraphClient: (chain: string) => {
          // Ternary because cannot dynamically access environment variables in the browser
          const paymentsSubgraphUrl =
          chain === "arbitrum-one"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ARBITRUM_ONE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-arbitrum-one/api"
            : chain === "avalanche"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_AVALANCHE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-avalanche/api"
            : chain === "base"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BASE || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-base/api"
            : chain === "bsc"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_BSC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-bsc/api"
            : chain === "celo"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CELO || "https://api.studio.thegraph.com/query/67444/request-payments-celo/version/latest"
            : chain === "core"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_CORE || "https://thegraph.coredao.org/subgraphs/name/requestnetwork/request-payments-core"
            : chain === "fantom"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FANTOM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-fantom/api"
            : chain === "fuse"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_FUSE || "https://api.studio.thegraph.com/query/67444/request-payments-fuse/version/latest"
            : chain === "mainnet"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MAINNET || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-mainnet/api"
            : chain === "matic"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MATIC || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-matic/api"
            : chain === "moonbeam"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_MOONBEAM || "https://api.studio.thegraph.com/query/67444/request-payments-moonbeam/version/latest"
            : chain === "optimism"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_OPTIMISM || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-optimism/api"
            : chain === "sepolia"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_SEPOLIA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-sepolia/api"
            : chain === "xdai"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_XDAI || "https://api.studio.thegraph.com/query/67444/request-payments-xdai/version/latest"
            : chain === "zksyncera"
            ? process.env.NEXT_PUBLIC_PAYMENTS_SUBGRAPH_URL_ZKSYNCERA || "https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-zksyncera/api"
            : undefined;
          if (!paymentsSubgraphUrl) {
            throw new Error(
              `Cannot get subgraph client for unknown chain: ${chain}`
            );
          }
          return getTheGraphClient(chain, paymentsSubgraphUrl);
        },
      },
    });

    setter(requestNetwork);
  } catch (error) {
    console.error("Failed to initialize the Request Network:", error);
    setter(null);
  }
};

Use the config object to pass additional configuration options. Please replace the builderId with your own, arbitrarily chosen ID. This is used to track how many invoices your application creates.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/config.ts
import { IConfig } from "./types";

export const config: IConfig = {
  builderId: "request-network", // Replace with your builder ID, arbitrarily chosen, used to identify your app
  dashboardLink: "/",
  logo: "/assets/logo-sm.svg",
  colors: {
    main: "#0BB489",
    secondary: "#58E1A5",
  },
};

Use a context provider to reinitialize the Request Network instance when the wallet changes.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/context.tsx
import React, {
  createContext,
  useContext,
  useState,
  ReactNode,
  useEffect,
} from "react";
import { initializeRequestNetwork } from "./initializeRN";
import type { RequestNetwork } from "@requestnetwork/request-client.js";
import { useAccount, useWalletClient } from "wagmi";

interface ContextType {
  requestNetwork: RequestNetwork | null;
}

const Context = createContext<ContextType | undefined>(undefined);

export const Provider = ({ children }: { children: ReactNode }) => {
  const { data: walletClient } = useWalletClient();
  const { address, isConnected, chainId } = useAccount();
  const [requestNetwork, setRequestNetwork] = useState<RequestNetwork | null>(
    null
  );

  useEffect(() => {
    if (walletClient && isConnected && address && chainId) {
      initializeRequestNetwork(setRequestNetwork, walletClient);
    }
  }, [walletClient, chainId, address, isConnected]);

  return (
    <Context.Provider
      value={{
        requestNetwork,
      }}
    >
      {children}
    </Context.Provider>
  );
};

export const useAppContext = () => {
  const context = useContext(Context);
  if (!context) {
    throw new Error("useAppContext must be used within a Context Provider");
  }
  return context;
};

A list of custom currencies to extend the default currency list.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/utils/currencies.ts
import { Types } from "@requestnetwork/request-client.js";

export const currencies = [
  {
    symbol: "FAU",
    address: "0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C",
    network: "sepolia",
    decimals: 18,
    type: Types.RequestLogic.CURRENCY.ERC20,
  },
  {
    symbol: "ETH",
    address: "eth",
    network: "sepolia",
    decimals: 18,
    type: Types.RequestLogic.CURRENCY.ETH,
  },
];

Specify types to avoid TypeScript errors.

https://github.com/RequestNetwork/invoicing-template/blob/01e44755b274d17c0718ca03f077d68a9fe8baec/types.d.ts
import { IConfig } from "@/utils/types";
import { Config as WagmiConfig } from "wagmi";
import type { RequestNetwork } from "@requestnetwork/request-client.js";

declare global {
  namespace JSX {
    interface IntrinsicElements {
      "invoice-dashboard": InvoiceDashboardElement;
      "create-invoice-form": CreateInvoiceFormElement;
    }
  }
}

interface InvoiceDashboardElement {
  ref?: React.Ref<InvoiceDashboardProps>;
}

interface CreateInvoiceFormElement {
  ref?: React.Ref<CreateInvoiceFormProps>;
}

interface InvoiceDashboardProps extends HTMLElement {
  config: IConfig;
  wagmiConfig: WagmiConfig;
  requestNetwork: RequestNetwork;
  currencies: any;
}

interface CreateInvoiceFormProps extends HTMLElement {
  config: IConfig;
  wagmiConfig: WagmiConfig;
  requestNetwork: RequestNetwork;
  currencies: any;
}

Props

Prop
Type
Description

config

IConfig

Additional configuration parameters

config.builderId

string

Unique builder ID, arbitrarily chosen, used for metrics

config.dashboardLink

string

Path to dashboard page

config.logo

string

Path to logo file

config.colors.main

string

Color used for primary buttons and labels

config.colors.secondary

string

Color used for borders and accents

requestNetwork

The RequestNetwork instance

wagmiConfig

WagmiConfig

Wallet connector config

currencies

Currency[]

A list of custom currencies

Last updated

Was this helpful?