Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmarks/terminal-fill-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ methodology:
- "Quote side, exact from balance deltas: user_q = what left or reached the user's SOL / WSOL / stable balance, the tx fee included when the user paid it, rent of accounts created or closed in the transaction excluded; pool_q = the pool's quote movement, converted to the user's quote unit when the pool is quoted differently; terminal_q = lamports, WSOL or stables landing in the fee wallets (FOMO: plus its user-signed USDC legs, 2 % of the trade or its $0.10 minimum)."
- "network_q = tx fee if the user paid it + inclusion tips: Jito, 0slot, bloXroute, Astralane, Nozomi and each terminal's own relay accounts (Axiom, Trojan, Maestro, Photon, Pepeboost, pump.fun app). other_q = user_q minus pool_q, terminal_q and network_q on single-pool routes without hops (pump.fun protocol and creator fees, referral payouts); on routed swaps it stays inside pool."
- "Pool identity: the token vault that moved against the user and the quote vault of the same swap instruction, both by pubkey. Two pools behind one shared authority (Raydium, Launchpad, Meteora DAMM v2) never merge, so a hop through a sibling pool is never read as pool impact and the previous-trade lookup only sees that pool."
- "Reference price, in order: the pool's exact pre-trade mid from its balances in the transaction on one constant-product pool (PumpSwap, Raydium); the venue's swap event when it carries the state before the trade (Launchpad, DLMM), when it reconciles with the vault deltas; else the previous trade on the pool at most 60 s old (pump.fun curve, CLMM, routed swaps). No off-chain token price; gas coins at Coinbase spot on the Relay rows, at the route's own hop mid on the native EVM rows."
- "Reference price, in order: the pool's exact pre-trade mid from its balances in the transaction (PumpSwap, Raydium); the venue's swap event when it carries the pre-trade state (Launchpad, DLMM) and reconciles with the vault deltas; else the previous trade on the pool, at most 60 s old (pump.fun curve, CLMM, routed swaps), up to 2 h on the EVM launchpad curves BasedBot's router trades on. No off-chain token price; gas coins at Coinbase spot on Relay rows, at the route's hop mid on native EVM rows."
- "Loss (headline): buy = 1 − tokens × ref / user_q; sell = 1 − user_q / (tokens × ref). Basis points of the trade (buy: quote spent; sell: tokens × ref, plus the gas on an EVM chain, paid apart from the tokens). Pool = loss minus terminal, network and other: the components (relay on Relay rows) sum to the loss per swap; the split columns are medians (flow-weighted on All chains) and do not sum to the median loss. Losses outside −10 % to +50 % stay out. Median with its 95 % interval, p90."
- "A product is published from 50 priced swaps in the window (a chain row, or a single-chain product, from 25) and marked as ranked from 100; a pooled entry also needs 25 effective swaps (weights considered), a row with one side only and no fee on it waits, and a product waits while its main chain fills. The previous-trade reference carries the previous trader's direction, so terminals priced mostly that way read some tens of bps worse in buy waves than on the exact mid."
- "Fail rate: failed over every swap attempt the feed saw on the terminal's wallets in the window, exhaustive; wallet funding, fee sweeps and marker transfers invoke no swap program and are not attempts. Error classes are in the JSON. On the EVM rows the fail rate comes from a sample of blocks read in full (every transaction sent to the routers, reverted or not)."
Expand All @@ -73,7 +73,7 @@ methodology:
- "Which side of an EVM pool is the token is settled by matching the swap event's amounts with the receipt's transfers, not the pool's token order. Split: terminal = the app fee; relay = what Relay kept (fees, spread, gas); pool = the settlement swap's impact and LP fee; network = the Solana tx fee (0 when the app's sponsor signs). Sales the other way (a token sold on that chain, USDC or SOL delivered on Solana) sit in the same per-chain row, priced at the origin pool's state before the sale."
- "FOMO sponsors gas: its own signer pays the tx fee, so the user's network cost is zero; its fee is a router commission plus user-signed USDC legs, all counted as terminal fee. pump.fun's app takes no fee; the fixed 0.001 SOL it forwards on some swaps is counted as network."
- "Native EVM (GMGN and Axiom on BNB and Robinhood Chain, Banana Gun): their router contracts emit an event on every swap; eth_getLogs on those routers lists every successful swap they routed; the fail rate comes from a sample of blocks read in full (every tx sent to the routers, reverted or not). Value given = what the user sent plus gas; received = the tokens at the pool's state before the swap; the fee is paid inside the router as a native transfer: the residual after pool and gas."
- "Unpriced draws: on the EVM rows a share of the drawn swaps (about a sixth on BNB, a quarter on Robinhood Chain, more on BasedBot's row) cannot be valued at the pool's state (a route through pools without a quote leg, a venue whose event is not decoded) and stays out of the figure; each entry's unpriced_share and its rejects in the JSON say how much and why."
- "Unpriced draws: on the EVM rows a share of the drawn swaps (about a sixth on BNB; on Robinhood Chain 2 of 35 fills on 2026-09-21 once the launchpad curves and ETH-quoted v4 pools were decoded) cannot be valued at the pool's state and stays out of the figure; a token launch's first curve buy is its own reject (launch_first_trade), not a fill; each entry's unpriced_share and its rejects in the JSON say how much and why."
- "Cohort discovery: the fee wallets and routers are learned from the chain, the lists being the seed. On a platform's transactions (Mobula's attribution as the sample, every 6 h), a recipient taking a fixed share of the trade across many payers is its fee wallet, adopted from 30 swaps of 20 payers with a stable 20 to 300 bps share; on EVM the transactions' destination is its router. The window's swaps also give the fee-like recipients no terminal lists yet (JSON, 'discovery')."
- "Phantom (Solana): its in-wallet swap pays 0.85 % to its fee wallet in SOL or WSOL, sampled like the other Solana terminals. Binance Wallet (BSC, Ethereum, Base): one router on the three chains, no event of its own, so its swaps are the transactions sent to it in blocks read in full; valued like the other native EVM rows. Terminal's EVM fee wallets get under $10 a day: not measured. Banana Gun on Ethereum: buys only, no fee, its users sell on Uniswap directly; held out (one side)."
- "four.meme's bonding curve on BNB is priced exactly: the curve is a constant product on virtual reserves (checked on consecutive trades to 1e-12), its constants come from the TokenManager's token info, so the mid before the trade is known; the launchpad's 1 % fee is 'other', like pump.fun's on Solana."
Expand Down
17 changes: 17 additions & 0 deletions harnesses/terminal-fill-quality/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,23 @@ depends on pool activity. The victim's extra cost is already inside
**Trade-size buckets**: `by_size` per terminal (under $25, $25 to $250,
over $250; median loss and n from 5 samples).

