Quadrata Integration
  • INTRODUCTION
    • Introduction to Quadrata
    • Passport Attributes
  • HOW TO INTEGRATE
    • Quadrata SDK
      • Get Started Quickly
      • Advanced
        • Installation
        • SDK Configuration
        • Onboarding Example
        • Client Libraries
          • Client Configuration
          • Client Lazy Loading
          • Client Eager Loading
          • Client Helper Component
          • Client React Hooks
            • useOnboardStatus
          • Client Examples
            • With Wagmi Connect
            • KYC Only
            • KYB Only
            • All In One
        • API Libraries
          • API Configuration
          • API Service Options
          • API Service Libraries
            • Create Access Token
            • Create Privacy Access Token
            • Fetch Attribute Values
            • Fetch Onboard Status
            • Fetch Passport List
            • Fetch Privacy Data
            • Fetch Privacy Grants
            • Fetch Wallet Screening
            • Revoke Privacy Grants
    • Onboard users
      • Individual Passport Onboarding
        • 1. Installation
        • 2. API Authentication
        • 3. API Onboard Status
        • 4. QuadClient Package
        • 5. Privacy Data Permissions
        • 6. Full Example
      • Business Passport Onboarding
        • 1. Installation
        • 2. API Authentication
        • 3. QuadrataKyb Package
        • 4. Privacy Data Permissions
        • 5. Full Example
      • All-In-One Passport Onboarding
        • 1. Installation
        • 2. API Authentication
        • 3. QuadrataReact Package
        • 4. Full Example
    • Request Privacy Data
      • List of Privacy Data
      • Privacy Data Permissions
      • API Requests
        • How to sign API
          • Full Example
          • Generate ECDSA Key Pair
        • API Get Privacy Permissions
        • API Privacy Access Token
        • API Get Privacy Data
        • API Revoke Permissions
    • Query attributes
      • Via Smart Contract
        • Query a single attribute
        • Query multiple attributes
        • Query Helper
      • Via API
    • On-Chain Wallet Screening
      • How to sign API
        • Full Example
        • Generate ECDSA Key Pair
      • API Get On-Chain AML Score
    • Webhooks
      • Onboarding Webhooks
      • Ongoing Monitoring Webhooks
      • Webhook Request Signature
    • Burn Passports
  • additional information
    • Smart contracts
    • Quadrata Sandbox
    • Passport Issuers
    • Privileged Roles & Ownership
    • Constants
    • Flex Kit Attributes
      • Smart Contract Addresses
Powered by GitBook
On this page
  1. HOW TO INTEGRATE
  2. Onboard users
  3. All-In-One Passport Onboarding

4. Full Example

In this example we use rainbowkit and wagmi libraries to manage Web3 connectivity, you might use any other library.

All of our UI libraries support both Javascript and Typescript environments.

Types, interfaces, helper functions, and object maps can be found in and imported from the @quadrata/quadrata-react NPM package.

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { parseUnits } from 'viem';

// wagmi
import {
  useAccount,
  useSignMessage,
  useSimulateContract,
  useWaitForTransactionReceipt,
  useWriteContract
} from 'wagmi';

// Quadrata
import QUAD_PASSPORT_ABI from '@quadrata/contracts/abis/QuadPassport.json';
import {
    Page,
    PageKyb,
    PrivacyConsentScopeEnglishMap,
    PrivacyConsentScopeParamKey,
    QuadAttribute,
    QuadClientEnvironment,
    QuadClientMintParamsReadyCallback,
    QuadMintParamsBigNumbers,
    QuadSupportedChainId,
    QuadrataReact,
    QuadrataReactConfigShared,
    QuadrataReactConfigUser,
} from '@quadrata/quadrata-react';
import { AttributeStatus, QuadrataOnApplicationEndCallback } from '@quadrata/core-react';

