React submodule for Custody of digital assets
npm install @verified-network/verified-custodybash
npm install @verified-network/verified-custody
`
š» Usage Example
`jsx
import React from "react";
import { VerifiedCustody } from "@verified-network/verified-custody";
function App() {
return (
);
}
`
š ļø Build
This module is built with TypeScript and works for both JavaScript and TypeScript React/Vite projects.
All components are compiled and available under the dist/ directory.
The module exports React components and helper functions useful for changing UI based on user state or custom business logic.
š Main Components & Workflows
The complete custody workflow component that handles new and existing users through multiple steps.
`jsx
`
š First Time Users (FTU)
Full workflow for first-time users, consisting of:
Starting point where users enter phone number or email to receive an OTP (One-Time Password).
Where new and existing users verify or resend OTP.
Users either:
Create a new WebAuth Passkey to secure their account.
Authenticate with existing Passkey and get redirected accordingly.
Reset/Update their existing passkey
New user accounts are created here after verifying phone/email.
New users add co-signers and define signing rules:
Up to 5 co-signers can be added with a minimum of 2 required signers.
š Returning Users
Existing users enter their PIN to:
Connect wallet/account to an application.
Accept or reject co-signer invitations.
Recover lost account/wallet keys.
Sign account recovery transactions.
Sign regular transactions, etc.
š Usage Details & Component Props
Component Props Types
`jsx
export interface VerifiedCustodyProps {
action?: VerifiedWalletAction;
actionText?: string;
origin?: string;
title?: string;
chainId?: number;
vaultData?: VerifiedWalletVault;
txData?: VerifiedWalletTx;
reqVaultData?: VerifiedWalletVault;
delayPageElement?: React.JSX.Element;
delayPageStyle?: React.CSSProperties;
helperFunctions?: VerifiedCustodyHelpers;
}
export type VerifiedWalletAction =
| "connect_wallet"
| "getPk"
| "invitation"
| "signRecovery"
| "completeRecovery"
| "eth_sendTransaction";
export type VerifiedWalletVault = {
vaultId: string,
hashedVaultId?: string,
hashedVaultPin?: string,
CoSignerId?: string,
vId?: string,
vPh?: string,
cId?: string,
address?: string,
channel?: "sms" | "email",
pk?: string,
};
export type VerifiedWalletTx = {
id: string,
};
export interface VerifiedCustodyHelpers {
sendCoSignerInvitation: (
channel: "sms" | "email",
coSignerId: string,
creatorId: string,
hashedCreatorPin: string
) => Promise;
sendCreatorConfirmation: (
channel: "sms" | "email",
creatorId: string,
coSignersList: [],
requiredSigners: number
) => Promise;
sendCreatorInitiation: (
channel: "sms" | "email",
creatorId: string,
hashedCreatorPin: string,
txId: number,
requiredSigners: number
) => Promise;
sendCreatorSigned: (
channel: "sms" | "email",
creatorId: string,
coSignerId: string
) => Promise;
sendCreatorCompleted: (
channel: "sms" | "email",
creatorId: string,
coSignerId: string,
vaultPinHashed: string,
txId: number
) => Promise;
sendCreatorAcceptance: (
channel: "sms" | "email",
creatorId: string,
coSignerId: string
) => Promise;
sendCreatorRejection: (
channel: "sms" | "email",
creatorId: string,
coSignerId: string
) => Promise;
}
`
Prop Descriptions
action:
Optional. Can be one of:
"connect_wallet" | "getPk" | "invitation" | "signRecovery" | "completeRecovery" | "eth_requestAccounts" | "eth_sendTransaction"
If omitted, the component handles account creation or login for new/existing users.
helperFunctions:
An object containing functions that handle messaging and notifications (email/SMS) for co-signers and creators.
Example:
sendCoSignerInvitation must take four parameters:
channel: "sms" | "email"
coSignerId: string (email or phone number of the co-signer)
phone number must be in format +{countryCode}{10DigitNumber} e.g. +1098765432
creatorId: string (email or phone number of the wallet creator)
hashedCreatorPin: string
It returns a Promise resolving to true on successful message delivery, otherwise false.
š Full Workflow Example
`jsx
import React from "react";
import { VerifiedCustody } from "@verified-network/verified-custody";
function App() {
const myCustomSendCoSignerInvitation = async (
channel: "sms" | "email",
coSignerId: string,
creatorId: string,
hashedCreatorPin: string
): Promise => {
try {
if (channel === "sms") {
// send SMS to coSignerId with creatorId and hashedCreatorPin
return true;
} else if (channel === "email") {
// send email to coSignerId with creatorId and hashedCreatorPin
return true;
}
return false;
} catch (err) {
return false;
}
};
const myCustomSendCreatorConfirmation = async (
channel: "sms" | "email",
creatorId: string,
coSignersList: [],
requiredSigners: number
): Promise => {
try {
if (channel === "sms") {
// send SMS showing coSigners and signing rules to creatorId
return true;
} else if (channel === "email") {
// send email showing coSigners and signing rules to creatorId
return true;
}
return false;
} catch (err) {
// If reverted for any reason return false
return false;
}
};
// Similarly define other helper functions: myCustomsendCreatorInitiation, myCustomSendCreatorSigned, etc.
return (
helperFunctions={{
sendCoSignerInvitation: myCustomSendCoSignerInvitation,
sendCreatorConfirmation: myCustomSendCreatorConfirmation,
sendCreatorInitiation: myCustomsendCreatorInitiation,
sendCreatorSigned: myCustomSendCreatorSigned,
sendCreatorCompleted: myCustomSendCreatorCompleted,
sendCreatorAcceptance: myCustomSendCreatorAcceptance,
sendCreatorRejection: myCustomSendCreatorRejection,
}}
/>
);
// You can also set action prop for different user flows, e.g. action="connect_wallet"
}
`
š First Time Users
`jsx
import React from "react";
import { FTUPage } from "@verified-network/verified-custody";
function App() {
const myCustomSendCoSignerInvitation = async (
channel: "sms" | "email",
coSignerId: string,
creatorId: string,
hashedCreatorPin: string
): Promise => {
// Implementation as above
};
const myCustomSendCreatorConfirmation = async (
channel: "sms" | "email",
creatorId: string,
coSignersList: [],
requiredSigners: number
): Promise => {
// Implementation as above
};
return (
helperFunctions={{
sendCoSignerInvitation: myCustomSendCoSignerInvitation,
sendCreatorConfirmation: myCustomSendCreatorConfirmation,
sendCreatorInitiation: myCustomsendCreatorInitiation,
sendCreatorSigned: myCustomSendCreatorSigned,
sendCreatorCompleted: myCustomSendCreatorCompleted,
sendCreatorAcceptance: myCustomSendCreatorAcceptance,
sendCreatorRejection: myCustomSendCreatorRejection,
}}
/>
);
}
`
š Existing/Returning Users
`jsx
import React from "react";
import { EnterPinPage } from "@verified-network/verified-custody";
function App() {
const myCustomSendCoSignerInvitation = async (
channel: "sms" | "email",
coSignerId: string,
creatorId: string,
hashedCreatorPin: string
): Promise => {
// Implementation as above
};
const myCustomSendCreatorConfirmation = async (
channel: "sms" | "email",
creatorId: string,
coSignersList: [],
requiredSigners: number
): Promise => {
// Implementation as above
};
return (
helperFunctions={{
sendCoSignerInvitation: myCustomSendCoSignerInvitation,
sendCreatorConfirmation: myCustomSendCreatorConfirmation,
sendCreatorInitiation: myCustomsendCreatorInitiation,
sendCreatorSigned: myCustomSendCreatorSigned,
sendCreatorCompleted: myCustomSendCreatorCompleted,
sendCreatorAcceptance: myCustomSendCreatorAcceptance,
sendCreatorRejection: myCustomSendCreatorRejection,
}}
/>
);
}
`
š React(Create React App) Setup and Fixes
ā
1. Initialize the Project
`jsx
npx create-react-app my-app
cd my-app
`
ā
2. Install react-app-rewired
`jsx
npm install react-app-rewired --save-dev
OR
yarn add --dev react-app-rewired
`
ā
3. Install other dependencies for node polyfils
`jsx
npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify vm-browserify buffer process
OR
yarn add crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify vm-browserify buffer process
`
note: the above dependencies are needed dependencies to make node related packages work, more dependencies can be added to handle any other nodeJs polyfills error.
ā
4. Override create-react-app webpack configuration
In the root folder of your project, create a new file named 'config-overrides.js', and add:
`jsx
//config-overrides.js
const webpack = require("webpack");
module.exports = function override(config) {
const fallback = config.resolve.fallback || {};
Object.assign(fallback, {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
assert: require.resolve("assert"),
http: require.resolve("stream-http"),
https: require.resolve("https-browserify"),
os: require.resolve("os-browserify"),
url: require.resolve("url"),
path: require.resolve("path-browserify"),
vm: require.resolve("vm-browserify"),
fs: false,
});
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp:
/^react-native$|^expo-font$|^@react-native-async-storage\/async-storage$/,
})
);
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ["buffer", "Buffer"],
}),
]);
config.module.rules = [
...config.module.rules,
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
];
config.resolve.fallback = fallback;
config.ignoreWarnings = [/Failed to parse source map/];
return config;
};
`
config-overrides.js' code snippet instructs webpack how to resolve node js related dependencies that may not be available on browser side but are required. It also instruct webpack to ignore react-native related dependencies that may break our react app.
ā
5. Modify package.json Scripts
In your package.json, replace this:
`json
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
`
With this:
`json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
}
`
note: the above scripts changes ensure your app is handled by react-app-rewired scripts instead of react-scripts this way the webpack configuration will be acknowledged.
ā
6. Import and Initialize Verified-Custody-Module and Start the application
Using the instruction on Verified Custody Module Usage initialize the module to suit your app logic,
You can start the application using:
`jsx
npm start
`
React(Create React App) Folder Structure
`pgsql
my-app/
āāā node_modules/
āāā public/
āāā src/
ā āāā App.js
ā āāā index.js
āāā config-overrides.js
āāā package.json
āāā README.md
`
š React Native(Using Expo) Setup and Fixes
ā
1. Initialize the Project
`jsx
npx create-expo-app my-rn-app
cd my-rn-app
`
ā
2. Install required dependencies
`jsx
npm react-native-get-random-values expo-font @react-native-async-storage/async-storage
OR
yarn add react-native-get-random-values expo-font @react-native-async-storage/async-storage
`
ā
3. Install other dependencies for node polyfils
`jsx
npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify vm-browserify buffer process
OR
yarn add crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url path-browserify vm-browserify buffer process
`
note: the above dependencies are required as external/peer dependencies for the module to work on react-native.
ā
4. Modify the (tabs) index.js or index.ts
In the app/(tabs) folder, open the index.js or index.ts file and add:
`jsx
//index.js or index.ts
import "react-native-get-random-values";
OR
require("react-native-get-random-values")
};
`
note the react-native-get-random-values import needs to be on the first line of the file so this is import early before the custody module is used. The react-native-get-random-values is very important for node packages like crypto and various hashing/encryption functions to work.
ā
6. Import and Initialize Verified-Custody-Module and Start the application
Using the instruction on Verified Custody Module Usage initialize the module to suit your app logic,
You can start the application using:
`jsx
npm start(if your script is setup)
OR
npx expo start
OR
npx expo run:android (to start the application on android)
OR
npx expo run:ios (to start the application on ios)
`
React Native(With Expo) Folder Structure
`pgsql
my-rn-app/
āāā node_modules/
āāā public/
āāā app/
ā āāā (tabs)
ā āāā index.ts or index.js
āāā package.json
āāā README.md
`
š Web Extension Setup and Fixes
Web Extension has various setup procedures, any of the setup you decide to use will work fine with Verify Custody Module.
It is important to know that Web extension are to be built like react app and therefore during build react native related dependencies should be ignored or set as external.
This can be done using plugin on webpack.
For Example:
`jsx
plugins: [
{
name: "node-and-react-native-polyfills",
setup(build) {
// treat react-native as external don't build
build.onResolve({ filter: /^react-native$/ }, () => {
return { external: true };
});
build.onResolve({ filter: /^expo-font$/ }, () => {
return { external: true };
});
build.onResolve(
{ filter: /^@react-native-async-storage\/async-storage$/ },
() => {
return { external: true };
}
);
...Other polyfills
},
}
]
``