@civic/gateway-client-react
v1.2.4
Published
## Overview
Downloads
1,527
Maintainers
Keywords
Readme
Civic Gateway Client React
Overview
The Civic gateway-client-react is a chain-agnostic wrapper for user Civic pass creation and refresh that instantiates the gateway-client-core and provides UI elements and listeners to it to listen for Civic pass data collection, and on-chain events. The Civic data-collection IFrame is instantiated and updated using the flowParameters output from the core instance, and the core Gateway Status and Gateway Token (if available) from the core are made available to dApp developers via the useGateway hook. The gateway-client-react library is intended to be used along with a gateway-chain-implementation to form a working on-chain gateway-client component used to issue passes on a desired chain.
Getting Started
1. Setting up your React project
You must have at least React 18 installed in your npm project.
2. Instantiate the gateway context and pass it a chain implementation
Surround any code that needs access to the gateway token with a GatewayContext, passing all the required props together. The example below combines a solana chain implementation with the gateway-client-react:
import { GatewayProvider } from "@civic/solana-gateway-react";
import { Connection, clusterApiUrl } from '@solana/web3.js';
// this is the solana chain implementation, other chains are available
import { SolanaChainImplementation } from '@civic/solana-gateway-chain-client';
// these values memoised because some are dependant on one-another so they need to all change together if an input changes
const gatewayProviderProps = useMemo(
() => ({
wallet: wallet?.publicKey?.toBase58() ? { publicKey: wallet?.publicKey?.toBase58() } : undefined,
chainImplementation:
wallet?.publicKey && wallet?.signTransaction && stage && gatekeeperNetwork?.toBase58()
? new SolanaChainImplementation({
connection,
cluster,
publicKey: wallet.publicKey,
signTransaction: wallet.signTransaction,
signMessage: wallet.signMessage,
gatekeeperNetworkAddress: gatekeeperNetwork,
})
: undefined,
gatekeeperNetwork: gatekeeperNetwork?.toBase58(),
options,
}),
[
wallet?.publicKey?.toBase58(),
connection?.rpcEndpoint,
cluster,
gatekeeperNetwork?.toBase58(),
]
);
<GatewayProvider
wallet={gatewayProviderProps.wallet}
chainImplementation={gatewayProviderProps.chainImplementation}
stage={gatewayProviderProps.stage}
gatekeeperNetwork={gatewayProviderProps.gatekeeperNetwork}
>
{children}
</GatewayProvider>
where:
connection
is a Solana connection to any Solana network, recommended commitment of 'processed'cluster
is the Solana network to use, i.e. devnet, mainnet-beta, testnet. This defaults to mainnet-beta, so should be set if a different connection endpoint is used than mainnet-betawallet
containspublicKey
,connected
andsignTransaction
(see below)gatekeeperNetwork
is a Solana publicKey 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
3. Accessing or requesting the gateway token
The component will automatically look for a gateway token on chain. To access it once created:
import { useGateway } from "@civic/gateway-client-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 './lib/button/IdentityButton';
...
<IdentityButton />
4. IdentityButton behaviour
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.
API Documentation
GatewayProvider
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 address of the wallet connected to the dApp, can be undefined initially pre-user wallet connection
walletAddress: string | undefined;
// the gatekeeper network public key address
gatekeeperNetwork: string | undefined;
// An implementation conforming to the Chain interface, representing the blockchain cluster you are interacting with
// can be undefined during initialisation
chainImplementation: ChainClientInterface | undefined;
// [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] 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] set Civic gatekeeper stage (testing only), defaults to 'prod'
stage?: string;
// [Optional] this will be passed to the gatekeeper in the x-civic-client header
reactComponentVersion?: string;
// [Optional]a react element that the dApp can wrap the iframe in to allow customer dApp styling
wrapper?: React.FC;
// [Optional] the 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] A set of client options (see 'Client Options' below)
options?: Options;
// [Optional] for gatekeeper networks that have pass expiry, set this value to prompt the user to refresh the pass this amount of seconds before the pass expires
expiryMarginSeconds?: number;
// the partnerAppId provided by Civic, only for partners who have registered with Civic
partnerAppId?: string;
// [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;
// [Optional] the number of seconds after a transation has been sent to wait for a token before timing out.
// Defaults to 2 minutes
expectTokenTimeoutSeconds?: number;
// [Optional] for use-cases where you want the fastest possible user-flow, and don't need any up-front location checks, this can be set to true so that the client will not call the GK-API on load. Defaults to false
disableInitialGatekeeperLookup?: boolean;
};
GatewayStatus
The GatewayStatus
is an enum that reveals the state of the requested Gateway Token.
export enum GatewayStatus {
UNKNOWN = 0, // the wallet or gatekeeper network is not set
CHECKING = 1, // the blockchain is being queried to find an existing gateway token
NOT_REQUESTED = 2, // then token has not been requested
COLLECTING_USER_INFORMATION = 3, // the wallet owner is undergoing KYC
PROOF_OF_WALLET_OWNERSHIP = 4, // the user needs to confirm wallet ownership,
IN_REVIEW = 5, // the token has been requested and civic gatekeeper is reviewing the request
REJECTED = 6, // the token issuance request was rejected
REVOKED = 7, // the token has been revoked
FROZEN = 8, // the token has been frozen
ACTIVE = 9, // the token has been issued successfully and is in an active state
ERROR = 10, // something went wrong
LOCATION_NOT_SUPPORTED = 11, // location is not currently supported
VPN_NOT_SUPPORTED = 12, // vpn usage is not currently supported
REFRESH_TOKEN_REQUIRED = 13, // waiting for the user to complete the civic pass refresh flow
VALIDATING_USER_INFORMATION = 14, // validation process is currently being reviewed
USER_INFORMATION_VALIDATED = 15, // validation process has finished processing
USER_INFORMATION_REJECTED = 16, // validation process failed and needs to be restarted
}
useGateway
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();
Client options
You can specify some options that affect the display behaviour of the Civic modal that the user interacts with:
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;
// [Optional] When set to true, prevents the flow to automatically restart on user validation failure
// Defaults to false
disableAutoRestartOnValidationFailure?: boolean;
};
Triggering early pass refresh
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.
Paying for your customers' passes
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:
const handleTransaction = async (transaction: Transaction):Promise<string> => {
// serialize the transaction to send to your backend.
const serializedTransaction = Buffer.from(transaction.serialize({
requireAllSignatures: false,
})).toString('base64');
// 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({ transaction: serializedTransaction }),
});
// return the tx signature
const { signature } = await response.json();
return signature;
};
return <GatewayProvider
gatekeeperNetwork={GATEKEEPER_NETWORK}
wallet={wallet}
connection={connection}
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';
// Must match the public key set as the payer in the frontend
const keypair = /* your keypair */;
app.post('/sign-transaction', async (req: Request<{}, {}, { transction: string }>, res: Response) => {
// parse the request into a transaction
const { transaction } = await request.json();
const parsedTransaction = Transaction.from(Buffer.from(transaction, 'base64'));
// sign 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
parsedTransaction.partialSign(keypair);
// send the transaction.
const signature = await connection.sendRawTransaction(parsedTransaction.serialize(), {
preflightCommitment: "processed"
});
// wait for the transaction to confirm
const blockhash = await connection.getLatestBlockhash();
await connection.confirmTransaction({ signature, ...blockhash });
// return the tx signature
res.json({ signature });
});
Wrapper
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>
)
}
Contributing
yarn build
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!
yarn publish
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/solana-gateway-react@beta
.
Automated publishing
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.
solana-rc-0.7.0
Note that this method should only be used for production versions, not beta tags.