@makerx/use-wallet v3.0.0

UseWallet v2
@txnlab/use-wallet is a React library that provides a simplified, consistent interface for integrating multiple Algorand wallets into your decentralized applications (dApps).
Overview
With UseWallet's hooks and utility functions, you can:
- Easily add or remove wallet support with a few lines of code
- Configure each wallet provider as needed for your application
- Allow users to easily switch between active accounts and wallet providers
- Sign and send transactions
- Restore sessions for returning users
It provides an abstraction layer that unifies the initialization, connection, and transaction signing logic, eliminating the need to interact with each wallet's individual API.
UseWallet supports most Algorand wallet providers, including Defly, Pera, Daffi, and Exodus (see Supported Wallet Providers for the full list).
Version 2.x introduces WalletConnect 2.0 support.
Table of Contents
- Live Examples
- Requirements
- Installation
- Initializing Providers
- The
useWalletHook - Type Definitions
- Connect Menu
- Displaying Account Details
- Signing and Sending Transactions
- Checking Connection Status
- Supported Wallet Providers
- Legacy Wallet Support
- Provider Configuration
- WalletConnect 2.0 Support
- Migration Guide
- Local Development
- Support
- Used By
- License
Live Examples
Storybook demo - https://txnlab.github.io/use-wallet
Next.js example
NFDomains - https://app.nf.domains/
Requirements
Since this library uses React Hooks, your app will need to be using React 16.8 or higher.
Installation
Commands shown below use npm install but you can use yarn add or pnpm add instead.
First, install the library
npm install @txnlab/use-walletIf you haven't already, install the Algorand JS SDK
npm install algosdkFinally, install any peer dependencies for the wallets you wish to support. For example, to support Defly, Pera, and Daffi wallets:
npm install @blockshake/defly-connect @perawallet/connect @daffiwallet/connectInitializing Providers
In the root of your app, initialize the WalletProvider with the useInitializeProviders hook.
This example initializes Defly, Pera, Daffi and Exodus wallet providers. The default node configuration (mainnet via AlgoNode) is used. See Provider Configuration for more options.
You can initialize your providers in two ways:
Static Import - This is the standard way of importing modules in JavaScript. In this method, the import statement is at the top of the file and the modules are imported when the file loads. This is done by passing the clientStatic property.
Dynamic Import - With the dynamic import() syntax, you can load modules on demand by calling a function. This can greatly reduce the initial load time of your app by only loading modules when they are needed. This is done by passing the getDynamicClient property which must be a function that returns a promise that resolves to the client.
Note: For each provider, either clientStatic or getDynamicClient must be passed, not both.
Here is an example of both:
Static Import Example
import React from 'react'
import { WalletProvider, useInitializeProviders, PROVIDER_ID } from '@txnlab/use-wallet'
import { DeflyWalletConnect } from '@blockshake/defly-connect'
import { PeraWalletConnect } from '@perawallet/connect'
import { DaffiWalletConnect } from '@daffiwallet/connect'
export default function App() {
const providers = useInitializeProviders({
providers: [
{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
{ id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
{ id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
{ id: PROVIDER_ID.EXODUS }
]
})
return (
<WalletProvider value={providers}>
<div className="App">{/* ... */}</div>
</WalletProvider>
)
}Dynamic Import Example
import React from 'react'
import { WalletProvider, useInitializeProviders, PROVIDER_ID } from '@txnlab/use-wallet'
const getDynamicDeflyWalletConnect = async () => {
const DeflyWalletConnect = (await import("@blockshake/defly-connect")).DeflyWalletConnect;
return DeflyWalletConnect;
};
const getDynamicPeraWalletConnect = async () => {
const PeraWalletConnect = (await import("@perawallet/connect")).PeraWalletConnect;
return PeraWalletConnect;
};
const getDynamicDaffiWalletConnect = async () => {
const DaffiWalletConnect = (await import("@daffiwallet/connect")).DaffiWalletConnect;
return DaffiWalletConnect;
};
export default function App() {
const providers = useInitializeProviders({
providers: [
{ id: PROVIDER_ID.DEFLY, getDynamicClient: getDynamicDeflyWalletConnect },
{ id: PROVIDER_ID.PERA, getDynamicClient: getDynamicPeraWalletConnect },
{ id: PROVIDER_ID.DAFFI, getDynamicClient: getDynamicDaffiWalletConnect },
{ id: PROVIDER_ID.EXODUS }
]
})
return (
<WalletProvider value={providers}>
<div className="App">{/* ... */}</div>
</WalletProvider>
)
}The useWallet Hook
The useWallet hook is used to access wallet provider and account state, send unsigned transactions to be signed, and send signed transactions to the node from anywhere in your app. It returns an object with the following properties:
providers- Array of wallet providers that have been initialized (seeProviderin Type Definitions)activeAccount- The currently active account in the active provider (seeAccountin Type Definitions)connectedAccounts- Array of accounts from all connected wallet providersconnectedActiveAccounts- Array of accounts from the active wallet provideractiveAddress- The address ofactiveAccountstatus,isReady,isActive- The current connection status, see Check connection statussignTransactions- Function that sends unsigned transactions to active wallet provider for signaturesendTransactions- Function that sends signed transactions to the nodegroupTransactionsBySender- Utility function that groups transactions by sender addressgetAddress- Utility function that returns the address of theactiveAccountgetAccountInfo- Utility function that fetchesactiveAccountaccount information from the nodegetAssets- Utility function that fetchesactiveAccountasset info/balances from the nodesigner- Function used by the KMD provider to sign transactions
Type Definitions
Provider
type Provider = {
accounts: Account[]
isActive: boolean
isConnected: boolean
connect: () => Promise<void>
disconnect: () => Promise<void>
reconnect: () => Promise<void>
setActiveProvider: () => void
setActiveAccount: (account: string) => void
metadata: Metadata
}Each provider has two connection states: isConnected and isActive.
isConnected means that the user has authorized the provider in the app. Multiple providers can be connected at the same time.
isActive means that the provider is currently active and will be used to sign and send transactions.
Account
interface Account {
providerId: PROVIDER_ID
name: string
address: string
authAddr?: string
}The activeAccount is the account that will be used to sign and send transactions.
To get the currently active wallet provider, read the providerId property of activeAccount.
Connect Menu
In your app's UI you will need a menu for the user to connect or disconnect wallet providers, setActiveProvider, and setActiveAccount.
This is a bare-bones example for demonstration purposes. For a styled example, see https://app.nf.domains/
import React from 'react'
import { useWallet } from '@txnlab/use-wallet'
export default function ConnectMenu() {
const { providers, activeAccount } = useWallet()
// 1. Map over `providers` array
// 2. Show the provider name/icon and "Connect", "Set Active", and "Disconnect" buttons
// 3. If active, map `provider.accounts` to render a select menu of connected accounts
return (
<div>
{providers?.map((provider) => (
<div key={provider.metadata.id}>
<h4>
<img
width={30}
height={30}
alt={`${provider.metadata.name} icon`}
src={provider.metadata.icon}
/>
{provider.metadata.name} {provider.isActive && '[active]'}
</h4>
<div>
<button type="button" onClick={provider.connect} disabled={provider.isConnected}>
Connect
</button>
<button type="button" onClick={provider.disconnect} disabled={!provider.isConnected}>
Disconnect
</button>
<button
type="button"
onClick={provider.setActiveProvider}
disabled={!provider.isConnected || provider.isActive}
>
Set Active
</button>
<div>
{provider.isActive && provider.accounts.length && (
<select
value={activeAccount?.address}
onChange={(e) => provider.setActiveAccount(e.target.value)}
>
{provider.accounts.map((account) => (
<option key={account.address} value={account.address}>
{account.address}
</option>
))}
</select>
)}
</div>
</div>
</div>
))}
</div>
)
}Displaying Account Details
The activeAccount object can be used to display details for the currently active account.
import React from 'react'
import { useWallet } from '@txnlab/use-wallet'
export default function Account() {
const { activeAccount } = useWallet()
if (!activeAccount) {
return <p>No account active.</p>
}
return (
<div>
<h4>Active Account</h4>
<p>
Name: <span>{activeAccount.name}</span>
</p>
<p>
Address: <span>{activeAccount.address}</span>
</p>
<p>
Provider: <span>{activeAccount.providerId}</span>
</p>
</div>
)
}Signing and Sending Transactions
Here is an example of a signing and sending simple pay transaction using signTransactions and sendTransactions.
import React from 'react'
import algosdk from 'algosdk'
import {
useWallet,
DEFAULT_NODE_BASEURL,
DEFAULT_NODE_TOKEN,
DEFAULT_NODE_PORT
} from '@txnlab/use-wallet'
const algodClient = new algosdk.Algodv2(DEFAULT_NODE_TOKEN, DEFAULT_NODE_BASEURL, DEFAULT_NODE_PORT)
export default function Transact() {
const { activeAddress, signTransactions, sendTransactions } = useWallet()
const sendTransaction = async (from?: string, to?: string, amount?: number) => {
try {
if (!from || !to || !amount) {
throw new Error('Missing transaction params.')
}
const suggestedParams = await algodClient.getTransactionParams().do()
const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
from,
to,
amount,
suggestedParams
})
const encodedTransaction = algosdk.encodeUnsignedTransaction(transaction)
const signedTransactions = await signTransactions([encodedTransaction])
const waitRoundsToConfirm = 4
const { id } = await sendTransactions(signedTransactions, waitRoundsToConfirm)
console.log('Successfully sent transaction. Transaction ID: ', id)
} catch (error) {
console.error(error)
}
}
if (!activeAddress) {
return <p>Connect an account first.</p>
}
return (
<div>
<button type="button" onClick={() => sendTransaction(activeAddress, activeAddress, 1000)}>
Sign and send transactions
</button>
</div>
)
}signTransactions
const signTransactions: (
transactions: Uint8Array[] | Uint8Array[][],
indexesToSign?: number[],
returnGroup?: boolean // defaults to true
) => Promise<Uint8Array[]>The signTransactions function will accept an array of transactions (encoded) or an array of transaction groups.
Advanced Usage
You can optionally specify which transactions should be signed by providing an array of indexes as the second argument, indexesToSign.
By setting returnGroup to false, the returned promise will resolve to an array of signed transactions only. Otherwise it will return a flat array of all transactions by default.
sendTransactions
const sendTransactions: (
signedTransactions: Uint8Array[],
waitRoundsToConfirm?: number
) => Promise<PendingTransactionResponse & { id: string }>If signTransactions is successful, the returned array of transactions can be passed to sendTransactions to be sent to the network.
It will wait for confirmation before resolving the promise. Use the optional argument waitRoundsToConfirm to indicate how many rounds to wait for confirmation.
The promise will resolve to an object containing the transaction id and the PendingTransactionResponse from the Algorand REST API.
Checking Connection Status
The isActive and isReady properties can be used to check the status of the wallet providers. The isActive property determines whether or not an account is currently active. The isReady property indicates whether client has mounted and successfully read the connection status from the wallet providers.
These properties are useful when setting up client side access restrictions, for example, by redirecting a user if no wallet provider isActive, as shown below.
const { isActive, isReady } = useWallet()
useEffect(() => {
if (isReady && isActive) {
allowAccess()
}
if (isReady && !isActive) {
denyAccess()
}
})Supported Wallet Providers
Defly Wallet
- Website - https://defly.app/
- Download Defly Wallet - iOS / Android
- Defly Connect - https://github.com/blockshake-io/defly-connect
- Install dependency -
npm install @blockshake/defly-connect
Pera Wallet
- Website - https://perawallet.app/
- Download Pera Mobile - iOS / Android
- Pera Web Wallet - https://web.perawallet.app/
- Pera Connect - https://github.com/perawallet/connect
- Install dependency -
npm install @perawallet/connect
Daffi Wallet
- Website - https://www.daffi.me/
- Download Daffi Wallet - iOS / Android
- Daffi Connect - https://github.com/RDinitiativ/daffiwallet_connect
- Install dependency -
npm install @daffiwallet/connect
WalletConnect
- Website - https://walletconnect.com/
- Documentation - https://docs.walletconnect.com/
- WalletConnect Cloud - https://cloud.walletconnect.com/
- Web3Modal - https://web3modal.com/
- Install dependency -
npm install @walletconnect/modal-sign-html
Exodus Wallet
- Website - https://www.exodus.com/
- Download - https://www.exodus.com/download/
KMD (Algorand Key Management Daemon)
- Documentation - https://developer.algorand.org/docs/rest-apis/kmd
Legacy Wallet Support
Support for these wallets will be removed in a future release.
AlgoSigner (deprecated)
- GitHub - https://github.com/PureStake/algosigner
- EOL Press Release - https://www.algorand.foundation/news/algosigner-support-ending
MyAlgo
- Website - https://wallet.myalgo.com/home
- FAQ - https://wallet.myalgo.com/home#faq
- Install dependency -
npm install @randlabs/myalgo-connect
Provider Configuration
The useInitializeProviders hook accepts a configuration object with the following properties:
| Key | Type | Default Value |
|---|---|---|
| providers | Array<ProviderDef> | required |
| nodeConfig.network | string \| undefined | 'mainnet' |
| nodeConfig.nodeServer | string \| undefined | 'https://mainnet-api.algonode.cloud' |
| nodeConfig.nodeToken | string \| algosdk.AlgodTokenHeader \| algosdk.CustomTokenHeader \| algosdk.BaseHTTPClient \| undefined | '' |
| nodeConfig.nodePort | string \| number \| undefined | 443 |
| nodeConfig.nodeHeaders | Record<string, string> \| undefined | |
| algosdkStatic | typeof algosdk \| undefined |
Provider Definitions
The providers property is required, and must include at least one provider definition object. This is how you specify which wallet providers you wish to support in your app.
If a wallet provider has an SDK library dependency, make sure you have installed the package. Then set the provider definition's clientStatic property to the dependency's exported module. For example, to use the Defly Wallet provider, you must install the @blockshake/defly-connect package and set its clientStatic property to DeflyWalletConnect.
import React from 'react'
import { WalletProvider, useInitializeProviders, PROVIDER_ID } from '@txnlab/use-wallet'
import { DeflyWalletConnect } from '@blockshake/defly-connect'
export default function App() {
const providers = useInitializeProviders({
providers: [{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect }]
})
return (
<WalletProvider value={providers}>
<div className="App">{/* ... */}</div>
</WalletProvider>
)
}If a provider has options that can be configured, you can set them in the clientOptions property. In most cases these would be the options that are passed to the provider's SDK library when it is initialized. See each supported provider's documentation for more details.
While most client options are optional, the WalletConnect provider has required clientOptions that must be set. See the WalletConnect 2.0 section below for more details.
TypeScript: The provider definitions are type-safe, so your IDE should be able to provide autocomplete suggestions for the client's available options based on the id that is set.
Node configuration
To configure the Algorand node that providers will use to send transactions, you can set the nodeConfig property. The network property should be specified as mainnet, testnet, betanet or the name of your local development network*.
* Refer to each wallet providers' documentation to see which networks they support.
const providers = useInitializeProviders({
providers: [...],
nodeConfig: {
network: 'testnet',
nodeServer: 'https://testnet-api.algonode.cloud',
nodeToken: '',
nodePort: '443'
}
})Algosdk Static Import
By default, the providers dynamically import the algosdk peer dependency installed in your app, to reduce bundle size.
Some React frameworks, like Remix, do not support dynamic imports. To get around this, you can set the optional algosdkStatic root property to the imported algosdk module.
import React from 'react'
import algosdk from 'algosdk'
import { PROVIDER_ID, WalletProvider, useInitializeProviders } from '@txnlab/use-wallet'
import { DeflyWalletConnect } from '@blockshake/defly-connect'
export default function App() {
const providers = useInitializeProviders({
providers: [{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect }],
algosdkStatic: algosdk
})
return (
<WalletProvider value={providers}>
<div className="App">{/* ... */}</div>
</WalletProvider>
)
}Full Configuration Example
import React from 'react'
import algosdk from 'algosdk'
import { PROVIDER_ID, WalletProvider, useInitializeProviders } from '@txnlab/use-wallet'
import { DeflyWalletConnect } from '@blockshake/defly-connect'
import { PeraWalletConnect } from '@perawallet/connect'
import { DaffiWalletConnect } from '@daffiwallet/connect'
import { WalletConnectModalSign } from '@walletconnect/modal-sign-html'
export default function App() {
const providers = useInitializeProviders({
providers: [
{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
{ id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
{ id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
{
id: PROVIDER_ID.WALLETCONNECT,
clientStatic: WalletConnectModalSign,
clientOptions: {
projectId: '<YOUR_PROJECT_ID>',
metadata: {
name: 'Example Dapp',
description: 'Example Dapp',
url: '#',
icons: ['https://walletconnect.com/walletconnect-logo.png']
},
modalOptions: {
themeMode: 'dark'
}
}
},
{ id: PROVIDER_ID.EXODUS }
],
nodeConfig: {
network: 'mainnet',
nodeServer: 'https://mainnet-api.algonode.cloud',
nodeToken: '',
nodePort: '443'
}
algosdkStatic: algosdk
})
return (
<WalletProvider value={providers}>
<div className="App">{/* ... */}</div>
</WalletProvider>
)
}WalletConnect 2.0 Support
UseWallet v2 introduces support for WalletConnect 2.0. This is a major upgrade to the WalletConnect protocol, and introduces a number of breaking changes for app developers.
However, Algorand apps with UseWallet will be able to support the new protocol with minimal effort:
Obtain a project ID - You will need to obtain a project ID from WalletConnect Cloud. This is a simple process, and there is no waiting period. Every app will need its own unique project ID.
Install client library - Install
@walletconnect/modal-sign-htmland setclientStaticto the importedWalletConnectModalSignmodule.Required options - Set the required
clientOptionsas shown below
const providers = useInitializeProviders({
providers: [
{
id: PROVIDER_ID.WALLETCONNECT,
clientStatic: WalletConnectModalSign,
clientOptions: {
projectId: '<YOUR_PROJECT_ID>',
metadata: {
name: 'Example Dapp',
description: 'Example Dapp',
url: '#',
icons: ['https://walletconnect.com/walletconnect-logo.png']
}
}
}
// other providers...
]
})While projectId and metadata are required, you can also set optional modalOptions to customize the appearance of the WalletConnect modal. See the WalletConnect docs for more details.
IMPORTANT: Wallets must migrate to WalletConnect v2 or they will NOT work with this provider. The latest versions of Pera Wallet, Defly Wallet, and Daffi Wallet still support WalletConnect v1 ONLY. Beta versions of these wallets with WalletConnect v2 support are available now:
Defly Wallet (iOS) beta
iOS users can join the Defly Wallet (iOS) beta program for testing WalletConnect v2 support. Android beta coming soon.
Pera Wallet (Android) beta
Android users can join the Pera Wallet (Android) beta program for testing WalletConnect v2 support. Fill out the form and wait for an email with instructions. Form submissions will be checked periodically and added in bulk.
"Module not found" errors in Next.js 13
With the WalletConnect provider initialized in your Next.js 13 app, you may see the error Module not found: Can't resolve 'lokijs' in... or similar in local development. To resolve this, add the following to your next.config.js file:
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.externals.push('pino-pretty', 'lokijs', 'encoding') // list modules in error messages
return config
}
// ...other config
}
module.exports = nextConfigSee https://github.com/WalletConnect/walletconnect-monorepo/issues/1908#issuecomment-1487801131
Migration Guide
Version 2.x is a major version bump, and includes some breaking changes from 1.x.
useInitializeProviders
The most significant change is how wallet providers are initialized.
initializeProviders has been replaced by the useInitializeProviders hook. It accepts a single configuration object as an argument. Its properties correspond to the three arguments passed to its predecessor.
The providers property is an array of provider definition objects, with an id set to a PROVIDER_ID constant. Wallet provider SDKs are no longer dynamically imported by their client classes. They must be statically imported and set as the clientStatic property.
- const providers = initializeProviders([PROVIDER_ID.PERA, PROVIDER_ID.DEFLY, PROVIDER_ID.EXODUS])
+ const providers = useInitializeProviders({
+ providers: [
+ { id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
+ { id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
+ { id: PROVIDER_ID.EXODUS }
+ ]
+ })We've also done away with the concept of "default" providers. Each provider you wish to support in your app must be explicitly defined in the providers array.
Node configuration should be set as the nodeConfig property.
- const providers = initializeProviders([...], {
- network: 'testnet',
- nodeServer: 'https://testnet-api.algonode.cloud',
- nodeToken: '',
- nodePort: '443'
- })
+ const providers = useInitializeProviders({
+ providers: [...],
+ nodeConfig: {
+ network: 'testnet',
+ nodeServer: 'https://testnet-api.algonode.cloud',
+ nodeToken: '',
+ nodePort: '443'
+ }
+ })See Provider Configuration for more details.
shouldShowSignTxnToast
Pera Connect, Defly Connect and Daffi Connect share a shouldShowSignTxnToast option that is set to true by default. UseWallet v1 set these to false by default, and required setting the option back to true to achieve the libraries' default behavior.
In v2 this option is set to true by default. If your app has this option set to true you can remove it from your configuration. If you wish to disable the toast(s), you must now explicitly set the option to false.
const providers = useInitializeProviders({
providers: [
{ id: PROVIDER_ID.DEFLY, clientOptions: { shouldShowSignTxnToast: false } },
{ id: PROVIDER_ID.PERA, clientOptions: { shouldShowSignTxnToast: false } },
{ id: PROVIDER_ID.DAFFI, clientOptions: { shouldShowSignTxnToast: false } }
// other providers...
]
})WalletConnect peer dependencies
The WalletConnect provider now supports WalletConnect 2.0. To continue supporting this provider, or to add support to your application, you must install the @walletconnect/modal-sign-html package.
npm install @walletconnect/modal-sign-htmlThe peer dependencies for WalletConnect 1.x should be uninstalled.
npm uninstall @walletconnect/client @json-rpc-tools/utils algorand-walletconnect-qrcode-modalSee WalletConnect 2.0 Support for more details.
Local Development
Install dependencies
yarn installDemo in Storybook
yarn devTo develop against a local version of @txnlab/use-wallet in your application, do the following:
Build the library
yarn buildSymlink the library
In the root of use-wallet directory, run:
yarn linkIn the root of your application, run:
yarn link @txnlab/use-walletSymlink React
In the root of your application, run:
cd node_modules/react
yarn link
cd ../react-dom
yarn linkIn the root of the use-wallet directory, run:
yarn link react react-domSupport
For questions or technical support, you can reach us in the #use-wallet channel on NFD's Discord: https://discord.gg/w6vSwG5bFK
To report bugs/issues or make a feature request here on GitHub:
Used By
Are you using @txnlab/use-wallet? We'd love to include your project here. Let us know! Twitter | Discord | Email
Full list of Dependents
License
See the LICENSE file for license rights and limitations (MIT)
2 years ago