// NOTE: find contract addresses at https://docs.quadrata.com/integration/additional-information/smart-contracts
const QUAD_PASSPORT_ADDRESS = '0x185cc335175B1E7E29e04A321E1873932379a4a0'; // Testnet

const QUADRATA_API_URL = 'https://int.quadrata.com/api'; // sandbox api

export interface AttributeOnboardStatusDto {
    data: {
        type: 'attributes';
        onboardStatus:{
            [attributeName: string]: {
                status: AttributeStatus;
                onboardedAt?: number;
                mintedOnchain?: boolean;
            };
        };
        offeringStatus?: {
            [attributeName: string]: {
                status: AttributeStatus;
                verifiedAt?: number;
            };
        };
        privacyStatus?: {
            [privacyPermission: string]: {
                status: 'ALLOWED' | 'REVOKED' | 'NEEDS_CONSENT';
                allowedAt?: number;
                revokedAt?: number;
                revokedReason?: string;
            };
        };
    };
}

function getAttributesToClaim(onboardStatus: any, isBypassMint: boolean) {
    const attributesToClaim = [];
    for (const attributeName in onboardStatus) {
        const { status, mintedOnchain } = onboardStatus[attributeName];
        if (
            (status !== AttributeStatus.READY && status !== 'NOT_APPLICABLE') ||
            (!isBypassMint && !mintedOnchain && status === AttributeStatus.READY)
        ) {
            attributesToClaim.push(attributeName as QuadAttribute);
        }
    }
    return attributesToClaim;
}

function checkConsentNeeded(privacyStatus: any) {
    let isConsentNeeded = false;
    if (privacyStatus) {
        for (const privacyScopeKey in privacyStatus) {
            const { status } = privacyStatus[privacyScopeKey];
            if (status !== 'ALLOWED') {
                isConsentNeeded = true;
                break;
            }
        }
    }
    return isConsentNeeded;
}