**Launchpad curves (`rhcurve.go`)**: BasedBot's router family also trades
bonding curves, one contract per token with the same bytecode, no Uniswap
event: `buy 0xec36bf57…` [gas coin in, tokens out, fee 1, fee 2], `sell
0x8113d738…` [tokens in, gas coin out, fee 1, fee 2], fees (1 % + 2 %) on the
gross amount. The quote is the gas coin itself unless an ERC20 moved the
amount to or from the curve (a stable, WETH, or a token the route bought on
the way, priced through that swap). Reference = the previous trade on the
curve at the reserve-side price, walked in 9,000-block chunks (the keyed
node caps `eth_getLogs` at 10,000; Robinhood Chain makes ~590 blocks a
minute) up to two hours back. A token launch's first buy (minted to the
curve in the same transaction) is rejected as `launch_first_trade`. On
2026-09-21, 15 of 40 router swaps on Robinhood Chain were curve trades and
7 more were sells against ETH-quoted v4 pools (no ERC20 transfer for the
quote leg: priced as the gas coin when no ERC20 of that order moved
through the manager, `nativeV4Quote`): 8 of 40 priced before, 33 of 35
fills after.

**Cross-chain (`xchain.go`)**: FOMO's users pay on BNB, Robinhood Chain,
Base, Ethereum or Arc and Relay delivers on Solana (BasedBot's requests are
its in-app bridge between the user's Solana and EVM wallets, both
Expand Down
37 changes: 37 additions & 0 deletions harnesses/terminal-fill-quality/cmd/script/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,29 @@ const (
arcPseudo = "0xfffffffffffffffffffffffffffffffffffffffe"
)

// nativeV4Quote: a v4 pool holds the gas coin itself, so a swap against
// ETH logs no ERC20 transfer for the quote leg. When no ERC20 flow of the
// manager matches the quote (to 0.5 %), the quote is the gas coin at the
// exchange's price, USD per wei; any ERC20 flow of half the quote or more
// through the manager means a token leg instead (left unpriced). Sells of
// BasedBot's users on Robinhood Chain against ETH-quoted v4 pools read
// no_quote_leg before this.
func nativeV4Quote(c originChain, gas map[string]float64, ev *swapEv, quoteRaw *big.Int, flows map[string]*big.Int) (float64, bool) {
if ev.kind != "v4" || c.gas == "" || quoteRaw == nil || quoteRaw.Sign() <= 0 {
return 0, false
}
p, ok := gas[c.gas]
if !ok || p <= 0 {
return 0, false
}
for _, amt := range flows {
if amt != nil && amt.Cmp(new(big.Int).Div(quoteRaw, big.NewInt(2))) >= 0 {
return 0, false // an ERC20 of that order moved through the manager: the quote is a token leg the passes above could not price, not the gas coin
}
}
return p * 1e-18, true
}

// quoteUSD prices a quote token: stables at $1, wrapped gas coins at the
// Coinbase spot of the chain's gas token.
func quoteUSD(sym string, c originChain, gas map[string]float64) (float64, bool) {
Expand Down Expand Up @@ -487,6 +510,10 @@ func priceEvmSettlement(ctx context.Context, httpc *http.Client, c originChain,
return out, nil
}
}
for _, tr := range rhTrades(rc.Logs, true, tokenFrom) {
priceRHCurve(ctx, httpc, c, &out, tr, token, meta.dec, gas, rc.Logs)
return out, nil
}
out.Unpriced = "no_pool"
return out, nil
}
Expand Down Expand Up @@ -545,6 +572,9 @@ func priceEvmSettlement(ctx context.Context, httpc *http.Client, c originChain,
}
}
}
if u, ok := nativeV4Quote(c, gas, ev, quoteRaw, intoPool[ev.pool]); ok {
return u, 0, true
}
return 0, 0, false
}
upr, hops, ok := usdPerRaw(main.ev, main.quote, 0)
Expand Down Expand Up @@ -832,6 +862,10 @@ func priceEvmOriginSale(ctx context.Context, httpc *http.Client, c originChain,
return out, nil
}
}
for _, tr := range rhTrades(rc.Logs, false, tokenTo) {
priceRHCurve(ctx, httpc, c, &out, tr, token, meta.dec, gas, rc.Logs)
return out, nil
}
out.Unpriced = "no_pool"
return out, nil
}
Expand Down Expand Up @@ -890,6 +924,9 @@ func priceEvmOriginSale(ctx context.Context, httpc *http.Client, c originChain,
}
}
}
if u, ok := nativeV4Quote(c, gas, ev, quoteRaw, outOfPool[ev.pool]); ok {
return u, 0, true
}
return 0, 0, false
}
mainQuote, _ := delivered(main.ev, main.quote)
Expand Down
17 changes: 15 additions & 2 deletions harnesses/terminal-fill-quality/cmd/script/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ func sampleNative(ctx context.Context, httpc *http.Client, st *State, nf *native
st.reject(t.Slug, rejectNotSwap)
continue
}
if sw.Flag == "launch_first_trade" {
st.reject(t.Slug, parseReject(sw.Flag)) // a token launch's first curve buy: no reference, not a user's fill
continue
}
if sw.Flag != "" && !sw.Priced && strings.HasPrefix(sw.Flag, "unpriced_") {
st.reject(t.Slug, parseReject(sw.Flag))
continue
Expand Down Expand Up @@ -706,7 +710,7 @@ func nativeRow(ctx context.Context, httpc *http.Client, t evmTerminal, hash stri
sw.TerminalQ = fee
if !s.Priced {
sw.finalize(nil, 0, "")
sw.Flag = "unpriced_" + s.Unpriced
sw.Flag = unpricedFlag(s.Unpriced)
return sw
}
ref := s.MidUSD
Expand Down Expand Up @@ -757,7 +761,7 @@ func nativeRow(ctx context.Context, httpc *http.Client, t evmTerminal, hash stri
sw.TerminalQ = fee
if !s.Priced {
sw.finalize(nil, 0, "")
sw.Flag = "unpriced_" + s.Unpriced
sw.Flag = unpricedFlag(s.Unpriced)
return sw
}
ref := s.MidUSD
Expand All @@ -767,3 +771,12 @@ func nativeRow(ctx context.Context, httpc *http.Client, t evmTerminal, hash stri
}
return &Swap{Flag: "unpriced_no_token_leg"}
}

// unpricedFlag: a settlement's reason as the row's flag; a token launch
// (the creator's first curve buy) is its own reject, not an unpriced fill.
func unpricedFlag(reason string) string {
if reason == "launch" {
return "launch_first_trade"
}
return "unpriced_" + reason
}
Loading
Loading