Skip to content

EligibilityWidget Integration in React

Here is how to integrate the Eligibility SDK widget into a typical React app.

Prerequisites

  • react (v18.x or higher)
  • react-dom (v18.x or higher)
  • viem (v2.x or higher)
  • wagmi (v2.x or higher)
  • @tanstack/react-query (v5.x or higher)
npm install @reown/appkit @reown/appkit-adapter-wagmi wagmi viem @tanstack/react-query

More Information

Overview

  1. API Key Requirement for Security: The SDK is Issuer Claim credential and onboarding flow restricted by an API key. To obtain an API key, please contact Averer Customer Support.

Basic Flow

  1. Set up a state to track modal and proof status.
  2. Use the EligibilityWidget and pass onSuccess + backend config.
  3. Display verified claims once proof is received.

Example

App.tsx
import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
import type { AppKitNetwork } from "@reown/appkit/networks";
import { defineChain } from "@reown/appkit/networks";
import { EligibilitySDKProvider } from "@redbellynetwork/eligibility-sdk";

import Home from "/Home";

// 🔹 Get your `projectId` from https://cloud.reown.com
export const projectId = import.meta.env.VITE_PROJECT_ID || "your-project-id";

if (!projectId) {
  throw new Error("Project ID is not defined");
}

// 🔹 Define Redbelly Network Configuration
const staging = defineChain({
  id: 153,
  caipNetworkId: `eip155:153`,
  chainNamespace: "eip155",
  name: "Redbelly Network",
  nativeCurrency: {
    decimals: 18,
    name: "Redbelly Token",
    symbol: "RBNT",
  },
  rpcUrls: {
    default: {
      http: ["https://governors.testnet.redbelly.network/"],
    },
  },
});

// 🔹 Define Available Networks
export const networks = [staging] as [AppKitNetwork, ...AppKitNetwork[]];

// 🔹 Initialize Wagmi Adapter for Wallet Integration
export const wagmiAdapter = new WagmiAdapter({
  projectId,
  networks,
});

const queryClient = new QueryClient();

// 🔹 Initialize AppKit
createAppKit({
  adapters: [wagmiAdapter],
  projectId,
  networks,
  metadata: {
    name: "Eligibility SDK Example",
    description: "Eligibility SDK with Redbelly Network",
    url: "https://reown.com",
    icons: ["https://avatars.githubusercontent.com/u/179229932"],
  },
  themeMode: "light",
  themeVariables: {
    "--w3m-accent": "#000000",
  },
});

//  EligibilitySDKProvider: Wrap other element inside this, it will initilize the EligibilitySdk and hooks

