From d74e5f4a1007065b5aa5b6772b5fbeb600e49527 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:37:58 +0200 Subject: [PATCH 1/3] fix: bench 203 pump.fun attribution via poolAddress/bondingCurveAddress instead of empty platform tag --- .../memecoin-platforms/cmd/monitor/main.go | 6 +++- .../memecoin-platforms/cmd/monitor/mobula.go | 36 ++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/harnesses/memecoin-platforms/cmd/monitor/main.go b/harnesses/memecoin-platforms/cmd/monitor/main.go index c7f3fbb0..7a99a012 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/main.go +++ b/harnesses/memecoin-platforms/cmd/monitor/main.go @@ -126,7 +126,11 @@ func runPoll(mobulaClient, rpcClient, heliusClient *http.Client, heliusKey, apiK } p := t.Platform if p == "" { - p = "unattributed" + if t.isPumpFunNative() { + p = "pump-fun" + } else { + p = "unattributed" + } } s := byPlatform[p] if s == nil { diff --git a/harnesses/memecoin-platforms/cmd/monitor/mobula.go b/harnesses/memecoin-platforms/cmd/monitor/mobula.go index 9fced12f..c46a72ae 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/mobula.go +++ b/harnesses/memecoin-platforms/cmd/monitor/mobula.go @@ -9,10 +9,38 @@ import ( ) type MobulaTrade struct { - Platform string `json:"platform"` - Hash string `json:"hash"` - Sender string `json:"sender"` - AmountUSD float64 `json:"amountUSD"` + Platform string `json:"platform"` + Hash string `json:"hash"` + Sender string `json:"sender"` + AmountUSD float64 `json:"amountUSD"` + PoolAddress string `json:"poolAddress"` + PairData struct { + Base struct { + BondingCurveAddress string `json:"bondingCurveAddress"` + Bonded bool `json:"bonded"` + SourceFactoryMetadata struct { + Name string `json:"name"` + } `json:"sourceFactoryMetadata"` + } `json:"base"` + } `json:"pairData"` +} + +// isPumpFunNative returns true when the trade happened on pump.fun's bonding +// curve (pre-migration). Identified by matching poolAddress to bondingCurveAddress, +// or by sourceFactoryMetadata.name containing "pump" when bonded is false. +func (t *MobulaTrade) isPumpFunNative() bool { + if t.PoolAddress != "" && t.PairData.Base.BondingCurveAddress != "" { + if t.PoolAddress == t.PairData.Base.BondingCurveAddress { + return true + } + } + if !t.PairData.Base.Bonded { + name := t.PairData.Base.SourceFactoryMetadata.Name + if name == "pump.fun" || name == "pumpfun" || name == "PumpFun" { + return true + } + } + return false } type mobulaTradesResp struct { From 1e72cb5a570bd153b55eb41a99ff7e101cd9a5ad Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:39:57 +0200 Subject: [PATCH 2/3] fix: bench 203 pump.fun attribution via poolType (pumpfun/pumpswap) --- .../memecoin-platforms/cmd/monitor/mobula.go | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/harnesses/memecoin-platforms/cmd/monitor/mobula.go b/harnesses/memecoin-platforms/cmd/monitor/mobula.go index c46a72ae..2a93487b 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/mobula.go +++ b/harnesses/memecoin-platforms/cmd/monitor/mobula.go @@ -9,38 +9,17 @@ import ( ) type MobulaTrade struct { - Platform string `json:"platform"` - Hash string `json:"hash"` - Sender string `json:"sender"` - AmountUSD float64 `json:"amountUSD"` - PoolAddress string `json:"poolAddress"` - PairData struct { - Base struct { - BondingCurveAddress string `json:"bondingCurveAddress"` - Bonded bool `json:"bonded"` - SourceFactoryMetadata struct { - Name string `json:"name"` - } `json:"sourceFactoryMetadata"` - } `json:"base"` - } `json:"pairData"` + Platform string `json:"platform"` + Hash string `json:"hash"` + Sender string `json:"sender"` + AmountUSD float64 `json:"amountUSD"` + PoolType string `json:"poolType"` } -// isPumpFunNative returns true when the trade happened on pump.fun's bonding -// curve (pre-migration). Identified by matching poolAddress to bondingCurveAddress, -// or by sourceFactoryMetadata.name containing "pump" when bonded is false. +// isPumpFunNative returns true for trades on pump.fun's bonding curve or +// PumpSwap AMM (post-migration). Uses poolType field from token/trades-enriched. func (t *MobulaTrade) isPumpFunNative() bool { - if t.PoolAddress != "" && t.PairData.Base.BondingCurveAddress != "" { - if t.PoolAddress == t.PairData.Base.BondingCurveAddress { - return true - } - } - if !t.PairData.Base.Bonded { - name := t.PairData.Base.SourceFactoryMetadata.Name - if name == "pump.fun" || name == "pumpfun" || name == "PumpFun" { - return true - } - } - return false + return t.PoolType == "pumpfun" || t.PoolType == "pumpswap" } type mobulaTradesResp struct { From 232cc661ab2460e0232810871822c7d277dfa64c Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:41:52 +0200 Subject: [PATCH 3/3] fix: bench 203 YAML methodology + pump.fun FAQ updated for poolType attribution --- benchmarks/memecoin-platforms.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/memecoin-platforms.yml b/benchmarks/memecoin-platforms.yml index 37021177..4b4284c4 100644 --- a/benchmarks/memecoin-platforms.yml +++ b/benchmarks/memecoin-platforms.yml @@ -46,7 +46,7 @@ methodology: - "Gas: meta.fee (lamports) converted to USD at the current SOL/USD price fetched from CoinGecko every 5 min." - "Platform fees: explicit SOL or USDC/WSOL transfers to known fee wallet owners (confirmed on-chain). Unknown small recipients are captured via heuristics: SOL recipients receiving less than 5% of the user net flow, USDC/WSOL recipients receiving less than 5% of the largest pool receipt. Trading bots that use pre-funded escrow accounts (Trojan, Maestro) collect fees through a separate settlement mechanism not visible in the swap transaction; their displayed fee represents gas only." - "AMM LP fees excluded: the 0.20% PumpSwap fee and 0.25% Raydium fee are settled inside pool accounts as LP fees — they are not transferred to any platform wallet and are outside this benchmark's scope." - - "Grouping: trades are split by the platform field returned by Mobula. Trades with no platform tag are currently assigned to pump-fun — known limitation: untagged traffic (MEV bots, snipers, direct scripts) inflates the pump-fun sample with zero-platform-fee trades, pulling its median toward gas-only. A dedicated Unattributed bucket is on the roadmap." + - "Grouping: platform tag from Mobula's platform field (frontend referral attribution). Trades with no platform tag use poolType to detect pump.fun natively: poolType=pumpfun (bonding curve) or poolType=pumpswap (graduated AMM) → attributed to pump-fun. All other untagged trades go to an internal unattributed bucket and are excluded from provider rows." - "Cadence: full sweep every 5 minutes; tx results are cached to avoid re-parsing the same hash." - "Aggregation: headline is the 24h median of the trade-count-weighted average of memecoin_platform_fee_pct across all tracked tokens." @@ -63,8 +63,8 @@ faq: a: "For confirmed platforms like Fomo, the fee wallet owner address is verified by inspecting multiple tagged trades on-chain. For platforms without a confirmed wallet, the harness uses a heuristic: any non-system, non-pool account receiving less than 5% of the user's net SOL flow, or less than 5% of the largest token pool receipt, is counted as a fee recipient. This captures Phantom-style referral tips without hardcoding every address." - q: "Why does Trojan show near-zero fee?" a: "Trojan and similar escrow-based bots (Maestro) pre-fund a shared wallet, execute trades from it, then settle with users in a separate transaction. The swap transaction itself shows a fair-price trade with no fee deduction. Their advertised ~0.9% fee is collected in the settlement step, which is not a swap and not captured by this benchmark. A near-zero value here does not mean Trojan is free — it means their fee is outside the on-chain scope this benchmark measures. Trojan's displayed value reflects gas only." - - q: "Why does pump.fun show near-zero?" - a: "Two factors combine. First, trades with no Mobula platform tag are assigned to the pump-fun row — this includes MEV bots, snipers, and untagged scripts that pay no platform fee, pulling the pump-fun median toward gas-only. Second, pump.fun's bonding-curve fee is embedded in the output token amount rather than a discrete SOL or USDC transfer to an identifiable wallet, so it is not captured by on-chain fee parsing. PumpSwap (the AMM for graduated tokens) does charge explicit protocol and creator fees as on-chain transfers — these are captured when they appear in tagged trades. The unattributed-to-pump-fun assignment rule is a known limitation; a dedicated Unattributed bucket is on the roadmap." + - q: "Why does pump.fun show a low fee?" + a: "pump.fun trades are identified via the poolType field in Mobula's trades-enriched API: poolType=pumpfun for bonding-curve swaps and poolType=pumpswap for post-migration AMM swaps. PumpSwap charges explicit protocol and creator fees as on-chain SOL or USDC transfers — these are captured. The bonding-curve fee (pre-migration) is embedded in the output token amount, not a discrete transfer to an identifiable wallet, so it is not captured. Since August 7 2026, pump.fun cut bonding-curve trading fees to zero, making the bonding-curve row gas-only by design. The low displayed value reflects PumpSwap protocol fees on graduated tokens." prometheus: window: 24h