- Ethereum Gateway React Component
Civic provides a developer-friendly plug and play React Component as an easy way to request a Gateway Token from your dApp with minimal setup. It includes a Civic Pass status widget, the Identity Button; and an in-dApp modal to guide your users through the process.
You can find the official documentation hosted on Gitbook.
We have also created an example React project that shows how you would go about integrating the different features provided by our @civic/ethereum-gateway-react
component project on Github.
Please refer to the project on Github for a complete example.
npm i @civic/ethereum-gateway-react
or using yarn
yarn add @civic/ethereum-gateway-react
Surround any code that needs access to the gateway token with:
import {GatewayProvider} from "@civic/ethereum-gateway-react";
const YourDapp = () => {
// this example is using the WAGMI connector
const { connector, address } = useAccount();
const [wallet, setWallet] = useState<Wallet>();
// update the wallet if the connector or address changes
useEffect(() => {
if (!connector) return;
connector.getSigner().then(setWallet);
}, [connector, address]);
<GatewayProvider
gatekeeperNetwork={GATEKEEPER_NETWORK}
wallet={wallet}
>
// your dApp content
</GatewayProvider>
}
where:
-
wallet
is an initialised ethers wallet (at least 6.x) -
gatekeeperNetwork
is a gatekeeperNetwork address provided by the dApp, you can get choose a gatekeeper network to use from https://docs.civic.com/integration-guides/civic-idv-services/available-networks
You can convert a viem/wagmi walletClient to an ethers v6 interface using the following snippet:
import * as React from 'react';
import { WalletClient, useWalletClient } from 'wagmi';
import { BrowserProvider, JsonRpcSigner } from 'ethers';
export function walletClientToSigner(walletClient: WalletClient) {
const { account, chain, transport } = walletClient;
const network = {
chainId: chain?.id,
name: chain?.name,
ensAddress: chain?.contracts?.ensRegistry?.address,
};
const provider = new BrowserProvider(transport, network);
const signer = new JsonRpcSigner(provider, account?.address);
return signer;
}
/** Hook to convert a viem Wallet Client to an ethers.js Signer. */
export function useEthersSigner({ chainId }: { chainId?: number } = {}) {
const { data: walletClient } = useWalletClient({ chainId });
return React.useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]);
}
You can find further information at the wagmi documentation.
The component will automatically look for a gateway token on chain. To access it once created:
import { useGateway } from "@civic/ethereum-gateway-react";
const { gatewayToken } = useGateway()
If the wallet does not have a gateway token, request it using requestGatewayToken
:
const { requestGatewayToken } = useGateway()
For example, by connecting it to a button component:
<button onclick={requestGatewayToken}>Validate your wallet</button>
Or by using Identity Button provided:
import IdentityButton from '@civic/ethereum-gateway-react';
...
<IdentityButton />
The IdentityButton is a reference implementation of a UI component to communicate to your dApp users the current status of their Gateway Token, or Gateway Token request. It changes appearance with text and icons to indicate when the user needs to take action and can be clicked by the user at any point in the process. The initial click for a new user will initiate the flow for the user to create a new Gateway Token. Once the user has gone through KYC and submitted their Gateway Token request via the Civic compliance iFrame, any subsequent click will launch the Civic Pass iframe with a screen describing the current status of the flow.
The GatewayProvider
is a React component that gives children access to the GatewayContext through the useGateway
function. This component holds the state for a given gateway token or gateway token request.
export type GatewayProviderProps = {
// The wallet connected to the dApp
// It should have the type:
// {
// address: string | undefined;
// signer: Signer | undefined;
// };
// where signer is an ethers.js signer
wallet: Wallet;
// The gatekeeper network key (provided by Civic - see docs.civic.com)
gatekeeperNetwork: string;
// [Optional] Set Civic gatekeeper stage (testing only), defaults to 'prod'
stage?: string;
// [Optional] A react element that the dApp can wrap the iframe in to allow customer dApp styling
wrapper?: React.FC;
// [Optional] A url of your logo that will be shown, if set, during verification
logo?: string;
// [Optional] A redirect URL that can be used for deep-linking and mobile-web
redirectUrl?: string;
// [Optional] If this is set, the transaction must be signed by this payer, and the payer will
// be charged the cost of the pass.
// Leave unset (default) for the user to pay for the pass.
// If set, you must also set handleTransaction. See an example below.
payer?: string;
// [Optional] A callback that will invoked with a partially signed transaction for the user or payer to sign and send to the blockchain.
// Use this if you want to send the transaction to a backend to sign and send, or if you want to otherwise control how the transaction is signed and sent.
handleTransaction?: (transaction: TransactionRequest) => Promise<TransactionResponse>;
// [Optional] If set, the gatekeeper will send the transaction to the blockchain. Defaults to false.
// When true, `payer` must be false or unset, and `handleTransaction` must be unset.
// Note: This is only supported for custom passes.
gatekeeperSendsTransaction?: boolean;
// [Optional] A set of client options (see 'Client Options' below)
options?: Options;
// [Optional] For passes with expiry, set this value to prompt the user to refresh the pass this amount of seconds before the pass expires
expiryMarginSeconds?: number
// [Optional] When set to true, initiates the refresh flow if the user has an active token, even if the pass hasn't expired
forceRequireRefresh?: boolean;
};
The GatewayStatus
is an enum that reveals the state of the requested Gateway Token.
export enum GatewayStatus {
UNKNOWN,
CHECKING,
NOT_REQUESTED,
COLLECTING_USER_INFORMATION,
PROOF_OF_WALLET_OWNERSHIP,
IN_REVIEW,
REJECTED,
REVOKED,
FROZEN,
ACTIVE,
ERROR,
LOCATION_NOT_SUPPORTED,
VPN_NOT_SUPPORTED,
REFRESH_TOKEN_REQUIRED,
VALIDATING_USER_INFORMATION,
USER_INFORMATION_VALIDATED,
USER_INFORMATION_REJECTED,
}
The behaviour of the React component depends on the status of the token. For example, if the token status is NOT_REQUESTED
and the user triggers the react component, then the flow to collect the user's information and request a Civic Token will be started.
For other cases, like the token status FROZEN
, triggering the React component opens a dialog explaining to the user the current status and offering him to reach out to Civic is he requires assistance.
The table bellow lists all status:
Status | Description | Behaviour when triggered |
---|---|---|
UNKNOWN |
No user wallet is connected or no gatekeeper network set | None |
CHECKING |
The Identity Component is making the relevant requests to check the state of the token | None |
NOT_REQUESTED |
A token has not been requested | Opens the Civic Pass modal dialog and initiates the token request flow |
COLLECTING_USER_INFORMATION |
The Identity Component is in progress and can be resumed | Opens the Civic Pass modal dialog and resumes the token request flow |
IN_REVIEW |
The token has been requested and the Gatekeeper is reviewing the request | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
ACTIVE |
The token has been issued successfully and is active. | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
FROZEN |
The token has been frozen, for example because the user connected from a blocked IP | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
REJECTED |
The token requests has been rejected by the Gatekeeper | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
REVOKED |
The token has been revoked, for example because the user connected from a banned IP | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
LOCATION_NOT_SUPPORTED |
The user's location is not currently supported | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
VPN_NOT_SUPPORTED |
The user is using a proxy or VPN | Opens the Civic Pass modal dialog with a user-friendly explanation of the status. |
REFRESH_TOKEN_REQUIRED |
The user needs to refresh their gateway token | Opens the Civic Pass modal dialog and takes the user through the flow for refreshing their token. |
ERROR |
There was an error retrieving the Gateway Token or completing a vrification flow | Opens the Civic Pass modal dialog and the user can restart the process. |
VALIDATING_USER_INFORMATION |
The validation process is currently being reviewed | Opens the Civic Pass modal dialog and shows a user friendly message |
VALIDATION_PROCESS_IN_PROGRESS |
The validation process is still in progress | Opens the Civic Pass modal dialog and the user can resume the process |
USER_INFORMATION_VALIDATED |
The validation process has completed | Opens the Civic Pass modal dialog and completes the process |
USER_INFORMATION_REJECTED |
The validation process was rejected | Opens the Civic Pass modal explaing the reasons for the failure |
Any component wrapped by GatewayProvider
can access the state and useful functions of the GatewayProvider through this function.
Returns the current context values for the GatewayContext
by exposing the following properties.
export type GatewayProps = {
requestGatewayToken: () => Promise<void>, // starts off gateway token process
reinitialize: () => Promise<void> // reinitializes the GatewayClient instance
gatewayStatus: GatewayStatus, // normally a value from a React hook state, defaults to GatewayStatus.UNKNOWN
gatewayToken?: GatewayToken, // the current GatewayToken used in the dApp
gatewayTokenTransaction: string, // if broadcastTransaction is false, this will be populated with any transactions generated by the backend
pendingRequests?: PendingPayload; // an object containing ID(s) that a partner needs to resolve before a pass can be issued
}
const gatewayProps: GatewayProps = useGateway();
You can specify some options that affect things such as:
- the display behaviour of the Civic modal that the user interacts with,
- the blockchain polling frequency
export type Options = {
// whether the Civic modal should appear automatically if the Civic Pass token state changes
autoShowModal: boolean;
// debug | info | warn | error
logLevel: LogLevel;
chainPollingIntervalMs?: number; // How often to poll the blockchain for pass updates. Defaults to every 5 seconds.
};
By default, the user requesting the pass will pay transaction costs and the cost of the pass.
If you want to pay for the pass, you can set the payer
prop in the GatewayProvider
to the address of the payer.
You must also set handleTransaction
.
For example, to pay for the pass using a backend service:
// handle json serialisation of bigints
const serializer = (key: string, value: any) => typeof value === 'bigint' ? value.toString() : value
const handleTransaction = async (request: TransactionRequest) => {
// call your backend to sign and send the transaction
const response = await fetch('https://your-backend.com/sign-transaction', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request, serializer),
});
const transactionResponse = await response.json();
return transactionResponse;
};
return <GatewayProvider
gatekeeperNetwork={GATEKEEPER_NETWORK}
wallet={wallet}
payer={payer}
handleTransaction={handleTransaction}
>...</GatewayProvider>
Your backend code is up to you, but should look something like this (Warning - sample code - not for production):
import { TransactionRequest, Wallet } from 'ethers';
const provider = /* your ethers provider */;
const wallet = new Wallet('your-private-key').connect(provider);
app.post('/sign-transaction', async (req: Request<{}, {}, TransactionRequest>, res: Response) => {
const transactionRequest = req.body as TransactionRequest;
// Sign and send the transaction
// WARNING - Do NOT sign arbitrary transactions sent from an unsecured client. Your funds may be at risk.
// This code is for demonstration purposes only
const transactionResponse = await wallet.sendTransaction(transactionRequest);
// Send back the transaction response
res.json(transactionResponse);
});
If you want to force a user to refresh their pass before the pass has expired, the boolean value 'forceRequireRefresh' can be passed to the GatewayProviderProps, e.g. if you want the user to have to refresh if their pass is older than 1 day old:
const shouldForceRefresh = gatewayTokenExpiry < 1_DAY_FROM_NOW
<GatewayProvider wallet={...} gatekeeperNetwork={...} forceRequireRefresh={shouldForceRefresh}/>
This would cause the refresh flow to be initiated for a user with a pass that was created more than one day ago.
You can customise how the verification flow is displayed by providing a custom wrapper.
...
const customWrapperStyle: CSS.Properties = {
backgroundColor: 'rgba(0,0,0,1)',
position: 'fixed',
zIndex: 999,
width: '100%',
height: '100%',
overflow: 'auto',
paddingTop: '5%'
}
const customWrapperContentStyle: CSS.Properties = {
backgroundColor: '#fefefe',
margin: 'auto',
width: '90%',
position: 'relative',
}
export const CustomWrapper: React.FC = ({ children = null }) => {
return (
<div style={customWrapperStyle}>
<div style={customWrapperContentStyle}>
<img style={{ maxWidth: '20%' }} src={logo} className="app-logo" alt="logo"/>
{children}
</div>
</div>
)
}
Builds the app for production to the build
folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
Publishes a new version of the React Component. We use beta releases before releasing an official release in the following format (major).(minor).(patch)-beta.(build number)
. You can release a beta with the following command yarn add @civic/ethereum-gateway-react@beta
.
To publish a new version of the react-component, add a Git tag and use a prefix along with the desired version number e.g.
ethereum-rc-0.7.0
Note that this method should only be used for production versions, not beta tags.