Every number on this page came out of a real run against the live Bags mainnet API on 2026-08-15. Nothing here is mocked, replayed, or hand-written. Where something could not be run, it says so instead of showing a plausible number.
Read the flags. BagOS ships a
USE_MOCK_DATAescape hatch, default off, which affects only thebags_get_claimable_feestool: when on, that tool stamps⚠️ [MOCK DATA ENABLED]on its own response. Every other tool ignores the flag. Everything below was captured withUSE_MOCK_DATA=falseset explicitly, so there is no question which path produced it.
USE_MOCK_DATA=false BAGS_NETWORK=mainnet node build/index.js --http # terminal 1
npm run demo # terminal 2Scenarios: 7 passed
Steps: 8 passed
Total: 8 steps across 7 scenarios wall clock 2.935s
| # | Step | Latency | What came back |
|---|---|---|---|
| 1 | HTTP health endpoint | 32ms | status + tool registry |
| 2 | MCP initialize | 17ms | protocol handshake |
| 3 | bags_heartbeat |
689ms | wallet status + claimable summary |
| 4 | bags_get_creators |
427ms | top creators by lifetime fees |
| 5 | bags_get_trade_quote |
388ms | 0.1 SOL → 100002412877777 units |
| 6 | bags_get_partner_stats |
329ms | upstream returned HTTP 500 — surfaced as a message, not a crash |
| 7 | bags_authenticate |
675ms | signature challenge → API key issued |
| 8 | bags_execute_trade |
76ms | denied by the token gate |
Real data, not fixtures — step 4 returned live pool state for
4UeLCRqARmfb6e6KQijtiktqqXUxbfk6jZng7DhuBAGS ("Asteroid The Space Shiba Inu"), lifetime
fees 3921221301402.
Step 6 is worth keeping in the table rather than hiding: the Bags partner-stats endpoint
returned a 500 during this run, and the tool reported
Failed to fetch partner stats: Request failed with status 500 as ordinary text. An
upstream outage does not take the server down or fabricate a value.
These are the three things the README claims. Each one is reproduced here as output.
The network-mismatch guard refuses to start. With BAGS_NETWORK left at its devnet
default while the configured RPC points at mainnet, the server does not boot:
[FAIL] network Network mismatch: BAGS_NETWORK is "devnet" but the configured RPC
endpoint points at mainnet. Set BAGS_NETWORK=mainnet, or point
SOLANA_RPC_URL at a devnet endpoint.
Server not started: fix the [FAIL] items above.
The failure mode this prevents is signing a mainnet transaction under a devnet banner.
The startup report redacts credentials. The RPC URL carries an API key in its query string; the report strips it:
[ok] RPC https://mainnet.helius-rpc.com/?api-key=REDACTED
The token gate denies a real write in 76ms. Step 8 above called the actual write tool against the actual wallet:
❌ Access denied: wallet holds 0 $BOS, but 10000 is required to trade.
Nothing was signed, nothing was sent, and the refusal came from the gate — not from a missing key or a mocked branch.
npm run ci # lint + typecheck + tests with coverageTest Suites: 13 passed, 13 total
Tests: 212 passed, 212 total
Time: 2.313 s
All files | 100 | 100 | 100 | 100 |
100% of statements, branches, functions and lines — and now actually enforced at 100.
jest.config.ts previously gated at 90/95/95/95, so the figure above was true by accident
rather than by construction, and would have stopped being true the first time anyone landed
an untested branch. The thresholds are 100 across the board as of this run.
The load-bearing tests are the bypass tests around the spend caps and the confirmation step, plus the leak-channel test added after the API key was found being echoed into tool output. They are mutation-checked: removing the cap guard, the confirmation check, the decimals lookup, or the spend recorder each makes the suite fail.
npm run proof:devnet pushes a transfer through the same simulate → sign → send → confirm path the write tools use (src/lib/execute.ts), then re-fetches the signature
from the chain rather than trusting the function's return value. Captured
2026-08-16:
network devnet
keypair 3nFqw88x51A2sPZvxg7RjkbmsFUmaPM3EakL71MDYhCw
balance 10 SOL — already funded, skipping the faucet
--- PROOF -------------------------------------------------
signature 2kvu25xWAjqCB3wuNzwMRcN2RMqqfYN6TeJjnA888YtCqNJi9EU9CHSxynkq5QdM499e6yKbXYAwXUbzDKY9U5Dm
slot 484219564
wall 864 ms (simulate + sign + send + confirm)
-----------------------------------------------------------
verified re-fetched from chain in slot 484219564, err=null
fee 5000 lamports
Verify it yourself — this is the point of the receipt, and it needs nothing from us:
curl -s -X POST https://api.devnet.solana.com \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getTransaction",
"params":["2kvu25xWAjqCB3wuNzwMRcN2RMqqfYN6TeJjnA888YtCqNJi9EU9CHSxynkq5QdM499e6yKbXYAwXUbzDKY9U5Dm",
{"encoding":"json","maxSupportedTransactionVersion":0}]}'
# → slot 484219564, meta.err null, meta.fee 5000Why this receipt and not a mainnet one: it is the execution layer that matters here, and devnet exercises it identically while costing nothing real. Do not substitute a mainnet transaction or a hand-written hash — an unverifiable receipt is worse than an absent one.
To reproduce from scratch: run it once to print and save the payer address, fund that
address on devnet (not testnet), then run again. The keypair persists to .proof/
(gitignored), so the second run reuses the same address, and it skips the faucet entirely
when the balance is already there:
npm run proof:devnet # prints the address, saves the keypair
solana airdrop 0.1 <address> --url devnet # or https://faucet.solana.com, network = Devnet
npm run proof:devnet # same address, now fundedStatus: not run. No human has executed this yet, so there is no signature below. It stays marked TODO until one exists. An empty receipt is honest; an invented one is not, and this file's whole claim is that its numbers came out of real runs.
Receipt 4 proves the execution layer lands transactions, but it bypasses the Bags
SDK, the token gate, the caps and the confirmation token — it transfers SOL to
itself on devnet. scripts/mainnet-proof.ts exercises the full
bags_execute_trade path in the order the tool uses it:
quote → token gate → spend caps → confirmation token → createSwapTransaction
→ simulate → sign → send → confirm → re-fetch from chain
This spends real money. It refuses to run unless both variables are set —
there is no default that reaches mainnet — and it enforces a hard 0.01 SOL
ceiling of its own, independent of BAGS_MAX_SOL_PER_TX, so that running it is
never a financial decision.
# Both are required. Neither has a default.
export I_UNDERSTAND_THIS_SPENDS_REAL_SOL=yes
export BAGS_NETWORK=mainnet
# Your funded mainnet keypair and a Bags API key.
export BAGS_KEYPAIR_PATH=~/.config/bags/keypair.json
export BAGS_API_KEY=...
# The gate token you must HOLD: $BOS, the token this project launched on Bags.
export BOS_TOKEN_MINT=Feqmy64uNvK198MAWFC5ujRnzif6kM9wKonTX2t3BAGS
# The token to swap INTO. Deliberately not $BOS: the gate wants a token you hold,
# the swap wants a token with liquidity, and $BOS has almost none. This one is a
# third-party Bags-launched token with a real pool, used only as a swap target.
export PROOF_OUTPUT_MINT=EkJuyYyD3to61CHVPJn6wHb7xANxvqApnVJ4o2SdBAGS
npm run proof:mainnet # swaps 0.01 SOL; PROOF_SOL_AMOUNT can only lower itOn success it prints the signature, the explorer link, the slot, and the result of re-fetching the signature from the chain. Paste that block here, replacing this notice.
Do not substitute a devnet transaction, a hand-written hash, or output from a run that errored. If it has not been run, this section says so.