11import { NextResponse } from "next/server"
22import { patchEdgeConfigItems } from "@/lib/vercel-edge-config"
33import { getAnalyticsClient } from "@/lib/analytics/client"
4+ import { FAST_SETTLEMENT_EXECUTOR_ADDRESS , WETH_ADDRESS } from "@/lib/swap-constants"
45
56// ---------------------------------------------------------------------------
67// Constants
@@ -381,22 +382,48 @@ async function computeBidCostEstimate(): Promise<number> {
381382/**
382383 * Per-token sweep overhead map: lowercased L1 token address → ETH overhead.
383384 *
384- * Mirrors the backend's `costEstimator.Get` semantics in
385+ * Mirrors the backend's combined `PerRowOverhead` in
385386 * `mev-commit/tools/fastswap-miles/cost_estimator.go`:
386- * - per-token p25 over the last 14 days when sample size ≥ 10
387- * - per-token p75 when sample size < 10 (low-data fallback)
388- * - `default` is a network-wide p25 across all tokens — used when the
389- * frontend's selected output token isn't in the map at all
390- * - falls back to `LAST_RESORT_SWEEP_OVERHEAD_ETH` if the network query
391- * itself returns nothing usable
387+ * 1. Per-token p25/p75 of pro-rata sweep gas (`get-sweep-overhead-by-token`).
388+ * 2. Plus per-token sweep bid contribution (`get-sweep-bid-by-token`):
389+ * `(n_sweeps × global_bid_p75) / n_user_rows`. The sweep tx is itself a
390+ * fastswap so a global percentile of recent bid_cost is the right proxy.
391+ *
392+ * Both terms scale with batch size — low-volume tokens have a small number
393+ * of user rows per sweep so the per-row contribution is high, and high-
394+ * volume tokens dilute both.
395+ *
396+ * The `default` key falls back to `LAST_RESORT_SWEEP_OVERHEAD_ETH` so the
397+ * frontend's selected output token has a value even when not in the map.
392398 *
393399 * Returned values are ETH (float) so the route handler can ship the map
394400 * straight to Edge Config without extra encoding.
395401 */
396402async function computeSweepOverheadByToken ( ) : Promise < Record < string , number > > {
397403 const client = getAnalyticsClient ( )
398404
399- const rows = await client . execute ( "fastswap/get-sweep-overhead-by-token" , { } )
405+ const [ gasRows , bidRows ] = await Promise . all ( [
406+ client . execute ( "fastswap/get-sweep-overhead-by-token" , { } ) ,
407+ client . execute ( "fastswap/get-sweep-bid-by-token" , {
408+ executor : FAST_SETTLEMENT_EXECUTOR_ADDRESS . toLowerCase ( ) ,
409+ weth : WETH_ADDRESS . toLowerCase ( ) ,
410+ fallback_bid_eth : FALLBACK_BID_COST_ETH ,
411+ } ) ,
412+ ] )
413+
414+ // Per-row sweep bid contribution, keyed by lowercased output_token.
415+ const bidByToken = new Map < string , number > ( )
416+ for ( const row of bidRows ) {
417+ const token = String ( row [ 0 ] ?? "" ) . toLowerCase ( )
418+ const nSweeps = Number ( row [ 1 ] )
419+ const nUsers = Number ( row [ 2 ] )
420+ const bidP75 = Number ( row [ 3 ] )
421+ if ( ! token || token === "null" ) continue
422+ if ( ! Number . isFinite ( nSweeps ) || nSweeps <= 0 ) continue
423+ if ( ! Number . isFinite ( nUsers ) || nUsers <= 0 ) continue
424+ if ( ! Number . isFinite ( bidP75 ) || bidP75 <= 0 ) continue
425+ bidByToken . set ( token , ( nSweeps * bidP75 ) / nUsers )
426+ }
400427
401428 // `default` mirrors the backend's `costEstimateLastResort` exactly — for
402429 // tokens the backend has no historical data on, both estimators must
@@ -405,7 +432,7 @@ async function computeSweepOverheadByToken(): Promise<Record<string, number>> {
405432 // user gets fewer miles than the badge said.
406433 const map : Record < string , number > = { default : LAST_RESORT_SWEEP_OVERHEAD_ETH }
407434
408- for ( const row of rows ) {
435+ for ( const row of gasRows ) {
409436 const token = String ( row [ 0 ] ?? "" ) . toLowerCase ( )
410437 const n = Number ( row [ 1 ] )
411438 const p25 = Number ( row [ 2 ] )
@@ -415,15 +442,18 @@ async function computeSweepOverheadByToken(): Promise<Record<string, number>> {
415442 if ( ! Number . isFinite ( n ) || n <= 0 ) continue
416443 if ( ! Number . isFinite ( p25 ) || ! Number . isFinite ( p75 ) ) continue
417444
418- const overhead = n >= SWEEP_OVERHEAD_MIN_SAMPLES ? p25 : p75
419- if ( ! Number . isFinite ( overhead ) || overhead < 0 ) continue
445+ const gasOverhead = n >= SWEEP_OVERHEAD_MIN_SAMPLES ? p25 : p75
446+ if ( ! Number . isFinite ( gasOverhead ) || gasOverhead < 0 ) continue
447+
448+ const sweepBid = bidByToken . get ( token ) ?? 0
449+ const combined = gasOverhead + sweepBid
420450
421451 // Round to 8 decimal places (0.01 µETH) to keep Edge Config JSON tight.
422- map [ token ] = Math . round ( overhead * 1e8 ) / 1e8
452+ map [ token ] = Math . round ( combined * 1e8 ) / 1e8
423453 }
424454
425455 console . log (
426- `[cron/miles-estimate-gas] sweepOverhead — ${ Object . keys ( map ) . length - 1 } tokens, default=${ ( map . default * 1e6 ) . toFixed ( 0 ) } µETH (last-resort), sample=${ JSON . stringify (
456+ `[cron/miles-estimate-gas] sweepOverhead — ${ Object . keys ( map ) . length - 1 } tokens, ${ bidByToken . size } with sweep bid, default=${ ( map . default * 1e6 ) . toFixed ( 0 ) } µETH (last-resort), sample=${ JSON . stringify (
427457 Object . fromEntries (
428458 Object . entries ( map )
429459 . filter ( ( [ k ] ) => k !== "default" )
0 commit comments