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/sdk"
4
5interface BuffToggleProps {
6 buff: Buff | null
7 onToggle: (enabled: boolean) => void
8 onPlanChange: (plan: PlanTier) => void
9 defaultPlan?: PlanTier
10 investAsset?: string
11}
12
13export 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)
19
20 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 ]
26
27 const toggle = () => {
28 const next = !enabled
29 setEnabled(next)
30 onToggle(next)
31 }
32
33 const changePlan = (key: string) => {
34 setPlan(key as PlanTier)
35 onPlanChange(key as PlanTier)
36 if (buff) buff.setPlan(key as any)
37 }
38
39 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-full
49 ${enabled ? "bg-amber-500" : "bg-gray-600"}`}>
50 <div className={`w-5 h-5 rounded-full bg-white transform
51 ${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-medium
60 ${plan === p.key
61 ? "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.0695BTC
Buff fee (0.75%)$0.0005
RoundUpPreview.tsx
typescript
1"use client"
2import type { FeeBreakdown } from "@buff/sdk"
3
4interface RoundUpPreviewProps {
5 breakdown: FeeBreakdown | null
6 asset: string
7}
8
9export 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 }
17
18 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}
40
41// Usage:
42// const breakdown = await buff.previewFees(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.38
SOL15%
$12.42
Next swap at $5$1.06 / $5
BuffPortfolio.tsx
typescript
1"use client"
2import { useState, useEffect } from "react"
3import type { Buff, Portfolio } from "@buff/sdk"
4
5interface BuffPortfolioProps {
6 buff: Buff | null
7 refreshInterval?: number // ms, default 60000
8}
9
10export function BuffPortfolio({ buff, refreshInterval = 60000 }: BuffPortfolioProps) {
11 const [portfolio, setPortfolio] = useState<Portfolio | null>(null)
12
13 useEffect(() => {
14 if (!buff) return
15 const fetch = () => buff.getPortfolio().then(setPortfolio)
16 fetch()
17 const interval = setInterval(fetch, refreshInterval)
18 return () => clearInterval(interval)
19 }, [buff, refreshInterval])
20
21 if (!portfolio) return <div className="animate-pulse h-40 rounded-xl bg-card" />
22
23 const total = portfolio.totalUsd
24 const plan = buff?.getCurrentPlan()
25
26 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 pending
31 ({" "}
32 ${portfolio.pendingUsd.toFixed(2)} / ${plan?.investThreshold ?? 5}
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 type { Buff } from "@buff/sdk"
3
4interface BuffStatsProps {
5 buff: Buff | null
6}
7
8export function BuffStats({ buff }: BuffStatsProps) {
9 if (!buff) return null
10
11 const stats = buff.getStats()
12 const plan = buff.getCurrentPlan()
13
14 return (
15 <div className="rounded-xl border p-5 bg-card">
16 <div className="grid grid-cols-2 gap-4">
17 <Stat label="Round-ups" value={stats.totalRoundUps.toString()} />
18 <Stat label="Total invested" value={`$${stats.totalInvestedUsd.toFixed(2)}`} />
19 <Stat label="Buff fees" value={`$${stats.totalBuffFeesUsd.toFixed(2)}`} />
20 <Stat label="Plan" value={plan.tier} />
21 <Stat label="Investing into" value={plan.investInto} />
22 <Stat label="Threshold" value={`$${plan.investThreshold}`} />
23 </div>
24 </div>
25 )
26}
27
28function Stat({ label, value }: { label: string; value: string }) {
29 return (
30 <div>
31 <div className="text-lg font-bold">{value}</div>
32 <div className="text-xs text-muted-foreground">{label}</div>
33 </div>
34 )
35}
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. Styling uses basic Tailwind — customize to match your design system.