function apiFetchAttributesOnboardStatus(args: {
    accessToken: string;
    account: string;
    attributes: Array<QuadAttribute>;
    chainId?: number;
    privacyScopes?: Array<PrivacyConsentScopeParamKey>;
}): Promise<AttributeOnboardStatusDto> {
    const {
        accessToken,
        account,
        attributes,
        chainId,
        privacyScopes
    } = args;
    const attrQuery = attributes.map((attr) => attr.toLowerCase()).join(',');
    const privacyScopesQuery = privacyScopes ? privacyScopes?.join(',') : undefined;
    const queryStringParameters: Record<string, any> = {
        wallet: account,
        attributes: attrQuery
    };
    if (chainId) {
        queryStringParameters.chainId = chainId;
    }
    if (privacyScopesQuery) {
        queryStringParameters.privacyScopes = privacyScopesQuery;
    }
    const queryString = new URLSearchParams(queryStringParameters);
    return fetch(
        `${QUADRATA_API_URL}/v2/attributes/onboard_status?${queryString.toString()}`,
        {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${accessToken}`,
            },
            cache: 'no-cache',
        }
    ).then((response) => {;
        if (!response.ok) {
            throw new Error('Onboard status failed');
        }
        return response.json();
    }) as Promise<AttributeOnboardStatusDto>;
};

function parseOnboardStatusResponse(resp: AttributeOnboardStatusDto, isBypassMint: boolean = false) {
    const {
        data: { onboardStatus, privacyStatus, offeringStatus },
    } = resp;
    const attributesToClaim = getAttributesToClaim(onboardStatus, isBypassMint);
    const isConsentNeeded = checkConsentNeeded(privacyStatus);
    if (offeringStatus) {
        // merge attribute to attest from offeringStatus into attributesToClaim
        const attributesToAttest = getAttributesToClaim(offeringStatus, true);
        for (const attributeName of attributesToAttest) {
            if (!attributesToClaim.includes(attributeName)) {
                attributesToClaim.push(attributeName);
            }
        }
    }
    return { attributesToClaim, isConsentNeeded };
}

// Component
export const Quadrata: React.FC<{ accessToken: string }> = ({
    accessToken
}) => {
    // Config options
    const bypassMint: boolean = false;
    const requiredAttributes = [QuadAttribute.DID, QuadAttribute.AML];
    const requiredPrivacyScopes = ['FN','LN','EM','DOB'] as Array<PrivacyConsentScopeParamKey>;

    // State
    const [attributesToClaim, setAttributesToClaim] = useState<QuadAttribute[] | undefined>(undefined);
    const [isError, setError] = useState(false);
    const [isLoading, setIsLoading] = useState(false);
    const [privacyScopes, setPrivacyScopes] = useState<Array<PrivacyConsentScopeParamKey> | undefined>(undefined);
    const [showModal, setShowModal] = useState<boolean>(false);
    const [signature, setSignature] = useState<string>();
    const [signatureConsent, setSignatureConsent] = useState<string>();
  
    // Minting
    const [mintComplete, setMintComplete] = useState(false);
    const [mintError, setMintError] = useState<string>();
    const [mintParams, setMintParams] = useState<QuadMintParamsBigNumbers>();

    // wagmi hooks
    const { signMessageAsync } = useSignMessage();
    const {
        address: account,
        chain: { id: chainId } = { id: 0 },
        isConnecting,
        isDisconnected
    } = useAccount();

    const contractConfig = useMemo(() => {
        if (!mintParams) {
            return undefined;
        }
        return {
            abi: QUAD_PASSPORT_ABI,
            args: [mintParams.account, mintParams.params[0], mintParams.signaturesIssuers[0]],
            address: QUAD_PASSPORT_ADDRESS as `0x${string}`,
            value: (mintParams?.fee || parseUnits('0', 18)) as bigint, // note: assumes same as ether with 18 decimals
            functionName: 'setAttributesIssuer',
        };
    }, [mintParams]);

    useSimulateContract(contractConfig);

    const {
        data: transactionHash,
        writeContract,
        error: writeContractError
    } = useWriteContract();
    
    useWaitForTransactionReceipt({
        hash: transactionHash,
    });

    useEffect(() => {
        if (transactionHash) {
            // Setting mint to complete
            setMintComplete(true);
            // Resetting state
            setMintParams(undefined);
            setSignature(undefined);
        }
    }, [transactionHash]);
    
    useEffect(() => {
        if (writeContractError) {
            console.log('[Quadrata Integration]: Mint error: ', writeContractError);
            setMintError(writeContractError.message);
        }
    }, [writeContractError]);
    
    // Check which attributes to claim for a given wallet
    const { error: onboardStatusError, data: onboardStatusData } = useQuery({
        queryKey: ['QUAD_API_ONBOARD_STATUS', account, requiredAttributes, chainId, bypassMint, requiredPrivacyScopes],
        queryFn: () => {
            setIsLoading(true);
            return apiFetchAttributesOnboardStatus({
                accessToken,
                account,
                attributes: requiredAttributes,
                privacyScopes: requiredPrivacyScopes,
                chainId: !bypassMint && chainId ? chainId : undefined,
            }).catch((err) => {
                setError(true);
                throw err;
            });
        },
        enabled: !!account,
        gcTime: 0,
    });
    useEffect(() => {
        if (onboardStatusError) {
            console.error(`/onboard_status error : ${onboardStatusError}`);
            setError(true);
            setIsLoading(false);
            throw new Error(`/onboard_status error : ${onboardStatusError}`);
        }
        if (onboardStatusData) {
            const { attributesToClaim, isConsentNeeded } = parseOnboardStatusResponse(onboardStatusData, bypassMint);
            setAttributesToClaim(attributesToClaim);
            if (isConsentNeeded) {
                setPrivacyScopes(requiredPrivacyScopes);
            } else {
                setPrivacyScopes(undefined);
            }
            setError(false);
            setIsLoading(false);
        }
    }, [onboardStatusData, onboardStatusError]);

    // Handlers
    const handleOnApplicationEnd: QuadrataOnApplicationEndCallback = ({ status, error }) => {
        console.log('handleOnApplicationEnd:::status:::', status);
        console.log('handleOnApplicationEnd:::error:::', error);
    };
  
    const handleSign = async (message: string, isConsent: boolean) => {
        // User clicked the initial sign button
        // Signing the message and updating state.
        // will automatically navigate to the next step upon signature update
        if (account) {
            const signature = await signMessageAsync({ message });
            if (isConsent) {
                setSignatureConsent(signature);
            } else {
                setSignature(signature);
            }
        }
    };

    const handlePageChange = (page: Page) => {
        if (page === Page.INTRO && signature) {
            // Intro page navigation will get triggered when a different wallet is detected,
            // Resetting previous state if present
            setSignature(undefined);
            setMintParams(undefined);
        }
    };
  
    const handleMintClick = useCallback(() => {
        if (writeContract && contractConfig) {
            // Trying to mint passport
            writeContract(contractConfig);
        }
    }, [writeContract, contractConfig]);

    const handleMintParamsReady: QuadClientMintParamsReadyCallback = (mintParams) => {
        // Setting mint params to prepare write function
        setMintParams(mintParams);
    };

    // QuadrataReact should only be displayed if the wallet is connected
    if (!account) {
        return <p>Please connect your wallet</p>;
    }

    if (isError) {
        return <p>Error initializing onboard status</p>;
    }

    // QuadrataReact should only be displayed if attributes to claim or privacy scopes has data
    if (isConnecting || isLoading || !attributesToClaim) {
        return <p>Loading...</p>
    }
    if (attributesToClaim.length === 0 
        && (!privacyScopes || privacyScopes.length === 0)
    ) {
        return <p>Onboarding Completed</p>;
    }

    if (!showModal) {
        // Button to launch the quadrata application
        return (
            <button
                type="button"
                onClick={() => setShowModal(true)}
            >
                Launch Quadrata
            </button>
        );
    }

    // Quadrata all-in-one configuration
    const configShared: QuadrataReactConfigShared = {
        _debug: true,
        accessToken: accessToken,
        apiUrl: `${QUADRATA_API_URL}/v1`,
        children: undefined,
        className: 'custom__class__name',
        contactEmail: 'support@quadrata.com',
        darkMode: false,
        discordUrl: 'https://discord.gg/SR5Fc6BK',
        error: undefined,
        environment: QuadClientEnvironment.SANDBOX,
        protocolName: 'NewCo',  // Your company name goes here
        showSocialButtons: false,
    };
    const configUser: QuadrataReactConfigUser = {
        ...configShared,
        account: account || '',
        attributes: attributesToClaim,
        bypassMint: bypassMint,
        chainId: chainId,
        countriesUnavailable: undefined, // ['US','COUNTRY','CODE','LIST']
        mintComplete: mintComplete,
        mintError: mintError,
        offeringId: undefined, // set to unique id for accreditation re-attestation
        privacyScopes: privacyScopes,
        signature: signature,
        signatureConsent: signatureConsent,
        transactionHash: transactionHash,
    };

    // Onboarding user and/or business
    // QuadrataReact should only be displayed when the configs are ready
    return (
        <>
            {accessToken && account ? (
                <QuadrataReact
                    configBusiness={configShared}
                    configUser={configUser}
                    onApplicationEnd={handleOnApplicationEnd}
                    onHide={() => setShowModal(false)}
                    onMintClick={handleMintClick}
                    onMintParamsReady={handleMintParamsReady}
                    onPageChange={handlePageChange}
                    onSign={handleSign}
                ></QuadrataReact>
            ) : (
                <></>
            )}
        </>
    );
};

Previous3. QuadrataReact PackageNextRequest Privacy Data

Last updated 11 months ago