Beacon wallet
The interface with the Tezos wallet
Beacon is a Tezos toolkit for wallet pairing. It allows wallet developers to integrate with dApps and dApps developers to give more choices to the user.
In order to implement the connect wallet button, and basic blockchain connectivity, we will start our first provider: WalletProvider
Add a providers
folder in ./components
, and create ./components/providers/WalletProvider.tsx
The imports that will be required:
import React, {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import { AccountInfo, NetworkType, ColorMode } from "@airgap/beacon-types"
import { TezosToolkit } from "@taquito/taquito"
import { BeaconWallet } from "@taquito/beacon-wallet"
import { set_binder_tezos_toolkit } from "@completium/dapp-ts"
In all our providers, we will define context props, that will represent the properties that will be made available to children using our context. We will then export a useContext
shorthand, and the provider itself.
Our wallet provider will provide the following:
interface WalletContextProps {
connect: () => Promise<void>
disconnect: () => Promise<void>
getBalance: () => Promise<void>
account?: AccountInfo
wallet?: BeaconWallet
Tezos?: TezosToolkit
balance: number
}
The properties are self-explanatory:
connect
will request a wallet connection using Beacondisconnect
will reset the connectiongetBalance
will trigger a refresh of the balance (wallet tez balance)account
provides the wallet address, among other thingswallet
andTezos
are Beacon/Taquito objects that will be used internally to connect the dots
Now that we have the props, let's define our context, with an empty props instance, and prepare the useContext
shorthand:
const WalletContext = createContext<WalletContextProps>({
connect: async () => {},
disconnect: async () => {},
getBalance: async () => {},
balance: 0,
})
const useWalletContext = () => useContext(WalletContext)
The fun part, implementing the provider itself: it will be a React component:
const WalletProvider = ({ children }: { children: ReactNode }) => {
...
}
Inside the component, we define the state that will be exposed to the children:
const [Tezos, setTezos] = useState<TezosToolkit>()
const [wallet, setWallet] = useState<BeaconWallet | undefined>()
const [account, setAccount] = useState<AccountInfo>()
const [balance, setBalance] = useState<number>(0)
Tezos toolkit
In React component lifecycle, to update the state when the component loads or when a dependency is updated, we use an effect:
useEffect(() => {
if (!Tezos) {
// create Taquito's Tezos toolkit instance and connect it to our RPC
const Tezos = new TezosToolkit(
process.env.NEXT_PUBLIC_TEZOS_RPC ?? "http://localhost:20000"
)
// instantiate the BeaconWallet object
const beacon = new BeaconWallet({
name: "TZombies",
preferredNetwork: (process.env.NEXT_PUBLIC_NETWORK ||
"ghostnet") as NetworkType,
colorMode: ColorMode.DARK,
})
// link the Tezos toolkit with Beacon
Tezos.setWalletProvider(beacon)
// link the Completium SDK to our toolkit
set_binder_tezos_toolkit(Tezos)
// a returning user may already have a connected wallet
beacon.client.getActiveAccount().then(setAccount)
// set the state
setTezos(Tezos)
setWallet(beacon)
}
}, [Tezos])
Let's stop here for a moment and analyse what is going on. This is an important part of the Tezos specifics.
First, we create a
TezosToolkit
instance (from taquito). It will connect to the given node RPCThen we create a
BeaconWallet
instance and we connect them, as per their documentationWe also need to connect the Completium SDK to our
TezosToolkit
instanceIf there is already an account connected (a returning user) then the connection will be restored
Finally, we set the state of our provider with the account, the toolkit and Beacon
Note that this is only done once, after the page has loaded.
Connect/disconnect
We can now implement the connect
and disconnect
callbacks:
const connect = useCallback(async () => {
try {
await wallet?.requestPermissions({
network: {
type: (process.env.NEXT_PUBLIC_NETWORK ?? "ghostnet") as NetworkType,
rpcUrl: process.env.NEXT_PUBLIC_TEZOS_RPC,
},
})
const active = await wallet?.client.getActiveAccount()
console.log(active)
setAccount(active)
} catch (e) {
console.error(e)
}
}, [wallet])
const disconnect = useCallback(async () => {
await wallet?.clearActiveAccount()
await wallet?.disconnect()
setAccount(undefined)
setBalance(0)
}, [wallet])
Balance update
The following fetches the current account's balance from the Tezos node, and is also triggered when the component loads.
const getBalance = useCallback(async () => {
if (!Tezos || !account) {
return
}
const balance = await Tezos.tz.getBalance(account.address)
setBalance(balance.dividedBy(1_000_000).toNumber())
}, [Tezos, account])
useEffect(() => {
getBalance()
}, [getBalance])
Wrap up
Let's wrap all this state and these methods into a memo that we will pass down to the provider's children:
const value: WalletContextProps = useMemo(
() => ({
connect,
disconnect,
getBalance,
account,
wallet,
Tezos,
balance,
}),
[connect, disconnect, getBalance, account, wallet, Tezos, balance]
)
return (
<WalletContext.Provider value={value}>{children}</WalletContext.Provider>
)
At the end of your file, be sure to append:
export { WalletProvider, useWalletContext }
That's it for the first provider. We just need to add it to the app hierarchy in ./pages/_app.tsx
such that the App
function returns:
<ThemeProvider theme={theme}>
<CssBaseline />
<WalletProvider>
<NavBar />
<Container sx={{ mt: 12 }}>
<Component {...pageProps} />
</Container>
</WalletProvider>
</ThemeProvider>
This will cause Visual Studio Code to complain that WalletProvider
is undefined. This is because the file requires the following import:
import { WalletProvider } from "../components/providers/WalletProvider"
For the remainder of the tutorial, imports for providers and components such as these may be ommitted and it will be left to the reader to include them where necessary.
The context is now usable in any child with:
const walletContext = useWalletContext()
Last updated