diff --git a/benchmarks/terminal-fill-quality.yml b/benchmarks/terminal-fill-quality.yml index 7adf36039..687aeef42 100644 --- a/benchmarks/terminal-fill-quality.yml +++ b/benchmarks/terminal-fill-quality.yml @@ -68,7 +68,7 @@ methodology: - "Trade-size buckets (under $25, $25 to $250, over $250): median loss per bucket in the public JSON and on each product page, so terminals with different typical trade sizes are compared at equal size." - "One row per product; the chain selector shows the product on one chain (Solana and five EVM chains), All chains pools its swaps across chains (bridge funding legs stay out). Each chain is sampled at a fixed daily rate, so All chains weighs a chain's sampled swaps by its swap attempts per sample: the pooled figure follows the users, not the sampler. Published from 50 priced swaps pooled over the chains at 25 or more; it waits while the chain carrying most of the flow fills." - "Cross-chain (FOMO): the user pays on BNB, Robinhood Chain, Base, Ethereum or Arc and Relay delivers on Solana. Relay's public requests feed identifies the app's requests (fee address or referrer) with what the user sent in USD, the fees, both hashes and the status. Value received = what the settlement delivered on Solana: USDC / SOL exactly (FOMO funds the wallet this way; the token buy that follows is a native swap in the FOMO row) or a token at the pool's state before the swap." - - "Cross-chain split: terminal = the app fee the user paid; network = the origin gas (receipt on the chain's public RPC); relay = Relay's fees and spread (deposit minus received minus app fee); pool only when a token was delivered. A token sold on the origin chain is priced at that chain's pool state in its per-chain row; a token deposit from Solana keeps Relay's valuation, shown, not counted. Refunds and failures count in the fail rate. Perps are not measured; swaps under $2 are dropped." + - "Cross-chain split: terminal = the app fee the user paid; network = origin gas plus the destination gas Relay charged (its fee breakdown); relay = Relay's fixed and price fees plus the solver's spread (deposit minus received minus app fee minus that gas); pool only when a token was delivered. A token sold on the origin chain is priced at that chain's pool state in its per-chain row. Refunds and failures count in the fail rate. Perps are not measured; swaps under $2 are dropped." - "Trading on another chain (FOMO on Robinhood Chain, BNB, Base, Ethereum, Arc): the user pays in USDC or SOL from the FOMO wallet, a Relay solver buys the token there. Value given = what was sent (tx fee inside); value received = the tokens delivered at the pool's state before the settlement swap: v2 forks from the Sync reserves minus the swap, v3 forks and Uniswap v4 from the price left by the previous swap on the pool, all from the pool's own logs on public RPCs." - "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." @@ -122,7 +122,7 @@ metric_panels: metric: tfq_component_bps{component="network",chain="all"} unit: bp higher_is_better: false - description: "Median transaction fee paid by the user plus inclusion tips (Jito and the terminal's own relay), share of the trade. Zero when the terminal sponsors gas." + description: "Median transaction fee paid by the user plus inclusion tips (Jito and the terminal's own relay), share of the trade; on cross-chain rows the destination gas Relay charged (its fee breakdown) is included. Zero when the terminal sponsors gas." - id: pool label: Pool label_key: terminal @@ -137,7 +137,7 @@ metric_panels: unit: bp higher_is_better: false tab: false - description: "Relay's fees and spread (deposit minus received minus app fee), share of the trade: the median on a chain row; on All chains, each chain's median weighted by its flow, blank when the bridged rows carry under half the product's flow (FOMO: most of its swaps are native on Solana)." + description: "Relay's fixed and price fees plus the solver's spread (deposit minus received minus app fee minus the destination gas, which is in Network), share of the trade: the chain row's median; on All chains flow-weighted, blank when the bridged rows carry under half the flow." - id: other label: Other fees label_key: terminal diff --git a/harnesses/terminal-fill-quality/cmd/script/main.go b/harnesses/terminal-fill-quality/cmd/script/main.go index a8d9bb3b1..5553a102e 100644 --- a/harnesses/terminal-fill-quality/cmd/script/main.go +++ b/harnesses/terminal-fill-quality/cmd/script/main.go @@ -808,8 +808,8 @@ func sampleXchain(ctx context.Context, rpc *rpcClient, httpc *http.Client, st *S q := sw.QuoteUSD sw.UserQ = (x.UsdIn + gasUSD) / q // what the user sent on the origin chain, plus its gas sw.TerminalQ = x.AppFeeUsd / q - sw.RelayQ = x.RelayFeeUsd / q - sw.NetworkQ = gasUSD / q + sw.RelayQ = (x.RelayFeeUsd + relayDeclaredFees(x)) / q + sw.NetworkQ = (gasUSD + x.DestGasUsd) / q sw.Others, sw.OtherQ = nil, nil if sw.PoolQ > 0 && sw.Pools == 1 { o := sw.UserQ - sw.PoolQ - sw.TerminalQ - sw.RelayQ - sw.NetworkQ @@ -1066,7 +1066,7 @@ var chainNames = map[string]string{"bnb": "BNB", "robinhood": "Robinhood Chain", func cohort() []Terminal { out := append([]Terminal{}, terminals...) for _, a := range xchainApps { - out = append(out, Terminal{Slug: a.Slug + "-funding", Name: a.Name + " · funding", Kind: "app", Note: "Funding legs through Relay, either way: the user pays on BNB, Robinhood Chain, Base, Ethereum or Arc and receives USDC or SOL on Solana (FOMO; the token buy that follows is a native swap in the app's Solana row), or moves funds between the Solana and EVM wallets of the same app account, either direction (BasedBot: an in-app bridge, not a deposit to trade). Value given = the origin deposit plus its gas; received = the amount delivered; terminal = the app fee the user paid; relay = what Relay kept (fees and spread); network = origin gas. Refunded and failed requests count in the fail rate. Out of the product's pooled figure: a bridge, not a fill."}) + out = append(out, Terminal{Slug: a.Slug + "-funding", Name: a.Name + " · funding", Kind: "app", Note: "Funding legs through Relay, either way: the user pays on BNB, Robinhood Chain, Base, Ethereum or Arc and receives USDC or SOL on Solana (FOMO; the token buy that follows is a native swap in the app's Solana row), or moves funds between the Solana and EVM wallets of the same app account, either direction (BasedBot: an in-app bridge, not a deposit to trade). Value given = the origin deposit plus its gas; received = the amount delivered; terminal = the app fee the user paid; network = origin gas plus the destination gas Relay charged (its fee breakdown); relay = the rest of what Relay kept (fixed and price fees, the solver's spread). Refunded and failed requests count in the fail rate. Out of the product's pooled figure: a bridge, not a fill."}) if a.FundingOnly { continue } @@ -1084,6 +1084,12 @@ func cohort() []Terminal { return out } +// relayDeclaredFees: Relay's fixed and price fees (USD) from its breakdown, +// for the rows whose relay figure is not a residual. +func relayDeclaredFees(x relayRequest) float64 { + return x.RelayFixedUsd +} + // evmSaleRow measures a request that sold a token on its origin chain and // settled USDC / SOL on Solana: the tokens sold at the origin pool's state // before the swap versus what reached the user on Solana. @@ -1117,9 +1123,9 @@ func evmSaleRow(ctx context.Context, rpc *rpcClient, httpc *http.Client, t Termi } sw.UserQ = recv.Tokens // quote received on Solana sw.TerminalQ = x.AppFeeUsd / q - sw.NetworkQ = s.GasUSD / q // origin gas, paid by the user - sw.PoolQ = s.PoolInUSD / q // quote the origin pool paid out - relay := s.PoolInUSD - s.OtherUSD - x.AppFeeUsd - recv.Tokens*q + sw.NetworkQ = (s.GasUSD + x.DestGasUsd) / q // origin gas paid by the user, plus the destination gas Relay charged + sw.PoolQ = s.PoolInUSD / q // quote the origin pool paid out + relay := s.PoolInUSD - s.OtherUSD - x.AppFeeUsd - recv.Tokens*q - x.DestGasUsd if relay < 0 { relay = 0 } @@ -1216,8 +1222,8 @@ func evmFundingRow(ctx context.Context, rpc *rpcClient, t Terminal, x relayReque } sw.UserQ = givenUSD / price sw.TerminalQ = x.AppFeeUsd / price - sw.NetworkQ = feeUSD / price - relay := givenUSD - feeUSD - recv*price - x.AppFeeUsd + sw.NetworkQ = (feeUSD + x.DestGasUsd) / price + relay := givenUSD - feeUSD - recv*price - x.AppFeeUsd - x.DestGasUsd if relay < 0 { relay = 0 } @@ -1268,9 +1274,9 @@ func evmRow(ctx context.Context, rpc *rpcClient, httpc *http.Client, t Terminal, } sw.UserQ = givenUSD / q sw.TerminalQ = x.AppFeeUsd / q - sw.NetworkQ = feeUSD / q + sw.NetworkQ = (feeUSD + x.DestGasUsd) / q // origin gas, plus the destination gas Relay charged sw.PoolQ = s.PoolInUSD / q - relay := givenUSD - feeUSD - x.AppFeeUsd - s.PoolInUSD - s.OtherUSD + relay := givenUSD - feeUSD - x.AppFeeUsd - s.PoolInUSD - s.OtherUSD - x.DestGasUsd if relay < 0 { relay = 0 } diff --git a/harnesses/terminal-fill-quality/cmd/script/xchain.go b/harnesses/terminal-fill-quality/cmd/script/xchain.go index 105fe0f84..56b3f16b8 100644 --- a/harnesses/terminal-fill-quality/cmd/script/xchain.go +++ b/harnesses/terminal-fill-quality/cmd/script/xchain.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "math" "math/rand" "net/http" "os" @@ -158,26 +159,28 @@ func (x relayRequest) rowSlug() string { // relayRequest is one settled (or failed) Relay request of a cohort app. type relayRequest struct { - ID string `json:"id"` - App string `json:"app"` - Chain string `json:"chain"` // origin chain slug ("solana" when leaving Solana) - DestChain string `json:"dest_chain"` // destination chain slug when not Solana - TokenOut string `json:"token_out"` // destination token address when not Solana - Funding bool `json:"funding,omitempty"` // the gas coin (or Arc's USDC) delivered to the user's wallet on the destination: a funding leg, not a trade - OutValueWei string `json:"out_value_wei,omitempty"` // native amount delivered on the destination (Funding) - Status string `json:"status"` - User string `json:"user"` // origin address - Recipient string `json:"recipient"` // destination wallet - InTx string `json:"in_tx"` - OutTx string `json:"out_tx"` // destination transaction (Solana signature or EVM hash) - UsdIn float64 `json:"usd_in"` - UsdOut float64 `json:"usd_out"` // Relay's own valuation of the output, cross-check only - AppFeeUsd float64 `json:"app_fee_usd"` - RelayFeeUsd float64 `json:"relay_fee_usd"` - OutIsToken bool `json:"out_is_token"` // Solana side delivers a token (not SOL / a stable) - InIsToken bool `json:"in_is_token"` // origin side was a token (not the gas coin / a stable): usd_in is Relay's valuation, not an on-chain mid - TokenIn string `json:"token_in"` // origin token address when InIsToken - Created int64 `json:"created"` + ID string `json:"id"` + App string `json:"app"` + Chain string `json:"chain"` // origin chain slug ("solana" when leaving Solana) + DestChain string `json:"dest_chain"` // destination chain slug when not Solana + TokenOut string `json:"token_out"` // destination token address when not Solana + Funding bool `json:"funding,omitempty"` // the gas coin (or Arc's USDC) delivered to the user's wallet on the destination: a funding leg, not a trade + OutValueWei string `json:"out_value_wei,omitempty"` // native amount delivered on the destination (Funding) + Status string `json:"status"` + User string `json:"user"` // origin address + Recipient string `json:"recipient"` // destination wallet + InTx string `json:"in_tx"` + OutTx string `json:"out_tx"` // destination transaction (Solana signature or EVM hash) + UsdIn float64 `json:"usd_in"` + UsdOut float64 `json:"usd_out"` // Relay's own valuation of the output, cross-check only + AppFeeUsd float64 `json:"app_fee_usd"` + RelayFeeUsd float64 `json:"relay_fee_usd"` + DestGasUsd float64 `json:"dest_gas_usd,omitempty"` + RelayFixedUsd float64 `json:"relay_fixed_usd,omitempty"` // Relay's fixed + price fees from its breakdown // destination gas Relay charged the user (actual execution when reported, else the quoted gas fee): network cost + OutIsToken bool `json:"out_is_token"` // Solana side delivers a token (not SOL / a stable) + InIsToken bool `json:"in_is_token"` // origin side was a token (not the gas coin / a stable): usd_in is Relay's valuation, not an on-chain mid + TokenIn string `json:"token_in"` // origin token address when InIsToken + Created int64 `json:"created"` } type relayResp struct { @@ -198,8 +201,16 @@ type relayRaw struct { Bps string `json:"bps"` AmountUsd string `json:"amountUsd"` } `json:"appFees"` - InTxs []relayTx `json:"inTxs"` - OutTxs []relayTx `json:"outTxs"` + InTxs []relayTx `json:"inTxs"` + OutTxs []relayTx `json:"outTxs"` + // Relay's own fee breakdown, USD: gas (destination execution), fixed (the relayer), price (the swap). + FeesUsd map[string]string `json:"feesUsd"` + // What actually happened, USD, signed from the user's side: execution (gas, negative), app, swap, relay. + ExpandedPriceImpact struct { + Actual map[string]struct { + Usd string `json:"usd"` + } `json:"actual"` + } `json:"expandedPriceImpact"` Metadata struct { CurrencyIn relayAmount `json:"currencyIn"` CurrencyOut relayAmount `json:"currencyOut"` @@ -699,14 +710,30 @@ func classify(a xchainApp, c originChain, r relayRaw) (relayRequest, bool) { } else { x.AppFeeUsd = appFee } - for _, k := range []string{"execution", "swap", "relay", "rent"} { + for _, k := range []string{"swap", "relay", "rent"} { if v, ok := comps[k]; ok { x.RelayFeeUsd += f64(v.UserPays.AmountUsd) } } + if v, ok := comps["execution"]; ok { + x.DestGasUsd = f64(v.UserPays.AmountUsd) + } } else { x.AppFeeUsd = appFee } + for _, k := range []string{"fixed", "price"} { + if v, ok := r.Data.FeesUsd[k]; ok { + x.RelayFixedUsd += math.Abs(f64(v)) + } + } + // Relay's breakdown: the destination gas is network cost, not the bridge's take. + if x.DestGasUsd == 0 { + if v, ok := r.Data.ExpandedPriceImpact.Actual["execution"]; ok { + x.DestGasUsd = math.Abs(f64(v.Usd)) + } else if g, ok := r.Data.FeesUsd["gas"]; ok { + x.DestGasUsd = math.Abs(f64(g)) + } + } return x, true } @@ -782,8 +809,8 @@ func bridgeRow(t Terminal, x relayRequest, tx *parsedTx, solUSD, gasUSD float64) } sw.UserQ = (x.UsdIn + gasUSD) / q sw.TerminalQ = x.AppFeeUsd / q - sw.NetworkQ = gasUSD / q - relay := x.UsdIn - recv*q - x.AppFeeUsd + sw.NetworkQ = (gasUSD + x.DestGasUsd) / q // origin gas plus the destination gas Relay charged + relay := x.UsdIn - recv*q - x.AppFeeUsd - x.DestGasUsd if relay < 0 { relay = 0 } diff --git a/src/components/terminal-fill-section.tsx b/src/components/terminal-fill-section.tsx index d5b37ac7a..bb68288ac 100644 --- a/src/components/terminal-fill-section.tsx +++ b/src/components/terminal-fill-section.tsx @@ -84,7 +84,7 @@ export async function TerminalFillSection({