export function App() {
  return (
    <WagmiProvider config={wagmiAdapter.wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        {/* wrap the application code inside EligibilitySDKProvider it will enable the hooks realated to network and EligibilitySdk widget */}
        <EligibilitySDKProvider
          config={{
            network: "staging", // Options: "mainnet" | "testnet" | "staging"
            apiKey: "enter-your-api-key", // Connect with Averer Customer Support to obtain your API key
          }}
        >
          <appkit-button />
          <Home />
        </EligibilitySDKProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

export default App;
Home.tsx
import { useAppKitAccount } from "@reown/appkit/react";

import {
  EligibilityWidget,
  protocol,
  useHasChainPermission,
} from "@redbellynetwork/eligibility-sdk";

export const Home = () => {
  const { address, isConnected } = useAppKitAccount();
  const { data: hasPermission } = useHasChainPermission(address!);
  const [openModal, setOpenModal] = useState(false);
  const [profile, setProfile] = useState<{
    userDID?: string;
    sessionId?: string;
    proof?: unknown;
    status?: string;
  } | null>(null);

  const onSuccessHandler: EligibilitySdk["onSuccess"] = (data) => {
    setProfile({
      userDID: data.userDID,
      sessionId: data.sessionId,
      proof: data.proof,
      status: data.status,
    });
  };

  // Custom query handler to call the backend and return session data
  const queryHandler: EligibilitySdk["config"]["queryHandler"] = async () => {
    // ⚠️ Note: Keep the query array minimal.
    // QR codes can only encode ~3 KB of data reliably.
    // Large or multiple queries may exceed the limit and cause QR rendering to fail.
    const query: Array<protocol.ZeroKnowledgeProofRequest> = [
      // {
      //   id: 1,
      //   circuitId: "credentialAtomicQuerySigV2",
      //   query: {
      //     allowedIssuers: ["*"],
      //     type: "KYCAgeCredential",
      //     context:
      //       "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld",
      //     skipClaimRevocationCheck: true,
      //     credentialSubject: { birthday: { $lt: 20000101 } },
      //   },
      // },
      {
        id: 1,
        circuitId: "credentialAtomicQuerySigV2",
        query: {
          allowedIssuers: ["*"],
          type: "AMLCTFCredential",
          context:
            "https://raw.githubusercontent.com/redbellynetwork/receptor-schema/refs/heads/main/schemas/json-ld/AMLCTFCredential.jsonld",
          skipClaimRevocationCheck: true,
          credentialSubject: {
            amlCheckStatus: { $eq: "passed" },
          },
        },
      },
    ];
    const res = await fetch("https://example.com/auth-request", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        flowName: "Investor Eligibility Check",
        scope: query,
      }),
    });
    const data = await res.json();
    return {
      request: data.request,
      sessionId: data.sessionId,
    };
  };

  // Custom status handler to check the session status
  const authStatusHandler: EligibilitySdk["config"]["authStatusHandler"] =
    async (sessionId) => {
      const res = await fetch(`https://example.com/status/${sessionId}`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      });
      const data = await res.json();
      return {
        status: data.status,
        proof: data?.proof,
        error: data?.error,
      };
    };

  return (
    <>
      <h2>Home page</h2>
      {openModal ? (
        <button onClick={() => setOpenModal(true)}>Close Eligibility</button>
      ) : (
        <button onClick={() => setOpenModal(false)}>Open Eligibility</button>
      )}
      <div>
        <p>proof status: {profile?.status}</p>
        <p>proof reveiled value</p>
        {profile.proof?.body.scope.map((item, index) =>
          item.vp && item.vp.verifiableCredential?.credentialSubject
            ? Object.entries(item.vp.verifiableCredential.credentialSubject)
                .filter(([key]) => key !== "@type")
                .map(([key, value], subIndex) => (
                  <div key={`${index}-${subIndex}`}>
                    <strong>{key}</strong>
                    <span>{value.toString()}</span>
                  </div>
                ))
            : null
        )}
        <p>chain permission {hasPermission ? "true" : "false"}</p>
      </div>
      {openModal && (
        <EligibilityWidget
          onSuccess={onSuccessHandler}
          config={{
            queryHandler: queryHandler,
            authStatusHandler: authStatusHandler,
          }}
        />
      )}
    </>
  );
};

⚠️ IMPORTANT: Replace "*" in allowedIssuers array with specific, authorized issuer DIDs. Refer to the "Authorized Issuers' DIDs for Redbelly Chains" section in "API Reference" for the correct DIDs for Mainnet, Testnet, or Staging.

📘 You can define selective disclosure queries by leaving the credentialSubject object empty eg: credentialSubject: { birthday: { } }.

🔗 The SDK opens a QR code which the Privado wallet scans, submits proof, and then the frontend polls the backend using authStatusHandler(sessionID) internally.

⚠️ QR Code Size Limitations Authorization requests embedded in QR codes must stay small enough to fit within QR standards.

•   Keep your query array minimal (1–2 queries when possible).
•   Avoid adding large or multiple credential subjects in a single request.
•   Oversized requests may cause the QR encoder to fail with errors such as “Data too long”.