Templates
Components
Drop-in React components for platforms integrating Buff. Fully configurable — change any value and all widgets update live.
Configure all widgets
$
$
Buff Toggle
Let users enable/disable Buff and pick their plan. Toggle it, change plans — the round-up preview below updates instantly.
$
Buff Round-Up
Auto-invest into BTC
Rounds to $0.10Buff fee 0.75%
BuffToggle.tsx
typescript
1"use client"2import { useState } from "react"3import type { Buff, PlanTier } from "buff-protocol-sdk"45interface BuffToggleProps {6 buff: Buff | null7 onToggle: (enabled: boolean) => void8 onPlanChange: (plan: PlanTier) => void9 defaultPlan?: PlanTier10 investAsset?: string11}1213export function BuffToggle({14 buff, onToggle, onPlanChange,15 defaultPlan = "sprout", investAsset = "BTC"16}: BuffToggleProps) {17 const [enabled, setEnabled] = useState(true)18 const [plan, setPlan] = useState(defaultPlan)1920 const plans = [21 { key: "seed", label: "Seed", roundTo: 0.05 },22 { key: "sprout", label: "Sprout", roundTo: 0.10 },23 { key: "tree", label: "Tree", roundTo: 0.50 },24 { key: "forest", label: "Forest", roundTo: 1.00 },25 ]2627 const toggle = () => {28 const next = !enabled29 setEnabled(next)30 onToggle(next)31 }3233 const changePlan = (key: string) => {34 setPlan(key as PlanTier)35 onPlanChange(key as PlanTier)36 if (buff) buff.setPlan(key as any)37 }3839 return (40 <div className="rounded-xl border p-4 bg-card">41 <div className="flex items-center justify-between mb-3">42 <div>43 <div className="text-sm font-medium">Buff Round-Up</div>44 <div className="text-xs text-muted-foreground">45 Auto-invest into {investAsset}46 </div>47 </div>48 <button onClick={toggle} className={`w-12 h-7 rounded-full49 ${enabled ? "bg-amber-500" : "bg-gray-600"}`}>50 <div className={`w-5 h-5 rounded-full bg-white transform51 ${enabled ? "translate-x-[22px]" : "translate-x-[3px]"}`} />52 </button>53 </div>54 {enabled && (55 <div className="grid grid-cols-4 gap-1">56 {plans.map((p) => (57 <button key={p.key}58 onClick={() => changePlan(p.key)}59 className={`text-xs py-2 rounded-lg font-medium60 ${plan === p.key61 ? "bg-amber-500/10 text-amber-500 border border-amber-500/20"62 : "text-muted-foreground hover:bg-accent"63 }`}>64 {p.label}65 </button>66 ))}67 </div>68 )}69 </div>70 )71}Round-Up Preview
Shows users exactly what their round-up will be. Change the tx value in the configurator above to see it update.
This transaction
Tx value$47.83
Rounded to$47.90
Round-up$0.07
You invest$0.0695 → BTC
Buff fee (0.75%)$0.0005
RoundUpPreview.tsx
typescript
1"use client"2import type { RoundUpBreakdown } from "buff-protocol-sdk"34interface RoundUpPreviewProps {5 breakdown: RoundUpBreakdown | null6 asset: string7}89export function RoundUpPreview({ breakdown, asset }: RoundUpPreviewProps) {10 if (!breakdown || breakdown.skipped) {11 return (12 <div className="rounded-xl border p-4 bg-card text-center">13 <p className="text-sm text-muted-foreground">No round-up needed</p>14 </div>15 )16 }1718 return (19 <div className="rounded-xl border p-4 bg-card">20 <div className="flex justify-between text-sm mb-2">21 <span>Tx value</span>22 <span>${breakdown.txValueUsd.toFixed(2)}</span>23 </div>24 <div className="flex justify-between text-sm mb-2">25 <span>Rounded to</span>26 <span>${breakdown.roundedToUsd.toFixed(2)}</span>27 </div>28 <hr className="my-2" />29 <div className="flex justify-between font-semibold text-amber-500">30 <span>Investing</span>31 <span>${breakdown.roundUpUsd.toFixed(2)} → {asset}</span>32 </div>33 <div className="flex justify-between text-xs text-muted-foreground mt-1">34 <span>Buff fee ({breakdown.buffFeePercent}%)</span>35 <span>${breakdown.buffFeeUsd.toFixed(4)}</span>36 </div>37 </div>38 )39}4041// Usage:42// const breakdown = await buff.calculateRoundUp(txValueUsd)43// <RoundUpPreview breakdown={breakdown} asset="BTC" />Portfolio Card
Displays portfolio with allocation bar, holdings breakdown, and progress toward next swap. Change asset and threshold above.
Buff Portfolio
+18.6%
$82.80
142 round-ups
BTC85%
$70.38SOL15%
$12.42Next swap at $5$1.06 / $5
BuffPortfolio.tsx
typescript
1"use client"2import { useState, useEffect } from "react"3import type { Buff, Portfolio } from "buff-protocol-sdk"45interface BuffPortfolioProps {6 buff: Buff | null7 walletAddress: string // Buff wallet address (from deriveWallet)8 refreshInterval?: number // ms, default 600009}1011export function BuffPortfolio({ buff, walletAddress, refreshInterval = 60000 }: BuffPortfolioProps) {12 const [portfolio, setPortfolio] = useState<Portfolio | null>(null)1314 useEffect(() => {15 if (!buff || !walletAddress) return16 const fetch = () => buff.getPortfolio(walletAddress).then(setPortfolio)17 fetch()18 const interval = setInterval(fetch, refreshInterval)19 return () => clearInterval(interval)20 }, [buff, walletAddress, refreshInterval])2122 if (!portfolio) return <div className="animate-pulse h-40 rounded-xl bg-card" />2324 const total = portfolio.totalUsd2526 return (27 <div className="rounded-xl border p-5 bg-card">28 <div className="text-2xl font-bold mb-1">${total.toFixed(2)}</div>29 <div className="text-xs text-muted-foreground mb-4">30 {portfolio.pendingSol.toFixed(4)} SOL pending31 ({" "}32 ${portfolio.pendingUsd.toFixed(2)}33 {" "})34 </div>35 {portfolio.balances.map((b) => (36 <div key={b.asset} className="flex justify-between text-sm py-1.5 border-t">37 <span className="font-medium">{b.asset}</span>38 <span>${b.usdValue.toFixed(2)}</span>39 </div>40 ))}41 </div>42 )43}Stats Card
Lifetime statistics. The Buff fees shown reflect the current plan's fee rate.
Lifetime Stats
142
Round-ups
$82.80
Total invested
$0.62
Buff fees paid
BTC
Investing into
Sprout
Plan
2h ago
Last swap
BuffStats.tsx
typescript
1"use client"2import { useState, useEffect } from "react"3import type { Buff } from "buff-protocol-sdk"45interface BuffStatsProps {6 buff: Buff | null7 walletAddress: string8}910export function BuffStats({ buff, walletAddress }: BuffStatsProps) {11 const [portfolio, setPortfolio] = useState<any>(null)12 const [plans, setPlans] = useState<any[]>([])1314 useEffect(() => {15 if (!buff || !walletAddress) return16 buff.getPortfolio(walletAddress).then(setPortfolio)17 buff.getPlans().then(setPlans)18 }, [buff, walletAddress])1920 if (!portfolio) return null2122 return (23 <div className="rounded-xl border p-5 bg-card">24 <div className="grid grid-cols-2 gap-4">25 <Stat label="Total value" value={`$${portfolio.totalUsd.toFixed(2)}`} />26 <Stat label="Pending SOL" value={portfolio.pendingSol.toFixed(4)} />27 <Stat label="Assets" value={portfolio.balances.length.toString()} />28 <Stat label="Plans available" value={plans.length.toString()} />29 </div>30 </div>31 )32}3334function Stat({ label, value }: { label: string; value: string }) {35 return (36 <div>37 <div className="text-lg font-bold">{value}</div>38 <div className="text-xs text-muted-foreground">{label}</div>39 </div>40 )41}Note
All widgets are controlled — they respond to props from the configurator. In your app, connect them to the real Buff instance. The code samples show exactly how. Note that getPortfolio() now requires a wallet address parameter, and plan info is fetched via getPlans(). Styling uses basic Tailwind — customize to match your design system.