🔐 6. Unlock the wallet with `waypoint/core`
Previously, you retrieved information about the user and their check-in status.
At this step, you will do the following:
- Ask users for their wallet recovery password.
- Use the wallet recovery password and Ronin Waypoint token to access users' keyless wallet in your web app.
- Create a
walletClient
that can be used to sign a message or send a transaction. - Put the
walletClient
in theReact.Context
for global access on the React app.
Create wallet context
In your app
directory, create a wallet-context
folder with these files:
app
----wallet-context
--------use-wallet-client.ts
--------wallet-context.tsx
--------wallet-provider.tsx
Go to wallet-context.tsx
, then create a new Context for holding the walletClient
:
import { Context, createContext } from "react"
import { WalletClient } from "viem"
type ContextValue = {
requestWalletClient: () => void
walletClient: WalletClient | undefined
}
type WalletContext = Context<ContextValue>
export const WalletContext: WalletContext = createContext<ContextValue>({
requestWalletClient: () => {},
walletClient: undefined,
})
WalletContext.displayName = "WalletContext"
- Use
*requestWalletClient*
to ask users for theirrecovery password
, then initialize thewalletClient
. - The
walletClient
is used to send transactions to the blockchain.
Add logic to wallet context
Read the React.Context & Context.Provider documentation for more information.
-
Go to
*wallet-provider.tsx*
, then create a new provider calledWalletProvider
:import { WalletContext } from "./wallet-context"
type Props = {
children?: ReactNode
}
export const WalletProvider: FC<Props> = props => {
const { children } = props
// walletClient should initialize here
// requestWalletClient logic here
return (
<WalletContext.Provider
value={{
requestWalletClient,
walletClient,
}}
>
{/* this Provider will wrap-up your web app */}
{/* => should render children */}
{children}
{/* UI to ask users for their password */}
</WalletContext.Provider>
)
} -
Implement
Dialog
for users to input their recovery password. We will go through this section quickly.const inputRef = useRef<HTMLInputElement>(null)
const [isOpen, setOpen] = useState<boolean>(false)
const [error, setError] = useState<string>()
const requestWalletClient = useCallback(async () => {
if (inputRef.current) {
inputRef.current.value = ""
}
setOpen(true)
}, [])
const handleUnlock = async () => {
const password = inputRef.current?.value ?? ""
// users' recovery password
// should init walletClient here
}
const handleClose = () => {
setOpen(false)
}
return (
<WalletContext.Provider
value={{
requestWalletClient,
walletClient,
}}
>
{children}
<div
className={clsx(
"z-50 inset-0 bg-slate-800 bg-opacity-70 flex items-center justify-center",
isOpen ? "fixed" : "hidden",
)}
>
<div className="bg-white w-96 rounded-lg p-5">
<h1 className="text-xl font-semibold text-slate-700">Unlock Wallet</h1>
<p className="text-md text-slate-500 font-light">With your wallet recovery password</p>
<input
autoFocus
ref={inputRef}
className="w-full mt-4 rounded-md border-slate-400 border h-10 px-2 font-light"
type="password"
placeholder="Wallet's recovery password"
defaultValue=""
/>
{error && <p className="text-rose-600 mt-2 text-sm">{error}</p>}
<div className="flex w-full gap-4">
<button
className="mt-8 w-full h-12 border-slate-200 border rounded-md text-slate-700 text-md font-semibold active:bg-slate-100"
onClick={handleClose}
>
Close
</button>
<button
className="mt-8 w-full h-12 bg-sky-600 rounded-md text-white text-md font-semibold active:bg-sky-700"
onClick={handleUnlock}
>
Unlock
</button>
</div>
</div>
</div>
</WalletContext.Provider>
)
Create new wallet client
-
Add a
newWalletClient
function and call it inhandleUnlock
:import { getKeylessProvider } from "@sky-mavis/waypoint/core"
import { WalletClient } from "viem"
const [walletClient, setWalletClient] = useState<WalletClient>()
const newWalletClient = useCallback(async (password: string) => {
}, [])
const handleUnlock = async () => {
const password = inputRef.current?.value ?? ""
try {
const newClient = await newWalletClient(password)
setWalletClient(newClient)
setOpen(false)
} catch (error) {
setError("Password is incorrect")
console.debug("🚀 | handleUnlock:", error)
}
} -
Get
waypointToken
to create a newKeylessProvider
:const newWalletClient = useCallback(async (password: string) => {
const waypointToken = localStorage.getItem("token")
const expectedAddress = localStorage.getItem("address")
if (!waypointToken || !expectedAddress) {
throw new Error("No waypoint token found")
}
// * Keyless provider will help you to interact with users' Keyless wallet
// * Need users' recovery password to unlock
const keylessProvider = await getKeylessProvider({
chainId: saigon.id,
waypointToken: token,
recoveryPassword: password,
})
}, []) -
Wrap
keylessProvider
withwalletClient
fromviem
:const newWalletClient = useCallback(async (password: string) => {
// Create the keylessProvider HERE
const walletAccounts = await walletClient.getAddresses()
// * Wrap the provider with viem - walletClient
// * Make it easier to interact with the wallet - signing messages or send transactions
const walletClient = createWalletClient({
transport: custom(provider),
chain: saigon,
})
// * Check if current account is matched with saved account - from authorized step
if (walletAccounts.length === 0) {
throw new Error("No connected accounts found")
}
if (walletAccounts[0] !== expectedAddress) {
throw new Error("Connected account does not match expected address")
}
return walletClient
}, []) -
Go to
use-wallet-client.ts
, then create a simple hook to get data from context:import { useContext } from "react"
import { WalletContext } from "./wallet-context"
export const useWalletClient = () => useContext(WalletContext)
Unlock your wallet
-
Go to the
check-in.tsx
file. -
Add the button for users to unlock their wallet if
walletClient
is not ready:import { FC, useState } from "react"
import { Address } from "viem"
import { useWalletClient } from "../wallet-context/use-wallet-client"
export const CheckIn: FC<Props> = ({ account }) => {
const { walletClient, requestWalletClient } = useWalletClient()
const handleUnlock = async () => {
requestWalletClient()
}
const handleCheckIn = async () => {
// Check in if walletClient is ready
}
return (
<div className="flex flex-col w-full max-w-screen-sm">
<div className="border-t border-slate-500 my-4" />
<div className="flex flex-col gap-2 mt-4">
{walletClient && (
<button onClick={handleCheckIn}>
Check In Button
</button>
)}
{!walletClient && (
<button
className="bg-sky-600 hover:bg-sky-700 text-white font-bold py-3 px-4 rounded-xl mt-2 disabled:opacity-50 tracking-wider"
onClick={handleUnlock}
>
Unlock your wallet
</button>
)}
</div>
</div>
)
}