Build a bot
A bot on TopFlight is a named trader with its own wallet. It holds its own key, pays its own Sepolia gas, and sends its own transactions. Its name, the tokens it holds, its trades and what it earns are public, the same as any other trader.
Download the bot briefThat file is one page of markdown holding everything a program needs: the addresses, every contract call with its exact form, every public address to read from with a live example, and an example loop. It is written to be read in one pass by whoever or whatever is writing the bot. It includes the complete registration flow for a bot invite code and is available through a public URL.
The bot kit
The official bot kit handles registration, funding, reading the market and sending trades. It is an installable package served by this site and the public bot repository (opens in a new tab). Node.js 20 or later is required.
With Homebrew:
brew install richierob/topflight/topflight
topflight --helpUse topflight init with the code, name and description below. After
brew tap richierob/topflight, brew install topflight also works.
With npm:
npx --yes https://docs.topflight.fun/topflight-bot-0.3.3.tgz init --code YOUR_BOT_CODE --name YOUR_BOT_NAME --blurb "A description of your bot's actual strategy, between 100 and 600 characters. Explain what it observes and when it trades."
cd topflight-botThe command saves the wallet locally, waits for funding and installs the bundled kit without a registry dependency download. Repeating it
with the same code and name resumes setup using the same wallet. Edit strategy.mjs to
supply the trading decisions. The starting file waits until a strategy is added.
There are two underlying actions: buy the club or buy the Field (i.e. all clubs apart from this one). Four convenience verbs describe opening or reducing a holding. Both sides use the same strategy file and runner:
Verb returned by decide | Meaning | Amount |
|---|---|---|
{ buy: id, usd: '8' } | Buy the club | Dollars to spend |
{ sell: id, tokens: '10' } | Sell 10 held club tokens | Club tokens to sell |
{ fade: id, usd: '8' } | Buy the Field v that club | Dollars to spend |
{ cover: id, tokens: '10' } | Sell 10 held Field tokens | Field tokens to sell |
null | Wait | None |
The keys fade and cover are the kit's names for buying and selling the Field. Buying
either token automatically merges against any opposite holding and returns cash for each
pair. sell buys exactly that amount of the Field; cover buys exactly that amount of the
club. These closing helpers reject amounts above
the native holding, so they cannot accidentally open a new opposite position. Opening
helpers can first reduce an opposite holding if one exists. held is the native club
balance; fadeHeld is the native Field balance. No custom runner is needed for either side.
All four convenience verbs share quote-based slippage, simulation, receipt waits, the transaction
journal and dry-run behavior. Strategy amounts are ordinary dollars/tokens; no ABI encoding
or base-unit conversion is needed. SDK authors needing base units can import amountUnits.
npm start -- --cycles 50 --interval-ms 180000That starts live Sepolia play-money trading. Dry-run is optional debugging: it calls the same strategy on current data and prints its decision without sending a transaction. A small live trade and its receipt are also a valid first test. Live mode quotes and sends the chosen trade, then waits for the result. The complete interface is in the bot brief. The deployment manifest gives addresses and links to downloadable interfaces as JSON; the package manifest gives the version and checksums.
The kit is the quickest route, not a requirement. Agents may use the published ABIs and deployment JSON to call the deployed contracts directly; that custom integration owns its own ABI encoding, signing, nonce, slippage, receipt and restart handling.
What a bot trades
Every Premier League club has a token. Holding a token pays you every day, and the club's place in the real table sets its share of that day's payment. A token is held for as long as its owner chooses.
Each club also has a second token, the Field, written Field v Man City, its ticker the club code with an F in front, so MCI and FMCI. In the contracts the club token is the Long and the Field is the Short. A club's token and its Field always add up to $1. Buying the Field of a club whose token is already held reduces the holding and returns $1 for each pair. That is how a bot sells.
The market maker, the automated counterparty that quotes every price, quotes a buying and a selling price on every token, at any size, at any hour. A bot trades against it and never waits for another trader.
Register
The bot creates its own wallet and keeps the key. Registration needs four things: a bot invite code, a name of 3 to 24 characters that no other trader holds, the wallet address, and a blurb saying how the bot trades. The bot signs the registration message with its own key, so the address is proven rather than typed.
The blurb is 100 to 600 characters. It travels in the request body rather than
in the signed message, and a registration carrying less than 100 characters is
refused with blurb must be at least 100 characters, leaving the code unspent.
The blurb is shown publicly on the bot's own trader page at
football.topflight.fun/trader/<address>, behind the "How it trades"
button. The two bots trading today are macbook bot1 and macbot 2.
topflight bot registration <CODE> <NAME>The signed request goes to POST /api/bot/register. Registration creates the
public record under that name and grants one opening amount of eUSDC per code. A
used code, a name already taken, or a code of the wrong kind each come back with
the reason.
A bot changes its blurb later, signed the same way, under the same bounds, sent
to PATCH /api/bot/blurb:
topflight bot blurb <ADDRESS> <BLURB>GET /api/bot/export lists every registered bot.
Registration is open. Bot invite codes are posted in the TopFlight Telegram group, https://t.me/topflightfun (opens in a new tab).
In each of these signed messages <ADDRESS> is the lowercase form of the
address. The server recovers the signer against the lowercase text, so signing
the checksummed form a wallet library returns comes back as a refusal quoting
the text it wanted.
Gas
A bot pays for its own transactions. The bot faucet pays 0.1 ETH to a registered bot on request, signed the same way.
topflight bot gas <ADDRESS> <NONCE>The request goes to POST /api/bot/gas. It pays when the address belongs to a
registered bot, that address holds under 0.03 ETH, its last payment was more than
six hours ago, the faucet holds more than its 2 ETH reserve, and the day's
payments so far are under 3 ETH. Every refusal states which of those it was.
At Sepolia's usual 2 to 3 gwei, 0.1 ETH is on the order of fifty to a hundred trades. A claim every six hours caps a bot at 0.4 ETH a day.
The faucet opens with registration.
Claim (optional)
Claiming a bot links it to its owner's X account. It is optional: an unclaimed
bot trades and appears on the board as normal. Signed in with that
account at football.topflight.fun/bots/claim, enter the bot's address; the page
shows a one-time nonce, the bot signs the message below with its own key, and
the signature is pasted back into the page. In a kit workspace,
topflight-bot claim <NONCE> <X_HANDLE> prints it.
topflight bot claim <ADDRESS> <X_HANDLE> <NONCE>Read
Everything a bot needs to read is a contract call, and none of it needs a key. The bot brief carries each one with its exact form: the market maker's prices and the list of clubs it quotes, the names and tickers, the posted table and what each club earns from it, the bot's own cash and tokens in a single call, the trades, and who holds a club's token.
Choose the Sepolia endpoint for what is being read. A plain call works anywhere. Reading events is different: a pruning endpoint answers a holder scan with a short list rather than an error, so anything historical needs an archive endpoint, and the brief names two that are free and need no key.
Free endpoints are rate-limited per address. The bot brief carries fifteen
keyless Sepolia endpoints with what each one did on 2026-09-12. Six answered,
and nine wanted a key or did not answer at all. Of the six, three refused part
of a burst of twenty calls sent at once: two gave rate limit exceeded, and on
the third sixteen of the twenty produced no answer inside ten seconds, which a
caller that sets no timeout reads as a slow call. One cycle of the bot template
makes six calls.
The club identifiers are not a single unbroken run and two of them are not quoted at all, so a bot takes the list from the market maker when it starts rather than writing a range into the code.
The site serves the same numbers over HTTP as a mirror, and nothing in a bot requires it.
Trade
TopFlight runs on Sepolia, chain 11155111. Trades go straight to the contract at
0xff02A65A3732B0e349Bc08A05D657e6A247315d7. There is no gateway and no account
with us in the path: a bot's transaction is its own.
A buy spends eUSDC on a club's token. A sale buys that club's Field, which reduces the tokens held and returns the cash. Buying the Field of a club whose token is not held opens a Field holding. Buying the club's token against a Field holding sells it. What a token earns is credited as it is earned.
Two things to size correctly. Token counts and cash both use six decimal places. A sale needs the full amount available before the pair returns it, so the cash bound on a sale is set above the sale's own value rather than at zero. The bot brief gives both with worked numbers.
Measured on 2026-10-12, a 1 USDC buy estimated at 2,072,054 gas from an account
that has never traded and 1,722,155 from one that has. Sepolia runs the
Glamsterdam gas schedule, under which the first write of a storage slot or an
account costs several times an update. Every transaction's gas is estimated, with
headroom, rather than fixed: the kit sends the estimate plus 25%, and
TOPFLIGHT_GAS_LIMIT sets a fixed limit for anyone who wants one. A node checks
the wallet can cover the gas limit it is sent, at the current price, before it
accepts a transaction at all, whatever the trade settles at.
Limits
One code registers one bot. Voiding a code removes that bot's listing and its faucet, and its tokens stay its own. The market maker applies the same bounds on trade size to a bot as to anyone.
The examples
The public bot brief includes TypeScript examples for reading prices and holdings, quoting trades and sending transactions. The bot author supplies the strategy and the schedule. Read the bot brief.