ckBTC Wallet
A specialized wallet interface for ckBTC (Chain Key Bitcoin). This example demonstrates how to build a finance-ready dApp that interacts with the ckBTC ledger and minter canisters.
Features Demonstrated
Section titled “Features Demonstrated”- ICRC-1 Standard: Interacting with the standard token interface (balance, transfer).
- ckBTC Specifics: Retrieving BTC addresses and updating balances.
- Complex Flows: Managing deposit/withdraw cycles.
- Exact Token Amounts: Balances and the fee shown with
formatTokenAmount, with no floating-point math.
Open in StackBlitzEdit and run in your browser
View on GitHubBrowse the source code
Live Preview
Section titled “Live Preview”Key Design Patterns
Section titled “Key Design Patterns”Domain Specific Hooks
Section titled “Domain Specific Hooks”Instead of generic queries, this example wraps them in domain-specific hooks for cleaner code:
import { ClientManager, DisplayReactor, createActorHooks, createMutation, createQueryFactory,} from "@ic-reactor/react"import { QueryClient } from "@tanstack/react-query"import { idlFactory as ckbtcIdlFactory, type _SERVICE as CkbtcLedger,} from "./declarations/ckbtc"import { idlFactory as minterIdlFactory, type _SERVICE as CkbtcMinter,} from "./declarations/minter"
export const queryClient = new QueryClient()export const clientManager = new ClientManager({ queryClient })
export const ckbtcLedger = new DisplayReactor<CkbtcLedger>({ clientManager, name: "ckbtcLedger", canisterId: "mc6ru-gyaaa-aaaar-qaaaq-cai", idlFactory: ckbtcIdlFactory,})
export const ckbtcMinter = new DisplayReactor<CkbtcMinter>({ clientManager, name: "ckbtcMinter", canisterId: "ml52i-qqaaa-aaaar-qaaba-cai", idlFactory: minterIdlFactory,})
// Ledger hooks, bound to the ckBTC ledger DisplayReactorexport const { useActorQuery: useCkbtcLedgerQuery } = createActorHooks(ckbtcLedger)
// Reusable query/mutation objects for the two canistersexport const balanceQuery = createQueryFactory(ckbtcLedger, { functionName: "icrc1_balance_of",})
export const updateBalanceMutation = createMutation(ckbtcMinter, { functionName: "update_balance",})Components then read the ledger through those hooks and objects:
import { useCkbtcLedgerQuery } from "./reactor"
export default function App() { const { data: name } = useCkbtcLedgerQuery({ functionName: "icrc1_name", })
return <h1>{name ?? "ckBTC"} Wallet</h1>}// src/MinterRetrieveBtc.tsx — `owner` is a plain string under DisplayReactorimport { formatTokenAmount } from "@ic-reactor/react"import { balanceQuery } from "./reactor"
/** ckBTC counts in satoshis: 1 ckBTC is 10^8. */const CKBTC_DECIMALS = 8
export default function MinterRetrieveBtc({ userPrincipal,}: { userPrincipal: string}) { // Under a DisplayReactor the balance is the satoshis as text const { data: balance, refetch: refetchBalance } = balanceQuery([ { owner: userPrincipal }, ]).useQuery()
return ( <button onClick={() => refetchBalance()}> {balance === undefined ? "—" : `${formatTokenAmount(balance, CKBTC_DECIMALS)} ckBTC`} </button> )}