Bursar

Telegram-native treasurer for a Binance Agentic sub-account

It asks before it spends.

Every order is confirmed first, kept inside hard caps and written to a hash-chained ledger you can verify yourself.

One order, replayed from the offline demo

Replay of the offline demo. Fake exchange, nothing here is live.

  1. Message
  2. Ticket
  3. Confirmed
  4. Sending
  5. Filled
Bursar bot owner only
buy 10 usdt of btc
Tickett_5asex7
BUY BTCUSDT · 10.00 USDT
0.00016 BTC @ ~61,250
MARKET · fees extra
After: 90.00 USDT
Spent today: 0.00 / 100.00 USDT
Expires in 120 s. Nothing is sent until you confirm.
ConfirmCancel
Sending the order
You tapped Confirm · same message, edited in place
Receiptt_5asex7
Bought 0.00016 BTC for 9.80 USDT
Avg price: 61,250 · 1 fill · fee 0.00000016 BTC
Binance order 8800001 · Filled
Ledger #8 · 44cf8628702ad18a
same message, edited again
  • Official Binance MCP server
  • OAuth 2.1 with PKCE
  • Hash-chained JSON ledger
  • Offline demo against a fake exchange

Guide

From /start to a verified receipt.

Bursar lives in one Telegram chat and there is nothing to install beyond Telegram. You pair it once, connect Binance once, then talk to it in plain English or Hinglish and tap the buttons it gives you.

  1. 01

    Open the bot

    Send /start. A fresh instance prints a one-time pair code in its server console; /pair with that code binds the desk to your Telegram account, and everyone else is refused from then on.

    /start
  2. 02

    Connect Binance

    /connect answers with one button to Binance's own login page. Bursar receives an OAuth token scoped to the Agentic sub-account. No API key is typed anywhere, and no withdrawal path exists. While Binance admits only its listed agents, the hosted bot runs on the paper desk and /connect says so.

    /connect
  3. 03

    Ask the desk

    Prices and balances by command or in plain words. /price btc and /balance work, and so do "what's btc doing" and "how much do I have". The rule parser needs no language model for any of this.

    what's solana at
  4. 04

    Place an order

    Say the side, the coin and the amount you mean. Bursar walks the live order book for that size and the ticket shows the expected fill, slippage and spread, the 24h range, a thin-book warning when it applies, fee basis, balance after and today's counter. Tap Confirm within 120 seconds or let it expire; either way a ledger line is written.

    buy 10 usdt of btc
  5. 05

    Leave a standing order

    Alerts, conditional buys and schedules, each with a per-order cap, a total cap and a maximum number of runs. Ask mode sends you a ticket when it triggers; auto mode runs inside the caps only if you said "without asking". /orders lists them, /pause and /cancel stop them.

    every day at 9am buy 10 usdt of eth
  6. 06

    Check the book

    /ledger shows the last receipts, /pnl replays them into cost basis and P&L, /brief is the whole desk in one message, every morning if you ask. /verify re-walks the hash chain and names the first broken link, if there is one. /freeze stops everything that moves money until /unfreeze.

    /verify

@thebursarbot is bound to its owner, like every Bursar. Open it from any other Telegram account and it answers with a one-line refusal and the repository link. To run your own desk, clone the repository, set a bot token from @BotFather and start it; the README covers the OAuth callback.

Ticket

Everything you need, before you confirm.

The ticket is the same object the bot will execute, not a summary written afterwards. Amount, estimate, fee basis, caps and expiry come from one calculation.

  • The consequence in one line

    Side, pair and amount, in the quote asset you typed. Nothing is inferred silently.

  • Estimates are marked as estimates

    The tilde is the reference price at ticket time. The fill price comes from the exchange and is reported on the receipt.

  • Caps in view before you decide

    Balance after the order and the daily counter sit on the ticket, so you never confirm against a number you have not seen.

  • Expiry is a hard stop

    An unconfirmed ticket dies after 120 seconds. Cancel is the same size as Confirm, and a cancelled ticket writes a ledger line too.

Tickett_5asex7
BUY BTCUSDT · 10.00 USDT
0.00016 BTC @ ~61,250
MARKET · fees extra
After: 90.00 USDT
Spent today: 0.00 / 100.00 USDT
Expires in 120 s. Nothing is sent until you confirm.
ConfirmCancel

Limits

Hard limits. Not suggestions.

Caps are configuration, not conversation. A request outside them is blocked and logged, and there is no phrasing that gets around a guard.

Standing orderm_3tyb3b
Every hour, buy 10.00 USDT of BTCUSDT
Auto mode, within caps
Caps: 10.00 USDT/order · 100.00 USDT total · max 10 runs
Next run: 2026-09-08 10:00Z
Used so far18.85 / 100.00 USDTRuns2 of 10
PauseCancel

