Building a Wallet
The @thirdweb-dev/wallets
package provides a set of pre-built components and functionality for creating and managing cryptocurrency wallets.
The following will guide you through how you can create your own wallet in a few simple steps:
- Implement your own
Connector
class - Implement your own
AbstractBrowserWallet
class - Expose your wallet to React and React Native wallet connect button
At the end of this guide you should have a fully functional wallet that can be used with the thirdweb TS, React and React Native SDKs.
1. Extending the Connector
class
The Connector
abstract class is the
interface between your wallet core functionality and the actual wallet class you'll use in your application.
This is the biggest part of the wallet implementation and allows to bridge any wallet provider to a standardized interface.
Interface
export class MyConnector extends Connector {
connect(args?: ConnectParams<TConnectParams>): Promise<string>
disconnect(): Promise<void>
getAddress(): Promise<string>
getSigner(): Promise<Signer>
getProvider(): Promise<providers.Provider>
switchChain(chainId: number): Promise<void>
isConnected(): Promise<boolean>
setupListeners(): Promise<void>
updateChains(chains: Chain[]): void
}
You can choose to implement all or part of the connector functionality.
Required methods
connect
This method should trigger the connection flow of your wallet with the user's app.
Configuration
args
The arguments needed to connect your wallet. This is specific to your wallet implementation which is why we leave it open for you to add custom arguments here.
Returns a string
containing the wallet address, or undefined
if the connection failed.
abstract connect(args?: ConnectParams<TConnectParams>): Promise<string>;
ConnectParams<TOpts extends Record<string, any> = {}> = {
chainId?: number;
} & TOpts;
getSigner
Returns a Promise<Signer>
with the ethers signer associated with your wallet.
getProvider
Returns a Promise<providers.Provider>
with the ethers provider associated with your wallet.
disconnect
Disconnect the currently connected wallet from your app.
isConnected
Returns a Promise<boolean>
indicating whether the wallet is connected or not.
getAddress
Returns a Promise<string>
with the currently connected wallet address.
Optional methods
You can throw an exception or leave the implementation empty if you don't want to implement these methods.
switchChain
Switch the chain the wallet is connected to.
setupListeners
This method should set all listeners needed for the wallet to work. It will be used by the AbstractClientWallet
class to setup the listeners
when the wallet is connected.
updateChains
Update the chains the wallet can connect to.
Configuration
chains
The chains that are supported by the wallet. Defaults to our default chains.
Must be an array of Chain
objects.
type Chain = {
chainId: number;
chainName: string;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
rpcUrls: string[];
blockExplorerUrls: string[];
};
2. Extending the AbstractClientWallet
class
Now that the hard part is done, the rest is easy, we just need to wrap our connector in a AbstractClientWallet
class.
The AbstractClientWallet
class is the base
class that provides an interface for interacting with your connector on one side and with applications on the other.
The main method that needs to be overridden is the getConnector
method. This method should return a Promise
that resolves to a the Connector
class that you implemented.
export class MyWallet extends AbstractClientWallet {
async getConnector(): Promise<Connector> {
return new MyConnector();
}
}
You can add expose any custom logic here as the public API for your wallet.
Required Methods
getConnector
Returns a Promise<Connector>
with the connector class that you implemented.
Using your new wallet
At this point you should be able to instantiate your new wallet and calling connect
on it.
const wallet = new MyWallet();
wallet.connect();
3. Integrate with Connect Wallet button
The last step is to integrate it with Connect Wallet button by creating a function that returns a ConfiguredWallet
object and specifying it in supportedWallets
import { ConfiguredWallet } from '@thirdweb-dev/react'
const myWallet = (config?: MyWalletConfig) {
const configuredWallet: ConfiguredWallet<MyWallet> = {
id: 'my-wallet',
meta: {
name: "My Wallet",
iconURL: "https://...", // or ipfs://...
// optional
urls: {
chrome: "https://...", // optional
firefox: "https://...", // optional
android: "https://...", // optional
ios: "https://...", // optional
},
},
create(options) {
return new MyWallet({ ...options, ...config })
}
// optional
connectUI(props) {
return <MyWalletUI {...props} />;
},
// optional
isInstalled() {
// detect if your wallet extension is installed on user's browser/device
return true; // or false
},
};
return configuredWallet;
};
config (optional)
If your wallet requires some extra configuration for creating a wallet instance that is not provided from the create
function's argument, you can expose it as config
from myWallet
function and merge the options
and config
to provide all the properties required by your wallet.
// if the options are not enough
const myWallet = (config) => {
const configuredWallet: ConfiguredWallet = {
create(options) {
return new MyWallet({ ...options, ...config });
},
// ...
};
return configuredWallet;
};
If your wallet does not require any configuration, you don't need to include the config
// if the options are enough
const myWallet = () => {
const configuredWallet: ConfiguredWallet = {
create(options) {
return new MyWallet(options);
},
// ...
};
return configuredWallet;
};
id
A unique ID for your wallet that is used to identify your wallet among other wallets used in the app.
It can be anything, but using the name of your wallet as id
is recommended as it
is not likely to be used by other wallets.
meta
metadata of your wallet.
name and iconURL
name
and iconURL
are required. They are used to display your wallet in the list of supported wallets in Connect Wallet Modal
urls
urls
object is optional and all of its properties are optional as well.
{
chrome?: string;
firefox?: string;
android?: string;
ios?: string;
}
It is used to display a built-in "Get Started" screen. This screen renders UI for downloading the wallet's browser extension or iOS/Android app.
"Get started" screen's link is rendered below the list of supported wallets in ConnectWallet Modal - This link is only rendered if the first wallet in supportedWallets array) has the urls
object in meta
.
create
A function that creates and returns the wallet instance.
Type
import { WalletOptions } from "@thirdweb-dev/wallets";
import { WalletInstance } from "@thirdweb-dev/react";
type Create = (options: WalletOptions) => WalletInstance;
You can create an instance of your wallet using the options
provided by the create
function and optionally use the config
provided by the myWallet
function.
create(options) {
return new MyWallet({
...options,
...config,
});
}
since the create
method only provides options
of type WalletOptions
- the rest of the wallet-specific configuration (if any) can be exposed as config
of the myWallet
function.
connectUI (optional)
A function that returns a React component that renders the UI for connecting to your wallet.
This UI will be rendered inside the ConnectWallet Modal when the user clicks on your wallet in the list of supported wallets.
The connectUI
function is given props of type ConnectUIProps
which you can use in your UI to trigger various actions - like closing the modal, opening the modal, going back to the list of supported wallets etc.
ConnectUIProps
{
/**
* close the connect wallet modal
* @param reset -
* - reset Connect Wallet Modal to initial state,
* - so that if it's opened again, it will start from the wallet-selection screen
* - default: `true`
*/
close: (reset?: boolean) => void;
/**
* indicates whether the connect wallet modal is open or not
*/
isOpen: boolean;
/**
* Open the connect wallet modal
*/
open: () => void;
/**
* go back to the wallet selector screen in connect wallet modal
*/
goBack: () => void;
};
Headless Mode
If no connectUI
is given, the wallet is assumed to be in "headless" mode - meaning that it does not require any UI to be rendered inside the ConnectWallet Modal to connect the wallet - This can be the case if either the wallet has its own UI that is appended to DOM or it does not require any UI at all.
In this case, when the user clicks on your wallet in the list of supported wallets, the wallet.connect
method is called and the ConnectWallet Modal is closed.
isInstalled (optional)
A function that returns a boolean indicating whether your wallet is installed on the user's browser/device or not.
It is used to show an "installed" badge next to your wallet's name in the list of supported wallets in Connect Wallet Modal if this function is defined and returns true
.
You can now use your wallet in the Connect Wallet button! Simply add it to the supportedWallets
property of the ThirdwebProvider
.
<ThirdwebProvider supportedWallets={[myWallet()]}>
<App />
</ThirdwebProvider>
Examples
You can look at how thirdweb-dev/react
package's built-in wallets are implemented for reference:
Contributing to the Wallets package
If you think your wallet implementation would be useful to others, please consider sharing it by opening a PR to the @thirdweb-dev/wallets
package.
Examples
You can look at how the built-in wallets in @thirdweb-dev/wallets
package are created for reference