Skip to main content

🔐 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:

  1. Ask users for their wallet recovery password.
  2. Use the wallet recovery password and Ronin Waypoint token to access users' keyless wallet in your web app.
  3. Create a walletClient that can be used to sign a message or send a transaction.
  4. Put the walletClient in the React.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 their recovery password, then initialize the walletClient.
  • 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.

  1. Go to *wallet-provider.tsx*, then create a new provider called WalletProvider:

    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>
    )
    }
  2. 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

  1. Add a newWalletClient function and call it in handleUnlock:

    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)
    }
    }
  2. Get waypointToken to create a new KeylessProvider:

    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,
    })
    }, [])
  3. Wrap keylessProvider with walletClient from viem:

      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
    }, [])
  4. 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

  1. Go to the check-in.tsx file.

  2. 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>
    )
    }

Next step

7. Check in the user