BullCheese documentation
How a token is launched, what is fixed and what is chosen, and where the numbers on screen come from. Written against the code that runs the app — every figure below is read from the module that decides it, so this page cannot drift from the product.
Overview
BullCheese launches fixed-supply tokens against a single Uniswap v3 position. The whole supply goes into that position, entirely on the token side, priced above spot. The creator posts none of the pair asset — every buyer brings their own, and the position converts to the pair asset as the price climbs.
There is no bonding curve and no migration step. The pool a token trades in on its first block is the pool it trades in forever; nothing has to be moved, re-deployed or graduated for trading to become real. The tiers below decide how a launch is listed and change nothing about the token or its pool.
How a launch works
A launch is one on-chain transaction, with three steps around it:
- Sign in. A signature proving the wallet, so the launch can be recorded against it afterwards. No transaction, no cost.
- Pin the metadata. Name, ticker, description, image and links are pinned, and the resulting hash is written into the token at deploy time. It cannot be added afterwards, which is why this happens before the transaction rather than after.
- Deploy. One call to MintPlus mints the supply, opens the pool, adds the position and locks it. Token, pool and locker all come out of that single transaction.
The position runs from just above the opening price to the top of the usable tick range. That upper bound is not a price anyone expects to reach — it is what “every price above the start” means in a range-based pool.
Either the transaction goes through and your token exists, or it reverts and nothing was created — there is no half-launched state to clean up. Once it has gone through, your token is real even if something afterwards is slow to catch up.
Launch terms
Most terms are properties of the deployment rather than choices. A creator picks the token’s identity, its opening market cap, its lock duration and an opening buy; everything else is the same for every launch on a chain.
- Total supply
- 1,000,000,000 tokens
- Decimals
- 18
- Opening market cap
- $20.0K or $5.0K — your choice
- Pool fee tier
- 1% (
10000in Uniswap’s units) - Position
- One-sided, entirely token, from just above spot upward
- Liquidity locked
- 100% of the position
- Pair asset
- USDC — set in the MintPlus contract, the same for every token here
Supply and decimals are the same for every token here, which is why the Create screen does not ask for them.
The opening market cap is the one pool term you do pick, and it is only a price: the cap divided by the supply is what one token costs on the first block. Everything else is identical either way — same supply, same range, same lock — so a $5.0K launch is the same token as a $20.0K one, opened cheaper.
The liquidity lock
The whole LP position is locked at launch, for 90 days unless the creator picks otherwise. The choice is one of a fixed set:
- 90D — 90 days
- 180D — 180 days
- 1Y — 365 days
- 3Y — 1095 days
- 5Y — 1825 days
The locker enforces a minimum of 30 days, and it checks the duration against the block timestamp at execution rather than at signing — which is why the shortest option here clears that floor by weeks rather than by minutes.
The position is held by a Team Finance locker, and it does not come back on its own. When the lock expires you withdraw it at app.team.finance, which is also where you can look the lock up in the meantime and reach the locker’s other functions.
The position comes back to the wallet that launched the token. The Create screen has no field to send it anywhere else. Each launch gets its own locker contract, and that wallet owns it.
Ownership of the locker can be handed to another wallet afterwards, in two steps: the owner proposes the new wallet, and the new wallet accepts. From then on the new owner is the one who withdraws the position when the lock expires and who collects the creator’s share of the fees. Hand it only to a wallet you control.
A lock is not a promise about price. It means the liquidity cannot be pulled for the duration; it says nothing about what the token is worth while it sits there.
Tiers
Every launch sits at one of three tiers, and the tier decides which section of Explore it appears in. A tier is earned by trading, never by price: a token can be worth millions and sit in Fresh Cheese if nobody trades it.
The tier is assigned by BullCheese’s backend and read by this app, not worked out here. That is not a technicality — see the Bulls Arena below for the reason it cannot be.
- Fresh Cheese
- Every launch starts here. Its card’s bar measures the 7-day trading volume against the gate below.
- Aged Cheese
- Traded at least $20.0K over a rolling 7-day window. Its bar measures the quote-side liquidity the Bulls Arena asks for — the trading half is already done, which is why it is on this shelf.
- Bulls Arena
- A member of this week’s cohort. No bar — a member has arrived.
A rolling window, so this moves both ways. A launch that stops trading drops back to Fresh Cheese as its busy week ages out; it is a liveness label, not an award, and nothing about the token or its pool changes when it moves.
$20.0K is the figure on Arc. Your own token’s page always shows the number deciding it, next to the bar measuring it — so if the two ever disagree, the token’s page is the one to believe.
Every bar in the app measures one condition, and a full one always means that condition is met. A card has room for the one its shelf is short of; a token’s own page shows every condition, with the figure and the threshold beside each. A condition nobody has measured yet shows an em dash rather than an empty bar — not being read is not the same as falling short.
The Bulls Arena
The Arena is a weekly competition, and a place in it is membership rather than a ranking of everything else. There is no fixed number of places: every launch that meets both conditions at the weekly intake is admitted. Members are admitted only at that intake — never the moment they qualify — and a launch appears in the Arena instead of in Aged or Fresh Cheese, not as well. Members are listed by their 7-day trading volume.
Admission asks for two things at once, and both are checked at that intake:
- Trading
- At least $20.0K of trading volume over the last 7 days
- Liquidity
- At least $50.0K of USDC sitting in the pool. It is the money buyers have put in: you post none of it yourself.
- When the intake runs
- Every Monday at 00:00 UTC. The date on your token's page is the one to plan around.
- Minimum age
- None. A launch can be admitted at the first intake after it opens.
- What counts as volume
- Every trade in the pool. Nothing is filtered out, including wash trading and the creator's own buys.
- A launch that cannot be read
- If its pool, price or volume cannot be read at the intake, it is not admitted that week and waits for the next one.
- Changes to the thresholds
- The thresholds in force when a week opens hold for that whole week. A change applies from the next intake.
Joining and leaving are not symmetrical. Admission waits for the intake; falling out does not wait for anything. If either condition slips at any point during the week, the launch leaves the Arena immediately and cannot return until the next intake.
So a token that already meets both conditions can sit at a full bar for days, waiting for the clock rather than for anything you can do. Its page says so, and gives the date of the next intake — that date is the one to plan around, since it comes from the Arena itself rather than from this page.
The Arena does not run on every network. Where it does not, Aged Cheese is the top tier however much a launch trades or holds.
Fees
- To launch
- Nothing upfront beyond gas
- Trading
- 1% of each swap, to the pool
- Fee split
- 75% of collected LP fees to the creator, 25% to the platform, taken when fees are claimed.
- Collecting
- The creator collects them from their token's page. Both shares move in the same transaction: the creator's to the wallet that owns the lock, the platform's to the platform.
The trading fee is the pool’s Uniswap fee tier, so it accrues to whoever holds liquidity — which, for the locked position, is the launch itself.
The split is set on the contract, not by this page. Every screen that shows it — the Create form and your token’s fees panel — reads it from the contract as you look at it, so if those ever disagree with the figure above, they are the ones to trust.
Launching a token
Connect a wallet on a supported network and open Create. The form asks for a name, a ticker, an optional description, image and links, a lock duration, an opening market cap and an opening buy. Supply, decimals, the fee tier and the pair asset are stated rather than asked for.
The launch button says what is missing rather than sitting disabled and silent — connect a wallet, switch network, fill in the required fields, or wait for pricing. That last one is a deliberate block: the pair asset’s price sets the pool’s opening price, and the app will not launch on a missing or stale quote.
The form also offers an opening buy — a small trade against your own pool, made at launch so the pool has something on the quote side and exchanges will list it. The amount is yours to set, and so is where the tokens it buys land: your own wallet, which is what the form offers first, or an unreachable address, which leaves nobody holding a position from that buy — not even you. Keeping them is a position bought at the lowest price the token will ever have, and it is visible on chain to everyone who looks at the token afterwards.
Where the chain allows it, that buy travels inside the launch itself. Arc carries a sender-preserving batch contract, so the deploy, the USDC approval and the buy run as one transaction: one signature, and if any part fails none of it happened. Elsewhere the wallet’s own batching does the same job where it exists, and where it does not the buy follows as its own transaction — announced before your wallet asks, and declinable. Your token exists either way.
While the transaction is in flight the screen shows what is being created and links the transaction as soon as the wallet returns one. Keep the page open — closing it does not cancel the launch, but nothing on the other side will tell you how it went.
Trading
Each token page carries a swap box, TeamSwap, which trades that token against the chain’s pair asset. Quotes come from the 0x Swap API, and approvals and the swap itself are ordinary transactions from the connected wallet.
Quotes cover the network this build launches on. 0x serves 22 chains, Arc among them. On a network it does not cover, the swap box says so rather than leaving you waiting for a quote that is not coming. The pool itself is a normal Uniswap v3 pool and can be traded anywhere that supports the chain.
Approvals are for the amount of the swap rather than unlimited. That costs an approval each time and leaves no standing permission over the balance afterwards.
Swap settings carry an optional gasless mode, off by default. With it on, 0x sends the transaction and takes the gas out of the tokens you sell, so the swap needs no native coin at all — you sign twice instead of paying gas. It is the more expensive way to trade, because the fee is 0x’s rather than the network’s, and it is offered only on networks 0x relays for and only to wallets that can sign typed data. A smart-contract wallet cannot, so it is not offered one.
Bridging to Arc
Arc uses USDC as its gas as well as its money, which means a wallet with nothing on Arc cannot do anything on Arc — including receive a bridge transfer, since the arriving transaction has to be paid for. Circle’s forwarding service is what resolves that: you sign twice on the chain you are sending from, and Circle submits the arrival.
The USDC is burned where it leaves and minted where it arrives, using Circle’s own transfer protocol. Nothing is wrapped and nothing sits in anyone’s escrow. It lands at the address that sent it — there is no recipient field, deliberately.
Each transfer carries a bridge fee of 0.5% of the amount, never less than 0.5 USDC and never more than 50 USDC. It is added on top of what you send, not taken out of it, and paid in USDC on the chain you send from. Circle’s own fee is separate and comes out of what arrives. The bridge form shows both before you sign.
Finding a launch
Explore has five views: everything, the Bulls Arena, Aged Cheese, Fresh Cheese, and the tokens the connected wallet has launched. Search matches a name, a ticker or an address.
A launch appears in exactly one of the three tier sections, never two — so the Bulls Arena is where its members are, not a second copy of them.
Each card links to the token’s own page, which carries its details, its standing, the price chart, the swap box and recent trades.
AI agents (MCP)
BullCheese MCP is a Model Context Protocol server that gives an AI agent its own wallet on Arc. With it, an agent can launch a token, trade, collect creator fees and browse launches from any harness that speaks MCP — Claude, Codex, Gemini, Cursor, VS Code and the rest. It is published on npm as @trustswap/bullcheese-mcp, and its source is on GitHub.
A launch made by an agent is an ordinary MintPlus launch: the same contracts, the same terms and the same lock as one made from the Create screen, and it appears in Explore like any other.
Use a dedicated wallet. The server signs real transactions on Arc mainnet with the key you give it, and its spend caps are off unless you set them — an agent can spend everything the wallet holds. Fund it only with what you are willing to lose, and never give it your main key.
1. Check the requirements
- Node.js
- Version 24 or newer, which includes
npx. Older versions fail at startup, because the audit log usesnode:sqlite. - A wallet
- A private key for a wallet holding USDC on Arc. It pays for gas and is the pair asset every launch and swap spends.
To generate a fresh throwaway key and its address:
cd "$(mktemp -d)" && npm install --silent viem && node --input-type=module -e '
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const key = generatePrivateKey();
console.log("key: " + key);
console.log("address: " + privateKeyToAccount(key).address);
'Without funds the server still starts and every read-only tool works; launching and trading wait until the address has a balance.
2. Install and verify
There is nothing to clone or build. Every client below starts the server with npx -y @trustswap/bullcheese-mcp, which downloads it from npm on first use. Run the protocol handshake by hand once before wiring a client: it catches a wrong Node version or a failed download that a client would hide behind a generic error, and it fills npx’s cache so the client’s first start is fast.
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| env BULLCHEESE_LOCAL_PRIVATE_KEY=0xYOUR_KEY BULLCHEESE_AUDIT_DB=:memory: \
npx -y @trustswap/bullcheese-mcp 2>/dev/null \
| tail -1 | python3 -c "import sys,json;print('tools:',len(json.load(sys.stdin)['result']['tools']))"The expected output is tools: 16. Anything else means stop there — a client will not fix it.
3. Connect your agent
Replace 0xYOUR_KEY with the key from step 1. Each harness names the same three things — the command, its arguments and the key — in its own format.
Claude Code
Run in a terminal, then check it with claude mcp list.
claude mcp add bullcheese \
-e BULLCHEESE_LOCAL_PRIVATE_KEY=0xYOUR_KEY \
-- npx -y @trustswap/bullcheese-mcpClaude Desktop
claude_desktop_config.json — macOS ~/Library/Application Support/Claude/, Windows %APPDATA%\Claude\, Linux ~/.config/Claude/.
{
"mcpServers": {
"bullcheese": {
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "0xYOUR_KEY" }
}
}
}Merge into the file rather than replacing it, then quit the app fully (⌘Q on macOS) — closing the window does not reload the config.
Cursor, Windsurf and Cline
Cursor ~/.cursor/mcp.json (or .cursor/mcp.json per project), Windsurf ~/.codeium/windsurf/mcp_config.json, Cline cline_mcp_settings.json.
{
"mcpServers": {
"bullcheese": {
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "0xYOUR_KEY" }
}
}
}Restart the editor afterwards.
VS Code (GitHub Copilot)
.vscode/mcp.json for one project, or your user settings.json for all of them.
{
"servers": {
"bullcheese": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "0xYOUR_KEY" }
}
}
}The key is "servers", not "mcpServers", and "type" is required.
Codex CLI
~/.codex/config.toml
[mcp_servers.bullcheese]
command = "npx"
args = ["-y", "@trustswap/bullcheese-mcp"]
[mcp_servers.bullcheese.env]
BULLCHEESE_LOCAL_PRIVATE_KEY = "0xYOUR_KEY"Gemini CLI
~/.gemini/settings.json
{
"mcpServers": {
"bullcheese": {
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "$BULLCHEESE_LOCAL_PRIVATE_KEY" },
"timeout": 30000
}
}
}Values can reference your environment as $VAR, so the key need not be written into the file. Leave trust at false to keep tool-call confirmations on.
Zed
settings.json
{
"context_servers": {
"bullcheese": {
"source": "custom",
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "0xYOUR_KEY" }
}
}
}Without "source": "custom" Zed skips the server silently.
Goose
~/.config/goose/config.yaml
extensions:
bullcheese:
name: bullcheese
type: stdio
cmd: npx
args: ["-y", "@trustswap/bullcheese-mcp"]
envs:
BULLCHEESE_LOCAL_PRIVATE_KEY: "0xYOUR_KEY"
enabled: true
timeout: 300Goose uses cmd and envs, not command and env.
opencode
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"bullcheese": {
"type": "local",
"command": ["npx", "-y", "@trustswap/bullcheese-mcp"],
"environment": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "$BULLCHEESE_LOCAL_PRIVATE_KEY" },
"enabled": true
}
}
}The command and its arguments are one array, and the env block is called environment.
OpenClaw
openclaw.json, or the Control UI at /settings/mcp.
{
"mcp": {
"servers": {
"bullcheese": {
"command": "npx",
"args": ["-y", "@trustswap/bullcheese-mcp"],
"env": { "BULLCHEESE_LOCAL_PRIVATE_KEY": "0xYOUR_KEY" }
}
}
}
}Servers nest under mcp.servers. OpenClaw filters the environment it passes on, so list every BULLCHEESE_ variable under env and restart the gateway after a change.
Hermes
Your Hermes config.
mcp_servers:
bullcheese:
command: "npx"
args: ["-y", "@trustswap/bullcheese-mcp"]
env:
BULLCHEESE_LOCAL_PRIVATE_KEY: "${BULLCHEESE_LOCAL_PRIVATE_KEY}"
BULLCHEESE_PERSONA_ID: "my-agent"Hermes passes a filtered environment, so every variable the server needs must be listed under env. Its approval prompts cover shell commands, not MCP tool calls — the spend caps below are what limit spending.
Any other MCP client
Run npx -y @trustswap/bullcheese-mcp as a stdio server with BULLCHEESE_LOCAL_PRIVATE_KEY in its environment. The server speaks JSON-RPC on stdout and logs to stderr, so nothing else may write to stdout.
To serve several agents from one process over HTTP instead, run the HTTP binary. Agents share the wallet, and each bearer token names a persona in the audit log:
BULLCHEESE_LOCAL_PRIVATE_KEY=0xYOUR_KEY \
BULLCHEESE_AGENT_TOKENS='{"<token>":"my-agent"}' \
BULLCHEESE_HTTP_PORT=8080 \
npx -y -p @trustswap/bullcheese-mcp bullcheese-mcp-httpClients then POST /mcp with Authorization: Bearer <token>.
Desktop apps do not inherit your shell’s PATH. If the client reports npx: command not found or starts the wrong Node, use the absolute path from which npx as the command.
4. First run
Ask your agent these, in order. None of them spends anything:
- “What chains can you launch on?” —
list_chains, which proves the wiring works. - “What’s my wallet address and balance?” —
get_walletandget_balances. - “Browse recent BullCheese launches.” —
explore_launches, which proves the feed reads.
Then, with a funded wallet, ask for a quote on a launch. A quote costs nothing and executes nothing.
Tools
- Launching
quote_launchlaunch_tokenpredict_token_addresspin_imageget_launchlist_my_launches- Trading
quote_swapswapcollect_feesget_balances- Reading
list_chainsget_walletexplore_launchesget_launches_by_ownerget_launch_by_tokenget_launch_detail
Configuration
| Variable | Default | Purpose |
|---|---|---|
BULLCHEESE_LOCAL_PRIVATE_KEY | required | 0x-prefixed 32-byte hex key the agent signs with |
BULLCHEESE_POLICY_TX_CAP | unlimited | Most the agent may spend in one transaction, in whole USDC |
BULLCHEESE_POLICY_DAILY_CAP | unlimited | Most the agent may spend in any rolling 24 hours, in whole USDC |
BULLCHEESE_PINATA_JWT | unset | Enables pin_image and metadata pinning for launches |
BULLCHEESE_RPC_5042 | https://arc.drpc.org | Overrides the Arc RPC |
BULLCHEESE_AUDIT_DB | ~/.bullcheese-mcp/audit.db | SQLite path or postgres:// URL for the audit log the daily cap is computed from |
BULLCHEESE_TF_API_QUERY_URL | https://api.team.finance | Source for the read-only feed tools; "" disables them |
BULLCHEESE_PERSONA_ID | default | Label recorded against each call in the audit log |
What stands between the agent and the wallet
- Spend caps
- Off by default. Set
BULLCHEESE_POLICY_TX_CAPandBULLCHEESE_POLICY_DAILY_CAPto enforce a limit per transaction and per rolling 24 hours, checked before anything is signed. - Quotes expire
- Every quote tool returns a quote id valid for 60 seconds, and the tools that execute take that id — nothing runs without a fresh quote.
- Audit log
- Every call is recorded with the wallet, chain, tool and outcome.
- Untrusted text
- Token names, tickers and descriptions come from whoever launched them. An agent should treat them as data, never as instructions.
Updating and removing
npx caches the package after its first run. To move to a newer release, clear the cache with rm -rf ~/.npm/_npx and restart the client. To remove the server, delete its entry from the client’s config; ~/.bullcheese-mcp holds the audit log, and deleting it also discards the spend history the daily cap is counted from.
Network
This build launches on Arc, and every token here trades against USDC. The pair is set inside the MintPlus contract, so it is the same for every launch — it is not something a creator picks.
| Network | Chain ID | Pair asset | Kind |
|---|---|---|---|
| Arc | 5042 | USDC | Stablecoin |
The distinction matters for pricing and for paying. A stablecoin is treated as a dollar and needs no quote; wrapped native needs a live one, and the app refuses to launch without it.
Contracts
The contracts a launch on Arc touches: five MintPlus contracts, the Team Finance locker that holds the position, and the Uniswap contracts that open the pool and make the opening buy. Verify any of them on chain before relying on it.
Arc (5042)
- MintPlus
0x16D4c13aD2A23288AA9b9384F24084edC8CBeF41- Token deployer
0x8Be8dF30809CFc8FB69B41a0FBD4A1391D973f4a- Token implementation
0x5FB8526D5FC7040959CB1a4f5a4dc88BF0468c28- Locker deployer
0x7c466B81335eD91a434711d661de17cb0E0B9AbB- Locker implementation
0x3C97Ba03df0F6152d2aAcD96d63591819e8440B8- Team Finance locker
0x154479cA34D77A176E74C038b70df102D9Be9935- Pair asset
0x3600000000000000000000000000000000000000- Uniswap V3 position manager
0x39654A85A4C05127f5Fd6ED22CAeC077A0fB1377- Uniswap V3 factory
0xf0db7b58379503491d857dB50AC9ece64c653918- SwapRouter02 (opening buy)
0x53bf6b0684ec7ef91e1387da3d1a1769bc5a6f77- Multicall3From
0x522fAf9A91c41c443c66765030741e4AaCe147D0
Verify these on chain before relying on them. They are recorded here from Team Finance’s deployment, and an address copied from documentation is exactly the kind of thing that should be checked rather than trusted.
A brand-new token page
For the first few minutes after a launch, a token’s page looks thinner than it will later: no image, no description, no price or volume. Nothing is wrong. Those come from the indexer, which has not reached the token yet, and the page falls back to reading the chain so that your token is there the moment it exists rather than after a wait.
Everything catches up on its own once the indexer picks the token up. There is nothing to re-submit and nothing to fix.
If a page cannot be loaded at all, it will tell you which of two things happened: the token could not be found, or we could not reach the network to ask. Those are different problems and only one of them is worth retrying.
What a dash means
Where a figure would go, a dash — — — means nobody has measured it yet. It does not mean zero, and it is not a mark against your token. A launch with no volume reading has not failed to trade; it has not been read.
The same goes for the conditions on a token’s page. One that says Not measured may already be met — your liquidity might clear the gate comfortably and simply not have been counted yet.
Some cards carry no tier at all: a token still too new to be indexed, and everything under “My Tokens”. They show no shelf rather than the bottom one, because putting a token in Fresh Cheese would be a judgement on figures nobody has collected.
Risks
- A launch cannot be undone. The supply is minted, the position is opened and the lock is set in one transaction. There is no owner switch that reverses it.
- The lock runs to its end. A duration chosen at launch cannot be shortened. Whoever owns the launch’s locker when it expires gets the position back, so transfer that ownership only to a wallet you control.
- A one-sided pool starts thin. The whole supply sits above spot, so early buys move the price a long way. That is the design, not a fault, but it means price impact on a young launch is large.
- A tier says nothing about quality. Both gates are volume and liquidity, and both can be manufactured. A Bulls Arena badge means a launch met two thresholds last week — no more than that.
- Metadata is pinned, not policed. A name, an image and a link are whatever the creator supplied. Nothing here vouches for them.