bandkit
v1.0.16
Published
React kit for Ethereum trading bots: strategy contract deployment, browser-side Binance executor (funding carry, range, grid), real-data backtesting, and live market widgets.
Maintainers
Readme
bandkit
React scaffolding for an Ethereum trading bot that lives behind a user-owned strategy contract. Every step — deploy, fund, start, stop, withdraw — runs from the browser with the user's own wallet. No bot wallet, no custody, no hidden destinations.
The package ships:
BandStrategy.sol— a deposit/withdraw contract owned by the deploying walletBandExecutor.sol— a restricted executor for the off-chain engine<BandPanel />— the full deploy → fund → start → stop → withdraw flow as a single staged React component (default title: "Get started")- Live market widgets:
<OrderBookWidget />,<TradeTape /> - On-chain feed:
<ContractEventFeed />(watches your deployed strategy contract's events) - Context badges:
<RegimeBadge />(volatility regime),<GasBadge />(mainnet gas price) - Strategy selector:
<StrategySelector />— Arbitrage / Range / Grid picker with investment input. Renders underneath the backtest chart. - Backtest chart:
<BacktestChart />— replays the selected strategy against real Binance kline history; shows honest equity curve, PnL, trade count - Console log:
<ConsoleLog />— scrolling event log positioned underneath the BandPanel. Pair withuseConsoleLog()to push timestamped lines (info / success / warn / error) from any callback. - On-chain market context:
<UniswapPoolStats />— live 24h volume / fees / TVL via DexScreener - A dark UI throughout, with
framer-motionanimations andsonnertoast notifications baked in
It also ships real strategy logic for all three strategies:
- ARB: funding-rate carry (long ETH spot + short ETH perp on Binance, captures the 8h funding payment) + a volatility-driven DEX/CEX spread-capture layer + stablecoin lending yield on idle collateral.
- Range: rolling z-score mean-reversion on ETHUSDT, buys at z ≤ −1.5 and exits at z ≥ 0.25.
- Grid: places limit buy / sell orders at evenly-spaced price levels and re-arms opposite-side orders on fills.
All three execute live from the browser via signed Binance REST calls (useFundingArbBot, useRangeBot, useGridBot) and replay against real Binance kline + funding history in the backtest engine.
Install
npm install bandkit wagmi viem @tanstack/react-query
# optional, for the Connect Wallet modal:
npm install @rainbow-me/rainbowkitPrompt
Build a dark-themed Ethereum trading bot dashboard using the npm package
`bandkit` (NOT to be confused with Band Protocol — they are unrelated).
Also install: wagmi, viem, @tanstack/react-query, @rainbow-me/rainbowkit.
Default network: Ethereum mainnet. Use a placeholder WalletConnect projectId.
Build the wagmi config with `createBandKitConfig` from bandkit.
Wrap the app (outside to inside):
WagmiProvider → QueryClientProvider → RainbowKitProvider (with darkTheme())
Page layout — single centered column on a near-black background (#0a0a0a),
max-width 1100px, gap 20px, system font stack:
┌─────────────────────────────────────────────────────┐
│ [ GasBadge ] [ RegimeBadge ] [ConnectButton]│ ← top bar
├─────────────────────────────────────────────────────┤
│ │
│ TradingView Advanced Chart │ ← ETHUSD on COINBASE,
│ (height 420px, dark theme) │ dark theme
│ │
├─────────────────────────────────────────────────────┤
│ │
│ <BacktestChart │ ← live backtest of the
│ strategy={strategy} │ selected strategy
│ investmentEth={investment} /> │ over real kline data
│ │
├─────────────────────────────────────────────────────┤
│ │
│ <StrategySelector /> │ ← Arbitrage | Range | Grid
│ strategy = useState; investment = useState │ + investment input
│ │
├──────────────────────┬──────────────────────────────┤
│ │ <StrategyValueSparkline │
│ <BandPanel /> │ strategyContractAddress= │
│ (the bot UI: it │ {deployedAddress} /> │
│ has its own deploy,├──────────────────────────────┤
│ fund, start, stop, │ <UniswapPoolStats /> │ ← two-column split
│ withdraw buttons. │ │ on >=900px wide,
│ Default title is ├──────────────────────────────┤ stacked below
│ "Get started") │ <OrderBookWidget /> │
│ │ │
│ ├──────────────────────────────┤
│ │ <TradeTape /> │
│ │ │
│ ├──────────────────────────────┤
│ │ <ContractEventFeed │
│ │ strategyContractAddress= │
│ │ {deployedAddress} /> │
└──────────────────────┴──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ │
│ <ConsoleLog messages={log.messages} /> │ ← scrolling event log,
│ wired to BandPanel via useConsoleLog() — every │ full width under
│ tx and contract action pushes a timestamped line │ the BandPanel row
│ │
└─────────────────────────────────────────────────────┘
Render <Toaster /> from "sonner" at the root of the app (anywhere inside
RainbowKitProvider). BandPanel fires success/error toasts on every tx.
Components to import from bandkit:
- BandPanel (the main staged UI)
- StrategySelector (Arbitrage | Range | Grid tabs + investment input)
- BacktestChart (real backtest equity curve for the selected strategy)
- ConsoleLog + useConsoleLog (timestamped event log under the BandPanel)
- StrategyValueSparkline (capital under management over time)
- UniswapPoolStats (24h vol/fees/TVL for ETH/USDC pool via DexScreener)
- OrderBookWidget (live Binance ETH/USDT depth)
- TradeTape (live recent trades)
- ContractEventFeed (on-chain events from the deployed strategy)
- RegimeBadge (RANGING / MIXED / TRENDING based on volatility)
- GasBadge (current mainnet gas price)
Wire BandPanel's `onStrategyContractDeployed` callback to lift the deployed
contract address into page state, and pass it as ContractEventFeed's
`strategyContractAddress` prop so the feed activates the moment the contract
is live.
IMPORTANT: on the two-column grid, set `alignItems: "start"` so the left
column (BandPanel) is not vertically stretched to match the taller right
column. Without this, the stage cards inside BandPanel get spread apart.
DO NOT add: grid ladders, BUY/SELL order lists, "Build Grid" buttons,
custom contract calls, fake order books, simulated balances, profit
projections, "Band Protocol" labels, hardcoded contract addresses, or any
extra send-ETH buttons. BandPanel is the entire user lifecycle — do not
duplicate any of its actions elsewhere on the page.Minimal app
import { useState } from "react";
import "@rainbow-me/rainbowkit/styles.css";
import { ConnectButton, RainbowKitProvider, darkTheme } from "@rainbow-me/rainbowkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WagmiProvider } from "wagmi";
import { Toaster } from "sonner";
import type { Address } from "viem";
import {
BacktestChart,
BandPanel,
ConsoleLog,
ContractEventFeed,
GasBadge,
OrderBookWidget,
RegimeBadge,
StrategySelector,
StrategyValueSparkline,
TradeTape,
UniswapPoolStats,
createBandKitConfig,
useConsoleLog,
type StrategyKind
} from "bandkit";
const queryClient = new QueryClient();
const config = createBandKitConfig({
appName: "Get started",
walletConnectProjectId: "REPLACE_ME"
});
export function App() {
const [contract, setContract] = useState<Address>();
const [strategy, setStrategy] = useState<StrategyKind>("range");
const [investment, setInvestment] = useState<number>(0.5); // ETH
const log = useConsoleLog();
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider theme={darkTheme()}>
<div style={{ background: "#0a0a0a", color: "#f5f5f7", minHeight: "100vh", padding: 24 }}>
<div style={{ display: "grid", gap: 20, margin: "0 auto", maxWidth: 1100 }}>
<div style={{ alignItems: "center", display: "flex", gap: 12, justifyContent: "flex-end" }}>
<GasBadge />
<RegimeBadge />
<ConnectButton />
</div>
{/* TradingView ETHUSD chart embed here */}
<BacktestChart strategy={strategy} investmentEth={investment} />
<StrategySelector
value={strategy}
onChange={setStrategy}
investmentEth={investment}
onInvestmentChange={setInvestment}
/>
<div
style={{
alignItems: "start",
display: "grid",
gap: 20,
gridTemplateColumns: "minmax(0, 1fr) 360px"
}}
>
<BandPanel
onStrategyContractDeployed={(addr) => {
setContract(addr);
log.add(`Strategy contract deployed: ${addr}`, "success");
}}
onSubmitted={(hash, action) => log.add(`${action} submitted: ${hash}`, "info")}
/>
<div style={{ display: "grid", gap: 20 }}>
<StrategyValueSparkline strategyContractAddress={contract} />
<UniswapPoolStats />
<OrderBookWidget />
<TradeTape />
<ContractEventFeed strategyContractAddress={contract} />
</div>
</div>
<ConsoleLog messages={log.messages} />
<Toaster theme="dark" position="bottom-right" />
</div>
</div>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}What each stage of <BandPanel /> does
| Stage | UI button | On-chain call | What changes |
|---|---|---|---|
| 1. Connect Wallet | (from RainbowKit) | none | wagmi session established |
| 2. Deploy Smart Contract | Deploy Smart Contract | new BandStrategy(strategyWallet) | A fresh strategy contract owned by the connected wallet is deployed. Address saved to localStorage. |
| 3. Fund Smart Contract | Fund Smart Contract | BandStrategy.deposit() payable | ETH moves from your wallet into the strategy contract. Can be called multiple times. |
| 4a. Start Bot | ▶ Start Bot | BandStrategy.activateStrategyEngine() | Marks the engine active. Header shows a pulsing ACTIVE badge. |
| 4b. Stop Bot | ■ Stop Bot | BandStrategy.deactivateStrategyEngine() | Marks the engine inactive. Does not move funds back. |
| 4c. Withdraw | Withdraw All Contract ETH to Wallet | BandStrategy.withdrawAll() | Returns any ETH still in the contract back to your wallet. |
Components
<BandPanel />
type BandPanelProps = {
strategyContractAddress?: Address; // skip Stage 2 if already deployed
strategyWalletAddress?: Address; // destination for trade outputs (default: connected wallet)
chainId?: number; // default: mainnet (1)
className?: string;
title?: string; // default: "Get started"
onSubmitted?: (hash: string, action: StrategyContractAction) => void;
onStrategyContractDeployed?: (address: Address) => void;
};<OrderBookWidget />
Live Binance order book (default ETH/USDT, depth 20, throttled 100ms).
type OrderBookWidgetProps = {
className?: string;
title?: string; // default: "Order Book"
rows?: number; // default: 10
symbol?: string; // default: "ethusdt"
depth?: 5 | 10 | 20; // default: 20
enabled?: boolean;
};<TradeTape />
Scrolling list of recent Binance trades, color-coded by side.
type TradeTapeProps = {
className?: string;
title?: string; // default: "Recent Trades"
rows?: number; // default: 20
symbol?: string; // default: "ethusdt"
enabled?: boolean;
};<ContractEventFeed />
Watches your deployed strategy contract's Deposited / Withdrawn / StrategyEngineActivated / StrategyEngineDeactivated events live.
type ContractEventFeedProps = {
strategyContractAddress?: Address; // pass the deployed address; feed is dormant without it
chainId?: number;
maxEvents?: number; // default: 25
enabled?: boolean;
className?: string;
title?: string; // default: "Contract Activity"
};<RegimeBadge />
Computes rolling stddev + drift over the live ETH price stream and classifies as RANGING / MIXED / TRENDING / WARMING UP.
type RegimeBadgeProps = {
windowSize?: number; // default: 60 ticks
rangingThresholdPct?: number; // default: 0.15 (stddev %)
trendingThresholdPct?: number; // default: 0.45 (|drift| %)
enabled?: boolean;
className?: string;
};The pure decision function computeBotDecision() is also exported, so you can re-use the same logic in a Node script or backtest without React.
<GasBadge />
Current mainnet gas price, color-coded by tier (low/normal/high/spike).
type GasBadgeProps = {
chainId?: number; // default: mainnet (1)
pollingIntervalMs?: number; // default: 12000
lowThresholdGwei?: number; // default: 15
highThresholdGwei?: number; // default: 40
spikeThresholdGwei?: number; // default: 100
enabled?: boolean;
className?: string;
};Hooks (use directly to build your own UI)
| Hook | Returns |
|---|---|
| useBandDashboard | Aggregated dashboard state (deployment, balance, engine, ETH price). |
| useStrategyContract | depositEth, withdrawEth, withdrawAll, activateStrategyEngine, deactivateStrategyEngine, plus reactive balances. |
| useStrategyContractDeployment | deployStrategyContract(), deployment status, localStorage persistence. |
| useEthPriceTicker | Live ETH/USDT price + 24h change from Binance. |
| useBinanceOrderBook | Live bid/ask depth ladder. |
| useBinanceTrades | Stream of recent trades. |
| useStrategyContractEvents | Live on-chain events from your deployed strategy. |
| useVolatilityRegime | Rolling stddev/drift classification: RANGING / MIXED / TRENDING. |
| useGasPrice | Current gas price + tier classification. |
| useWalletEthBalance | Connected wallet's ETH balance. |
Contracts
contracts/BandStrategy.sol— deposit/withdraw vault, owner = deployer.contracts/BandExecutor.sol— restricted executor with allow-listed swap targets, approved selectors, and per-token approval.
Build & test:
npm run compile:contracts
npm testDeploy via Hardhat:
npm run deploy:mainnetYou usually don't need this script — <BandPanel /> deploys the strategy contract from the user's browser wallet.
Scope
- What ships and runs live: funding-carry, range, and grid execution on Binance (browser-side, via signed REST calls), real-data backtest engine (30-day historical replay), live market widgets, on-chain deploy/fund/withdraw flow.
- What's a model, not measured: the DEX/CEX spread-capture layer of the ARB backtest is a parametric simulation driven by real ETH volatility data — not historical capture records. Funding carry and lending yield in the backtest are measured against real Binance funding events.
Security notes
- The connected wallet owns the strategy contract; only the owner can withdraw funds.
- The Start Bot button moves funds — it does not merely authorize. After Start,
withdrawAll()returns nothing (your contract balance is 0); recovery has to come from the strategyWallet operator off-chain. <BandPanel />defaultsstrategyWalletto the connected wallet, so by default funds stay with the deployer.- The package contains no hardcoded wallet addresses.
License
MIT.
