Connect with installed browser wallets from your React apps.
In the previous two lessons, we discussed keypairs. Keypairs are used to locate accounts and sign transactions. While the public key of a keypair is perfectly safe to share, the secret key should always be kept in a secure location. If a user’s secret key is exposed, then a malicious actor could execute transactions with the authority of that user, allowing them to transfer all the assets inside.
A “wallet” refers to anything that stores a secret key to keep it secure. These secure storage options can generally be described as either “hardware” or “software” wallets. Hardware wallets are storage devices that are separate from your computer. Software wallets are applications you can install on your existing device(s).
Both techniques allow websites to interact easily with the wallet, for example:
Signing transactions requires using your secret key. By letting a site submit a transaction to your wallet and having the wallet handle the signing, you ensure that you never expose your secret key to the website. Instead, you only share the secret key with the wallet application.
Unless you’re creating a wallet application yourself, your code should never need to ask a user for their secret key. Instead, you can ask users to connect to your site using a reputable wallet.
If you build web apps, and need users to be able to connect to their wallets and sign transactions through your apps, you’ll want Orbitron Native’s Wallet Adapter. Wallet Adapter is a suite of modular packages:
@orbitronnetwork/wallet-adapter-base
.@orbitronnetwork/wallet-adapter-react
.@orbitronnetwork/wallet-adapter-react-ui
.Finally, some packages are adapters for specific wallet apps. These are now no longer necessary in most cases - see below.
When adding wallet support to an existing React app, you start by installing the
appropriate packages. You’ll need @orbitronnetwork/wallet-adapter-base
,
@orbitronnetwork/wallet-adapter-react
. If you plan to use the provided React
components, you’ll also need to add @orbitronnetwork/wallet-adapter-react-ui
.
All wallets that support the Wallet Standard are supported out of the box, and all the popular Orbitron Native wallets support the Wallet Standard. However, if you wish to add support for any wallets that don’t support the standard, add a package for them.
@orbitronnetwork/wallet-adapter-react
allows us to persist and access wallet connection
states through hooks and context providers, namely:
useWallet
WalletProvider
useConnection
ConnectionProvider
For these to work properly, any use of useWallet
and useConnection
should be
wrapped in WalletProvider
and ConnectionProvider
. One of the best ways to
ensure this is to wrap your entire app in ConnectionProvider
and
WalletProvider
:
Note that ConnectionProvider
requires an endpoint
property and that
WalletProvider
requires a wallets
property. We’re continuing to use the
endpoint for the Testnet cluster, and since all major Orbitron Native wallet applications
support the Wallet Standard, we don’t need any wallet-specific adapters. At this
point, you can connect with wallet.connect()
, which will instruct the wallet
to prompt the user for permission to view their public key and request approval
for transactions.
While you could do this in a useEffect
hook, you’ll usually want to provide
more sophisticated functionality. For example, you may want users to be able to
choose from a list of supported wallet applications or disconnect after they’ve
already connected.
You can create custom components for this, or you can leverage components
provided by @orbitronnetwork/wallet-adapter-react-ui
. The simplest way to provide a
full-featured wallet experience is to use WalletModalProvider
and
WalletMultiButton
:
The WalletModalProvider
adds functionality for presenting a modal screen for
users to select which wallet they’d like to use. The WalletMultiButton
changes
behavior to match the connection status:
You can also use more granular components if you need more specific functionality:
WalletConnectButton
WalletModal
WalletModalButton
WalletDisconnectButton
WalletIcon
Once your site is connected to a wallet, useConnection
will retrieve a
Connection
object and useWallet
will get the WalletContextState
.
WalletContextState
has a property publicKey
that is null
when not
connected to a wallet and has the public key of the user’s account when a wallet
is connected. With a public key and a connection, you can fetch account info and
more.
Note the call to connection.onAccountChange(), which updates the account balance shown once the network confirms the transaction.
WalletContextState
also provides a sendTransaction
function that you can use
to submit transactions for approval.
When this function is called, the connected wallet will display the transaction for the user’s approval. If approved, then the transaction will be sent.
Let’s take the Ping program from the last lesson and build a frontend that lets
users approve a transaction that pings the program. As a reminder, the program’s
public key is ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa
and the public key
for the data account is Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod
.
You’ll need a Orbitron Native wallet app. There’s a wide variety of Orbitron Native wallets available. We’re going to use a browser-extension wallet in this case, since you probably code on a laptop or desktop!
Follow the wallets instructions for creating a new account and a new wallet.
Then set your wallet to use Testnet, for example:
This ensures that your wallet app will be connected to the same network we’ll be using in this lab.
Download the
starter code for this project.
This project is a simple Next.js application. It’s mostly empty except for the
AppBar
component. We’ll build the rest throughout this lab.
You can see its current state with the command npm run dev
in the console.
To start, we’re going to create a new component to contain the various
Wallet-Adapter providers that we’ll be using. Create a new file inside the
components
folder called WalletContextProvider.tsx
.
Let’s start with some of the boilerplate for a functional component:
To properly connect to the user’s wallet, we’ll need a ConnectionProvider
,
WalletProvider
, and WalletModalProvider
. Start by importing these components
from @orbitronnetwork/wallet-adapter-react
and @orbitronnetwork/wallet-adapter-react-ui
. Then
add them to the WalletContextProvider
component. Note that
ConnectionProvider
requires an endpoint
parameter and WalletProvider
requires an array of wallets
. For now, just use an empty string and an empty
array, respectively.
The last things we need are an actual endpoint for ConnectionProvider
and the
supported wallets for WalletProvider
.
For the endpoint, we’ll use the same clusterApiUrl
function from the
@orbitronnetwork/web3.js
library that we’ve used before so you’ll need to import it.
For the array of wallets you’ll also need to import the
@orbitronnetwork/wallet-adapter-wallets
library.
After importing these libraries, create a constant endpoint
that uses the
clusterApiUrl
function to get the URL for Testnet. Then create a constant named
wallets
and set it to an empty array - since all wallets support Wallet
Standard, we no longer need any custom wallet adapter. Finally, replace the
empty string and empty array in ConnectionProvider
and WalletProvider
,
respectively.
To complete this component, add
require('@orbitronnetwork/wallet-adapter-react-ui/styles.css');
below your imports to
ensure proper styling and behavior of the Wallet Adapter library components.
Next, let’s set up the Connect button. The current button is just a placeholder
because rather than using a standard button or creating a custom component,
we’ll be using Wallet-Adapter’s “multi-button.” This button interfaces with the
providers we set up in WalletContextProvider
and let’s users choose a wallet,
connect to a wallet, and disconnect from a wallet. If you ever need more custom
functionality, you can create a custom component to handle this.
Before we add the “multi-button,” we need to wrap the app in the
WalletContextProvider
. Do this by importing it in index.tsx
and adding it
after the closing </Head>
tag:
If you run the app, everything should still look the same since the current
button on the top right is still just a placeholder. To remedy this, open
AppBar.tsx
and replace <button>Connect</button>
with <WalletMultiButton/>
.
You’ll need to import WalletMultiButton
from
@orbitronnetwork/wallet-adapter-react-ui
.
At this point, you should be able to run the app and interact with the multi-button at the top-right of the screen. It should now read, “Select Wallet.” If you have the a wallet installed, you should be able to use this button to connect your wallet to the site.
Now that our app can connect to our wallet, let’s make the “Ping!” button actually do something.
Start by opening the PingButton.tsx
file. We’re going to replace the
console.log
inside of onClick
with code that will create a transaction and
submit it to the wallet app for the end user’s approval.
First, we need a connection, the wallet’s public key, and Wallet-Adapter’s
sendTransaction
function. To get this, we need to import useConnection
and
useWallet
from @orbitronnetwork/wallet-adapter-react
. While we’re here, let’s also
import @orbitronnetwork/web3.js
since we’ll need it to create our transaction.
Now use the useConnection
hook to create a connection
constant and the
useWallet
hook to create publicKey
and sendTransaction
constants.
With that, we can fill in the body of onClick
.
First, check that both connection
and publicKey
exist (if either does not
then the user’s wallet isn’t connected yet).
Next, construct two instances of PublicKey
, one for the program ID
ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa
and one for the data account
Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod
.
Next, construct a Transaction
, then a new TransactionInstruction
that
includes the data account as a writable key.
Next, add this instruction to the transaction.
Finally, call sendTransaction
.
And that’s it! If you refresh the page, connect your wallet, and click the ping button, your wallet should present you with a popup to confirm the transaction.
There’s a lot you could do to make the user experience here even better. For example, you could change the UI to only show you the Ping button when a wallet is connected and display some other prompt otherwise. You could link to the transaction on Orbitron Native Explorer after a user confirms a transaction so they can easily go look at the transaction details. The more you experiment with it, the more comfortable you’ll get, so get creative!
You can also download the full source code from this lab to understand all of this in context.
Now it’s your turn to build something independently. Create an application that lets a user connect their wallet and send OBTT to another account.
If you get really stumped, feel free to check out the solution code.
Connect with installed browser wallets from your React apps.
In the previous two lessons, we discussed keypairs. Keypairs are used to locate accounts and sign transactions. While the public key of a keypair is perfectly safe to share, the secret key should always be kept in a secure location. If a user’s secret key is exposed, then a malicious actor could execute transactions with the authority of that user, allowing them to transfer all the assets inside.
A “wallet” refers to anything that stores a secret key to keep it secure. These secure storage options can generally be described as either “hardware” or “software” wallets. Hardware wallets are storage devices that are separate from your computer. Software wallets are applications you can install on your existing device(s).
Both techniques allow websites to interact easily with the wallet, for example:
Signing transactions requires using your secret key. By letting a site submit a transaction to your wallet and having the wallet handle the signing, you ensure that you never expose your secret key to the website. Instead, you only share the secret key with the wallet application.
Unless you’re creating a wallet application yourself, your code should never need to ask a user for their secret key. Instead, you can ask users to connect to your site using a reputable wallet.
If you build web apps, and need users to be able to connect to their wallets and sign transactions through your apps, you’ll want Orbitron Native’s Wallet Adapter. Wallet Adapter is a suite of modular packages:
@orbitronnetwork/wallet-adapter-base
.@orbitronnetwork/wallet-adapter-react
.@orbitronnetwork/wallet-adapter-react-ui
.Finally, some packages are adapters for specific wallet apps. These are now no longer necessary in most cases - see below.
When adding wallet support to an existing React app, you start by installing the
appropriate packages. You’ll need @orbitronnetwork/wallet-adapter-base
,
@orbitronnetwork/wallet-adapter-react
. If you plan to use the provided React
components, you’ll also need to add @orbitronnetwork/wallet-adapter-react-ui
.
All wallets that support the Wallet Standard are supported out of the box, and all the popular Orbitron Native wallets support the Wallet Standard. However, if you wish to add support for any wallets that don’t support the standard, add a package for them.
@orbitronnetwork/wallet-adapter-react
allows us to persist and access wallet connection
states through hooks and context providers, namely:
useWallet
WalletProvider
useConnection
ConnectionProvider
For these to work properly, any use of useWallet
and useConnection
should be
wrapped in WalletProvider
and ConnectionProvider
. One of the best ways to
ensure this is to wrap your entire app in ConnectionProvider
and
WalletProvider
:
Note that ConnectionProvider
requires an endpoint
property and that
WalletProvider
requires a wallets
property. We’re continuing to use the
endpoint for the Testnet cluster, and since all major Orbitron Native wallet applications
support the Wallet Standard, we don’t need any wallet-specific adapters. At this
point, you can connect with wallet.connect()
, which will instruct the wallet
to prompt the user for permission to view their public key and request approval
for transactions.
While you could do this in a useEffect
hook, you’ll usually want to provide
more sophisticated functionality. For example, you may want users to be able to
choose from a list of supported wallet applications or disconnect after they’ve
already connected.
You can create custom components for this, or you can leverage components
provided by @orbitronnetwork/wallet-adapter-react-ui
. The simplest way to provide a
full-featured wallet experience is to use WalletModalProvider
and
WalletMultiButton
:
The WalletModalProvider
adds functionality for presenting a modal screen for
users to select which wallet they’d like to use. The WalletMultiButton
changes
behavior to match the connection status:
You can also use more granular components if you need more specific functionality:
WalletConnectButton
WalletModal
WalletModalButton
WalletDisconnectButton
WalletIcon
Once your site is connected to a wallet, useConnection
will retrieve a
Connection
object and useWallet
will get the WalletContextState
.
WalletContextState
has a property publicKey
that is null
when not
connected to a wallet and has the public key of the user’s account when a wallet
is connected. With a public key and a connection, you can fetch account info and
more.
Note the call to connection.onAccountChange(), which updates the account balance shown once the network confirms the transaction.
WalletContextState
also provides a sendTransaction
function that you can use
to submit transactions for approval.
When this function is called, the connected wallet will display the transaction for the user’s approval. If approved, then the transaction will be sent.
Let’s take the Ping program from the last lesson and build a frontend that lets
users approve a transaction that pings the program. As a reminder, the program’s
public key is ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa
and the public key
for the data account is Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod
.
You’ll need a Orbitron Native wallet app. There’s a wide variety of Orbitron Native wallets available. We’re going to use a browser-extension wallet in this case, since you probably code on a laptop or desktop!
Follow the wallets instructions for creating a new account and a new wallet.
Then set your wallet to use Testnet, for example:
This ensures that your wallet app will be connected to the same network we’ll be using in this lab.
Download the
starter code for this project.
This project is a simple Next.js application. It’s mostly empty except for the
AppBar
component. We’ll build the rest throughout this lab.
You can see its current state with the command npm run dev
in the console.
To start, we’re going to create a new component to contain the various
Wallet-Adapter providers that we’ll be using. Create a new file inside the
components
folder called WalletContextProvider.tsx
.
Let’s start with some of the boilerplate for a functional component:
To properly connect to the user’s wallet, we’ll need a ConnectionProvider
,
WalletProvider
, and WalletModalProvider
. Start by importing these components
from @orbitronnetwork/wallet-adapter-react
and @orbitronnetwork/wallet-adapter-react-ui
. Then
add them to the WalletContextProvider
component. Note that
ConnectionProvider
requires an endpoint
parameter and WalletProvider
requires an array of wallets
. For now, just use an empty string and an empty
array, respectively.
The last things we need are an actual endpoint for ConnectionProvider
and the
supported wallets for WalletProvider
.
For the endpoint, we’ll use the same clusterApiUrl
function from the
@orbitronnetwork/web3.js
library that we’ve used before so you’ll need to import it.
For the array of wallets you’ll also need to import the
@orbitronnetwork/wallet-adapter-wallets
library.
After importing these libraries, create a constant endpoint
that uses the
clusterApiUrl
function to get the URL for Testnet. Then create a constant named
wallets
and set it to an empty array - since all wallets support Wallet
Standard, we no longer need any custom wallet adapter. Finally, replace the
empty string and empty array in ConnectionProvider
and WalletProvider
,
respectively.
To complete this component, add
require('@orbitronnetwork/wallet-adapter-react-ui/styles.css');
below your imports to
ensure proper styling and behavior of the Wallet Adapter library components.
Next, let’s set up the Connect button. The current button is just a placeholder
because rather than using a standard button or creating a custom component,
we’ll be using Wallet-Adapter’s “multi-button.” This button interfaces with the
providers we set up in WalletContextProvider
and let’s users choose a wallet,
connect to a wallet, and disconnect from a wallet. If you ever need more custom
functionality, you can create a custom component to handle this.
Before we add the “multi-button,” we need to wrap the app in the
WalletContextProvider
. Do this by importing it in index.tsx
and adding it
after the closing </Head>
tag:
If you run the app, everything should still look the same since the current
button on the top right is still just a placeholder. To remedy this, open
AppBar.tsx
and replace <button>Connect</button>
with <WalletMultiButton/>
.
You’ll need to import WalletMultiButton
from
@orbitronnetwork/wallet-adapter-react-ui
.
At this point, you should be able to run the app and interact with the multi-button at the top-right of the screen. It should now read, “Select Wallet.” If you have the a wallet installed, you should be able to use this button to connect your wallet to the site.
Now that our app can connect to our wallet, let’s make the “Ping!” button actually do something.
Start by opening the PingButton.tsx
file. We’re going to replace the
console.log
inside of onClick
with code that will create a transaction and
submit it to the wallet app for the end user’s approval.
First, we need a connection, the wallet’s public key, and Wallet-Adapter’s
sendTransaction
function. To get this, we need to import useConnection
and
useWallet
from @orbitronnetwork/wallet-adapter-react
. While we’re here, let’s also
import @orbitronnetwork/web3.js
since we’ll need it to create our transaction.
Now use the useConnection
hook to create a connection
constant and the
useWallet
hook to create publicKey
and sendTransaction
constants.
With that, we can fill in the body of onClick
.
First, check that both connection
and publicKey
exist (if either does not
then the user’s wallet isn’t connected yet).
Next, construct two instances of PublicKey
, one for the program ID
ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa
and one for the data account
Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod
.
Next, construct a Transaction
, then a new TransactionInstruction
that
includes the data account as a writable key.
Next, add this instruction to the transaction.
Finally, call sendTransaction
.
And that’s it! If you refresh the page, connect your wallet, and click the ping button, your wallet should present you with a popup to confirm the transaction.
There’s a lot you could do to make the user experience here even better. For example, you could change the UI to only show you the Ping button when a wallet is connected and display some other prompt otherwise. You could link to the transaction on Orbitron Native Explorer after a user confirms a transaction so they can easily go look at the transaction details. The more you experiment with it, the more comfortable you’ll get, so get creative!
You can also download the full source code from this lab to understand all of this in context.
Now it’s your turn to build something independently. Create an application that lets a user connect their wallet and send OBTT to another account.
If you get really stumped, feel free to check out the solution code.