From the offline demo after two automatic runs. Every run is a ticket, and every ticket is a ledger line.

  1. 01

    Per-order cap

    25 USDT

    Default, set by the operator in configuration as the ceiling. /limits lowers it at once from chat; a raise waits 24 hours and never passes the ceiling. A 500 USDT request is refused before any ticket is drafted.

  2. 02

    Daily cap

    100 USDT

    Counted on settled buys, standing orders included. Resets at midnight in the configured timezone.

  3. 03

    Ticket lifetime

    120 s

    An expired ticket cannot be confirmed. A new message produces a new ticket at the current price.

The counter

Money moves the way it does at a counter.

You state the order. The teller writes a ticket. Nothing leaves the till until you sign, or until a standing instruction you signed earlier comes due inside its caps. Every step is entered in the book.

Ledger

Receipts you can verify, in this tab.

The offline demo's ledger, checked here with the bot's own hash rule. Each entry links to the one before it. Change one number and see exactly where the chain breaks.

Loading the demo ledger

Download ledger.jsonl
#KindSummaryprev → hashCheck
This table is filled by your browser from demo-ledger.jsonl. Without JavaScript, download the file and run npm run verify -- ledger.jsonl in the repository.

Scroll to inspect all entries. hash = SHA-256 of the entry with sorted keys and no whitespace, prev included. The first entry points at 64 zeros.

Rails

Three parties, one path for money.

Telegram is the surface, Bursar is the policy, Binance is the venue. No step is skipped and no party is trusted with more than its role.

Read the code, run the demo.

Node 24, two JSON files, no database, no build step. The whole conversation replays offline against a fake exchange.

View the source
  • Telegram

    Where you type and tap. One owner, plain sentences, buttons with a single meaning each.

  • Bursar

    Parses intent, drafts the ticket, applies the caps, writes the ledger and holds the OAuth token.

  • Binance MCP server

    The official Agent OS endpoint. Quotes, balances and market orders on the Agentic sub-account, each order tagged with a client order id.

  • Authorisation

    OAuth 2.1 with PKCE against the Binance MCP server. Bursar publishes its client metadata and receives the callback itself.

Status

What is built, and what is not.

Built for the Binance Agent OS Mini Hackathon, Track A. This is the honest boundary of the build; the status page shows the live values.

What works today?
  • Confirm-first tickets with caps, balance-after and 120 second expiry
  • Standing orders: alerts, conditional buys and schedules, ask or auto mode, pause, resume and cancel
  • Hash-chained ledger with an offline verifier and a tamper test
  • Offline demo of the whole conversation against a fake exchange
  • OAuth client metadata and callback for the Binance MCP server
What is not built, or not by design?
  • Binance's authorization server currently admits only its listed agents (Claude, Codex, ChatGPT, VS Code, Grok); a self-served client id is rejected with error 3346001. The OAuth and MCP code is complete and tested, so the hosted bot and the demo video run on the paper desk: real Binance prices, simulated fills, every receipt labelled Paper order
  • Limit orders, derivatives and withdrawals are not built
  • Self-hosted: one owner per bot, one sub-account. The hosted bot runs in hosted mode instead, where every Telegram user gets their own paper book and their own ledger; those desks share nothing but the process
  • No success rates, no counters, no badges: the ledger is the only proof this project offers
Which account does it trade on?

An Agentic sub-account, authorised through Binance OAuth. Bursar never asks for exchange API keys and never sees the main account.

Which order types are supported?

Spot market orders only, so a ticket always means the same thing. Limit orders and derivatives are not built. There is no withdrawal tool wired in; the capability is absent, not disabled.

Who can operate it?

One Telegram account. The bot is claimed once, with a pairing code printed at start-up or an owner id pinned in configuration. Anyone else gets a refusal, never a ticket.

Do standing orders get the same caps?

Yes. Per-order cap, total cap and a maximum run count per order. Ask-first by default; auto mode only when you say so. Pause, resume or cancel at any time.

What happens when the exchange does not answer?

The budget stays reserved and the order is looked up by its client order id on the next tick. An unknown result is never reported as a failure or a fill.

Does it need a language model?

No. Commands and the fallback parser are deterministic rules. A configured model only widens phrasing coverage, hands back to the rules on doubt, cannot switch a standing order to auto by itself, and never places an order.

Where does state live?

Tickets, standing orders and the ledger are JSON files in a directory you choose. Back them up by copying them. This page is static HTML with fonts served from this domain, no analytics and no third-party requests.

git clone https://github.com/bunnyyxtan/bursar && cd bursar && npm install && npm run demo