Provenance.io WalletConnect-JS
Library to interface with Provenance Wallet using WalletConnect.
Provenance is a distributed, proof of stake blockchain designed for the financial services industry.
For more information about Provenance Inc visit https://provenance.io
Table of Contents
- Installation
- Window messages
- WalletConnectContextProvider
- QRCodeModal
- useWalletConnect
- walletConnectService
- walletConnectState
- Web App
- Non React Setup
- Webpack 5 Issues
- Clone LocalStorage
- WalletConnect-js Status
Installation
Import the dependency
npm install @provenanceio/walletconnect-js --save
Importable items:
import {
WINDOW_MESSAGES,
WalletConnectContextProvider,
useWalletConnect,
} from "@provenanceio/walletconnect-js";
Window Messages
Each method will return a window message indicating whether it failed or was completed as well as the result
Note A: See walletConnectService
for all WINDOW_MESSAGES
based on method.
Note B: All of these are based off Node.js Event Emitters, read more on them here: Node.js Event Emitters
List of WINDOW_MESSAGES:
export const WINDOW_MESSAGES = {
// WalletConnect Connected
CONNECTED: 'FWC_CONNECTED',
// WalletConnect Disconnect
DISCONNECT: 'FWC_DISCONNECT',
// Send Message
SEND_MESSAGE_COMPLETE: 'SEND_MESSAGE_COMPLETE',
SEND_MESSAGE_FAILED: 'SEND_MESSAGE_FAILED',
// JWT
SIGN_JWT_COMPLETE: 'SIGN_JWT_COMPLETE',
SIGN_JWT_FAILED: 'SIGN_JWT_FAILED',
// Sign
SIGN_MESSAGE_COMPLETE: 'SIGN_MESSAGE_COMPLETE',
SIGN_MESSAGE_FAILED: 'SIGN_MESSAGE_FAILED',
};
Example Usage:
// (Example using sendMessage)
// Listen for complete/success
const successAction = (result) => {
console.log(`WalletConnectJS | Send Message Complete | Result: `, result);
};
walletConnectService.addListener(
WINDOW_MESSAGES.SEND_MESSAGE_COMPLETE,
successAction
);
// Listen for error/failure
const failAction = (result) => {
const { error } = result;
console.log(`WalletConnectJS | Send Message Failed | result, error: `, result, error);
};
walletConnectService.addListener(
WINDOW_MESSAGES.SEND_MESSAGE_FAILED,
failAction
);
// Unmount listeners once they are no longer needed (typically in useEffect return)
// Remove event listeners once no longer needed (Node: Each requires specific function to remove, see Note B above)
walletConnectService.removeListener(
WINDOW_MESSAGES.SEND_MESSAGE_COMPLETE,
successAction
);
walletConnectService.removeListener(
WINDOW_MESSAGES.SEND_MESSAGE_FAILED,
failAction
);
WalletConnectContextProvider
React context provider to supply state to every child within
-
Include as parent to all Components using
walletconnect-js
-
Optional: You may pass your own instance of walletConnectService into the context provider under the param
service
-
Usage Example (w/React.js):
// index.js ... ReactDOM.render( <WalletConnectContextProvider> <App /> </WalletConnectContextProvider>, document.getElementById('root') );
QRCodeModal
To start the connection from dApp to wallet you will need to initiate the connection using the QRCodeModal component.
- Takes in the following params:
walletConnectService
: Service pulled out ofuseWalletConnect()
hook (Required)devWallets
: Array of allowed dev wallets to connect into. (Optional)hideWallets
: Array of prod wallets to hide from user. (Optional)- For list of available wallets and their IDs see
src/consts/walletList.ts
- Usage:
// App.js import { useWalletConnect, QRCodeModal } from '@provenanceio/walletconnect-js'; ... export const App = () => { const { walletConnectService: wcs } = useWalletConnect(); ... return ( <QRCodeModal walletConnectService={wcs} devWallets={['figure_web_test', 'figure_mobile_test']} hideWallets={['figure_web', 'figure_mobile']} /> ) };
- Note: This modal is built with React.js and will only work within a react project. If you are not using React.js look through the
examples
folder to see how to initiate the connection without QRCodeModal manually.
useWalletConnect
React hook which contains walletConnectService
and walletConnectState
walletConnectService
-
Holds all main methods and functions to use WalletConnect service
-
connect
Connect a WalletConnect wallet
walletConnectService.connect({bridge, duration, noPopup}); // WINDOW_MESSAGE: CONNECTED
Param Type Required Default Example Info bridge string no "wss://figure.tech/service-wallet-connect-bridge/ws/external"
"wss://custom.bridge"
Custom bridge to connect into duration number no 1800
3600
Custom connection timeout in seconds noPopup boolean no false true Should the QRCodeModal popup automatically on connect call address string no ''
tp1knsxfnn0lq48mmnkfnkgtkk8qnxxdu0y2tklkh
Required address for dApp connection prohibitGroups boolean no false
true
Prohibit group accounts from connecting to this dApp -
disconnect
Disconnect current session
walletConnectService.disconnect(); // WINDOW_MESSAGE: DISCONNECT
-
generateAutoConnectUrl
Change the amount of connection time remaining for the currenct walletconnect session
walletConnectService.generateAutoConnectUrl({ url, duration, walletId });
Param Type Required Default Example Info url string yes - 'https://figure.com' URL to send user (with wcjs functionality) duration number no current connected wallet timeout 3600 Seconds to set future connection to walletId string no current connected wallet type id 'provenance_extension' Which wallet to auto-connect with -
resetConnectionTimeout
Change the amount of connection time remaining for the currenct walletconnect session
walletConnectService.resetConnectionTimeout(connectionTimeout);
Param Type Required Default Example Info connectionTimeout number no 1800 3600 Seconds to extend current session -
signJWT
Prompt user to sign a generated JWT
walletConnectService.signJWT(expire); // WINDOW_MESSAGES: SIGN_JWT_COMPLETE, SIGN_JWT_FAILED
Param Type Required Default Example Info expire number no 24 hours (Date.now() + 86400) 1647020269
Custom expiration date (ms) of JWT -
sendMessage
Pass through a custom base64 encoded message
walletConnectService.sendMessage({ message, description, method, gasPrice, feeGranter, feePayer, memo, timeoutHeight, extensionOptions, nonCriticalExtensionOptions, }); // WINDOW_MESSAGES: SEND_MESSAGE_COMPLETE, SEND_MESSAGE_FAILED
Param Type Required Default Example Info message string / array yes - 'CiwvcHJvdmVuYW5jZS5tZX...'
B64 encoded Message(s) to pass to wallet description string no 'Send Message'
'My Special Message'
Prompt title on mobile wallet method string no 'provenance_sendTransaction'
'provenance_sendTransaction'
Message method gasPrice object no { gasPrice: [Figure Default], gasPriceDenom: [Figure Default] }
{ gasPrice: 1337, gasPriceDenom: 'nhash' }
Optional gasPrice object, defaults to Figure values feeGranter string no - 'tp1knsxfnn0lq48mmnkfnkgtkk8qnxxdu0y2tklkh'
Specify a fee granter address feePayer string no - 'tp1knsxfnn0lq48mmnkfnkgtkk8qnxxdu0y2tklkh'
Specify a fee payer address memo string no - 'My special memo'
Specify a tx memo timeoutHeight number no - 3
Specify a tx timeoutHeight extensionOptions any[] no - ['CiwvcHJvdmVuYW5jZS5tZX...']
Specify tx extensionOptions nonCriticalExtensionOptions any[] no - ['CiwvcHJvdmVuYW5jZS5tZX...']
Specify tx nonCriticalExtensionOptions -
signMessage
Prompt user to sign a custom message
walletConnectService.signMessage(message); // WINDOW_MESSAGES: SIGN_MESSAGE_COMPLETE, SIGN_MESSAGE_FAILED
Param Type Required Default Example Info message hex string yes - 'My Custom Message'
Hex string message to send
walletConnectState
- Holds current walletconnect-js state values
initialState: { account: '', // Figure account uuid [string] address: '', // Wallet address [string] bridge: 'wss://figure.tech/service-wallet-connect-bridge/ws/external', // WalletConnect bridge used for connection [string] connected: false, // WalletConnect connected [bool] connectionEat: null, // WalletConnect expires at time [number] connectionIat: null, // WalletConnect initialized at time [number] connectionTimeout: 1800, // Default timeout duration (seconds) connector: null, // WalletConnect connector figureConnected: false, // Account and address both exist [bool] isMobile: false, // Is the connected browser a mobile device [bool] loading: '', // Are any methods currently loading/pending [string] newAccount: false, // Is this a newly created account peer: {}, // Connected wallet info [object] publicKey: '', // Wallet public key (base64url encoded) QRCode: '', // QRCode image data to connect to WalletConnect bridge [string] QRCodeUrl: '', // QRCode url contained within image [string] showQRCodeModal: false, // Should the QR modal be open [bool] signedJWT: '', // Signed JWT token [string] walletApp: '', // What type of wallet is this "provenance_extension" | "provenance_mobile" | "figure_web" walletInfo: {}, // Contains wallet coin, id, and name representedGroupPolicy: null, //Present when the wallet holder is acting on behalf of a group }
Web App
This package comes bundled with a full demos that you can run locally to test out the various features of walletconnect-js
.
To see how to initiate and run the different examples, look through the README.md
- Quick Start:
- Pull down the latest
walletconnect-js
. - Run
npm i
to install all the required node packages - Run
npm run start
to launch a localhost demo.
- Pull down the latest
Non React Setup
This package works without react and with any other javascipt library/framework (tested with vanilla js)
Using a CDN Import
You can find this package on https://unpkg.com/
: - Note: Change the version in the url as needed: https://unpkg.com/@provenanceio/walletconnect-js@2.0.0/umd/walletconnect-js.min.js
- Example use:
js <script src="https://unpkg.com/@provenanceio/walletconnect-js@2.0.0/umd/walletconnect-js.min.js"></script>
Using Imports
There are a few differences in getting setup and running: 1) Note Webpack 5 Issues 2) When connecting, you will need to manually generate the QR code image element (Component is only available to React.js apps) 3) Don't use the default imports (@provenanceio/walletconnect-js
), instead pull the service from @provenanceio/walletconnect-js/lib/service
4) Don't forget to set up event and loading listeners * Basic non-React.js example with
import { WalletConnectService, WINDOW_MESSAGES } from '@provenanceio/walletconnect-js/lib/service';
// Pull out the service (includes methods and state)
const walletConnectService = new WalletConnectService();
// Function to generate a QR code img element
const generateQRImgElement = () => {
// Pull `QRCode` from `WalletConnectService` state and use it as the element `src`
const QRCodeData = walletConnectService.state.QRCode;
const QRImage = document.createElement('img');
QRImage.src = QRCodeData;
return QRImage;
};
// Function to generate a custom button element
const Button = (content: string, id?: string, onClick?: () => void) => {
const button = document.createElement('button');
button.textContent = content;
button.id = id;
if (onClick) button.addEventListener('click', onClick);
return button;
};
// Initialize wcjs connection (builds qrCode into state)
const connect = async () => {
await walletConnectService.connect();
// Build QRCode img element
const QRImageElement = generateQRImgElement();
// Take this QR element and appendChild to wherever you wanted to render it
document.body.appendChild(QRImageElement);
// User can now scan the QR code to connect
}
// Manually disconnect from the wc-js session
const disconnect = async () => { await walletConnectService.disconnect(); };
// wc-js has returned a connection event, we should have data to use
const connectionEvent = (result) => {
const { address, publicKey } = walletConnectService.state;
document.body.innerHTML = `
<div>Status: Connected to wallet via ${result.connectionType}</div>
<div>Address: ${address}</div>
<div>PublicKey: ${publicKey}</div>
`;
document.body.appendChild(Button('Disconnect', 'disconnect', disconnect));
}
// Something has caused wc-js to return a disconnect event, we're no longer connected
const disconnectEvent = (result) => {
// Clear everything out, re-add the connect button
document.body.innerHTML = '';
document.body.appendChild(Button('Connect', 'connect', connect));
};
// Listen for specific walletconnect-js events
const setupEventListeners = () => {
// Connected
walletConnectService.addListener(WINDOW_MESSAGES.CONNECTED, connectionEvent)
// Disconnected
walletConnectService.addListener(WINDOW_MESSAGES.DISCONNECT, disconnectEvent)
}
// Initial function when page/app loads
const init = () => {
setupEventListeners();
document.body.appendChild(Button('Connect', 'connect', connect));
};
// Run on app load
init();
Webpack 5 Issues
If you are on Webpack version 5+ (Note: Create React App 5+ uses Webpack 5+) then you will likely encounter a message like this:
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
To fix this issue, add the following plugins and module rules to your webpack.config.js
:
const webpack = require('webpack');
...
module.exports = {
...
plugins: [
...
// Work around for Buffer is undefined:
// https://github.com/webpack/changelog-v5/issues/10
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
new webpack.ProvidePlugin({
process: 'process/browser',
}),
],
resolve: {
extensions: ['.js'],
fallback: {
crypto: require.resolve('crypto-browserify'),
buffer: require.resolve("buffer/"),
util: require.resolve("util/"),
stream: require.resolve("stream-browserify")
},
},
};
If using React Create App, you will need to install a new package called react-app-rewired
. After installing, swap your react-scripts
start command with react-app-rewired
start. Create a new file named config-overrides.js
in the root of your project as follows:
const webpack = require("webpack");
module.exports = function override(config) {
// Pull any existing fallbacks from config, or make a new fallback object
const fallback = config.resolve.fallback || {};
// Merge/add new webpack fallbacks
Object.assign(fallback, {
crypto: require.resolve("crypto-browserify"),
buffer: require.resolve("buffer/"),
stream: require.resolve("stream-browserify"),
util: require.resolve("util/"),
});
// Set new fallback into config
config.resolve.fallback = fallback;
// Combine new plugins with any existing plugins
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ["buffer", "Buffer"],
}),
]);
// Return updated/modified webpack config
return config;
};
Next, make sure to have the following packages added into your package.json
dependencies or devDependencies list:
"buffer",
"crypto-browserify",
"process",
"stream-browserify",
"util"
Quickly add these to your dependencies with this command:
npm i --save-dev buffer crypto-browserify process stream-browserify util
For more information/examples, look through the webDemo
files and structure which uses these methods.
Automatic localSession copy
Copy window.localStorage
values from one site to another (mainly, to localHost
)
- Run this command in console on the first page you with to copy from
copy(`Object.entries(${JSON.stringify(localStorage)})
.forEach(([k,v])=>localStorage.setItem(k,v))`);
- Paste result (clipboard should automatically have been filled) into target page console.
- Refresh page, storage values should be synced.
Status
This application is under heavy development. The upcoming public blockchain is the evolution of the private Provenance network blockchain started in 2018. Current development is being supported by Figure Technologies.