diff --git a/benchmarks/usdy-nav-basis.yml b/benchmarks/usdy-nav-basis.yml new file mode 100644 index 00000000..9b756be2 --- /dev/null +++ b/benchmarks/usdy-nav-basis.yml @@ -0,0 +1,90 @@ +# OpenChainBench. Bench № 078 + +slug: usdy-nav-basis +number: "078" +title: USDY NAV basis, live market price vs official redemption rate +seo_title: "USDY price vs NAV 2026" +seo_description: "Does USDY trade at its NAV? Live basis between Ondo's tokenized treasury market price (Orca, Pyth) and its official redemption rate, in bps, keyless." +subtitle: "Signed basis between USDY's onchain market price and the official Ondo redemption rate published on Pyth, in basis points. Tokenized treasuries are the largest RWA segment; this is the live answer to whether the market actually prices the NAV." + +category: RWA +status: live +metric: NAV basis +unit: bps +higher_is_better: false + +seo_intro: | + USDY is Ondo's yield-bearing tokenized treasury note, one of the + largest RWA tokens by float. Its official value is the redemption + rate Ondo publishes (the NAV a redeeming holder receives), but its + price on the open market is whatever the pools say. This page + measures the gap live: the Orca USDY/USDC whirlpool on Solana (the + deepest genuine venue, about $2.9M) and the Pyth market composite, + each against the Pyth redemption rate feed, every 60 seconds, + keyless on every leg. A persistent negative basis means the market + discounts the NAV (liquidity preference, exit friction); a positive + one means buyers pay a premium over redemption value. Most tokenized + treasuries (OUSG, BUIDL, BENJI) are transfer-restricted and never + trade on open pools, so their NAV can never be market-tested; USDY + is the rare one where the question is measurable at all. + +abstract: | + Every 60 seconds the harness reads the Ondo redemption rate and the + Pyth USDY/USD market composite from one Hermes call, and the Orca + USDY/USDC whirlpool price from one Solana getAccountInfo (sqrtPrice + decoded from the account bytes). Basis = (market - NAV) / NAV in + signed basis points per venue. No keys, no transactions. + +methodology: + - "NAV leg: the Pyth Hermes feed Crypto.USDY/USD.RR (the redemption rate Ondo publishes onchain), fetched keyless. The same Hermes call carries the Pyth USDY/USD market composite, which doubles as a venue row." + - "Market leg, Orca: the USDY/USDC whirlpool on Solana (~$2.9M, the deepest genuine USDY pool anywhere), price decoded straight from getAccountInfo bytes (sqrtPrice u128 at offset 65, both mints 6 decimals). Keyless against a public RPC." + - "Basis: signed, (market - NAV) / NAV x 10000. Negative = the market discounts the NAV. The ranking sorts by absolute basis; the sign is the story and both are displayed." + - "Excluded, verified 2026-07-13: the Arbitrum Camelot USDY/USDC pool holds 232 USDY against 7M USDC, effectively drained, its stale price sitting ~345bps under NAV with near zero volume. Kept out of the ranking as the textbook example of why pool depth gates peg quality; revisited monthly." + - "Also not measurable, disclosed: OUSG, BUIDL and BENJI are transfer-restricted mint/redeem instruments with no genuine open pools, so no market test of their NAV exists to publish." + - "USDY is yield accruing: the redemption rate rises daily, so a naive USD peg comparison would show permanent drift. Comparing against the live RR feed removes that by construction." + +findings: + - "{{best_name}} trades closest to NAV at {{best_p50}} (p50 absolute basis, 24h) across {{count}} measured venues." + - "{{name:orca-solana}} ({{p50:orca-solana}}) is the deepest genuine USDY venue anywhere at about $2.9M; its basis is the closest thing to a market verdict on Ondo's published NAV." + - "{{name:pyth-market}} ({{p50:pyth-market}}) aggregates offchain and onchain USDY trading into one composite; its spread against the RR feed is the cleanest single number for the NAV discount." + - "The excluded Camelot pool on Arbitrum is the finding that did not make the table: drained to 232 USDY, price frozen ~345bps under NAV. Depth is not a detail in RWA pricing, it is the whole game." + +faq: + - q: "Does USDY trade at its NAV?" + a: "Close to it, with a measurable basis that this page tracks live. At verification the Orca pool sat within a few bps of the redemption rate and the Pyth market composite about 10bps under. A persistent discount reflects exit friction and liquidity preference, not a broken product; the point is that it is now measured instead of assumed." + - q: "Why only two venues?" + a: "Because genuine USDY liquidity is rarer than the token's float suggests. The Arbitrum Camelot pool is drained and excluded (232 USDY left, price frozen well under NAV), and most other tokenized treasuries never trade openly at all. Two honest venues beat five misleading ones." + - q: "What does a negative basis mean?" + a: "The market prices USDY under its redemption value. Redeeming through Ondo takes time and has minimums, so sellers who want out now accept a small discount. The size and persistence of that discount is exactly what this bench publishes." + - q: "Why is USDY measurable when OUSG and BUIDL are not?" + a: "OUSG, BUIDL and BENJI are transfer-restricted: they move between allowlisted addresses and cannot trade on open AMMs, so no market price exists to compare against NAV. USDY circulates freely and has genuine pools, which makes it the one tokenized treasury where the NAV question can be answered by measurement." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/usdy-nav-basis + +prometheus: + window: 24h + freshness_metric: usdy_basis_bps + +providers: + - slug: orca-solana + name: Orca (Solana) + tag: "USDY/USDC whirlpool, ~$2.9M, deepest genuine USDY venue" + formula: "p50 over 24h of the absolute signed basis (bps) between the Orca whirlpool USDY/USDC price and the Pyth redemption rate." + queries: + p50: quantile_over_time(0.50, abs(usdy_basis_bps{venue="orca-solana"})[24h:]) + p90: quantile_over_time(0.90, abs(usdy_basis_bps{venue="orca-solana"})[24h:]) + p99: quantile_over_time(0.99, abs(usdy_basis_bps{venue="orca-solana"})[24h:]) + mean: avg_over_time(abs(usdy_basis_bps{venue="orca-solana"})[24h:]) + success: avg_over_time(usdy_health{venue="orca-solana"}[24h]) + series: usdy_basis_bps{venue="orca-solana"} + - slug: pyth-market + name: Pyth market composite + tag: "Crypto.USDY/USD aggregate vs the RR feed, same oracle network" + formula: "p50 over 24h of the absolute signed basis (bps) between the Pyth USDY/USD market composite and the Pyth redemption rate." + queries: + p50: quantile_over_time(0.50, abs(usdy_basis_bps{venue="pyth-market"})[24h:]) + p90: quantile_over_time(0.90, abs(usdy_basis_bps{venue="pyth-market"})[24h:]) + p99: quantile_over_time(0.99, abs(usdy_basis_bps{venue="pyth-market"})[24h:]) + mean: avg_over_time(abs(usdy_basis_bps{venue="pyth-market"})[24h:]) + success: avg_over_time(usdy_health{venue="pyth-market"}[24h]) + series: usdy_basis_bps{venue="pyth-market"} diff --git a/benchmarks/xstocks-peg.yml b/benchmarks/xstocks-peg.yml new file mode 100644 index 00000000..9f7d8d8e --- /dev/null +++ b/benchmarks/xstocks-peg.yml @@ -0,0 +1,207 @@ +# OpenChainBench. Bench № 077 + +slug: xstocks-peg +number: "077" +title: xStocks price accuracy, live onchain vs Nasdaq across 12 equities on Solana +seo_title: "xStocks price tracker 2026" +seo_description: "Do Backed's xStocks track the real market? AAPLx, TSLAx, NVDAx and 9 more on Solana vs their Nasdaq price, deviation in bps, live and keyless." +subtitle: "Absolute deviation between each xStock's executable Jupiter price on Solana and its real market reference, in basis points, labeled by market session. Companion to the Robinhood Chain tokenized stock bench: same methodology, different issuer, so the two pages read as a head to head." + +category: RWA +status: live +metric: Price deviation +unit: bps +higher_is_better: false + +seo_intro: | + Backed's xStocks are the other big tokenized equity program: 20+ US + stocks and ETFs issued on Solana and distributed through Kraken and + Bybit, trading around the clock on Raydium and Orca with pool depths + that reach several million dollars. This page measures how closely + each xStock's executable price tracks its real market reference, + every 60 seconds, keyless on both legs. The price measured is the + Jupiter aggregated quote for actually swapping one share in each + direction, so it is an executable mid, not a theoretical pool spot. + Sessions are labeled pre, regular, post and closed, and the closed + series is the weekend drift panel. The same methodology runs against + Robinhood Chain's tokenized equities on the companion bench, which + turns the two pages into the first public issuer head to head for + tokenized stocks. Fun fact this page makes measurable: HOODx, the + tokenized Robinhood stock, trades here on Solana, while Robinhood's + own chain does not tokenize HOOD at all. + +abstract: | + Every 60 seconds the harness quotes one share of each of 12 xStocks + through Jupiter's keyless lite API in both directions (sell to USDC, + buy back), takes the executable mid, corrects for the Token-2022 + scaled amount multiplier where present, and compares against the + Yahoo reference price with holiday-aware session labels. Deviation is + 10000 x |mid - reference| / reference in basis points. Same metric + contract as the Robinhood Chain bench with issuer="xstocks". + +methodology: + - "Onchain leg: Jupiter lite-api swap quotes, keyless, both directions per symbol (sell 1 share to USDC, buy the proceeds back), spaced 1.1s apart, ~26s per sweep for 12 symbols. Price = mid of the two implied prices: an executable aggregated price across Raydium, Orca and every routed venue, not a single pool spot." + - "Token-2022 correction: 7 of 12 xStocks mints carry a ScaledUiAmount multiplier (~1.0009 observed). One batched price/v3 call per tick supplies the live multiplier per mint (usdPrice over usdPricePrescaled), converting raw quote units to exactly one UI share. Ignoring it would bake a ~9bp systematic error into affected symbols." + - "Reference leg: Yahoo Finance spark batch, one keyless call for all 12 underliers per tick, 1 minute candles; session labels (pre, regular, post, closed) derived from Yahoo currentTradingPeriod, holiday aware. HOODx maps to HOOD, SPYx to SPY, QQQx to QQQ, COINx to COIN." + - "Deviation: 10000 x |executable mid - reference| / reference, quantiles over 24h via quantile_over_time. Headline pins market_state=\"regular\"; the closed series is the weekend drift panel." + - "Cohort: the 12 xStocks with verified Jupiter routes at under 2bp of 1-share price impact (2026-07-13): TSLAx, NVDAx, AAPLx, MSFTx, AMZNx, GOOGLx, METAx, HOODx, SPYx, QQQx, COINx, PLTRx. All mints 8 decimals, verified individually; counterfeit lookalike mints excluded by address allowlist." + - "Round-trip honesty: the sell/buy spread observed at verification was ~11bp on TSLAx. Using the mid rather than one side keeps the fee component out of the deviation number; the spread itself is executable cost, not tracking error." + - "Quote asset caveat: prices are in USDC against USD references. A USDC peg wobble would appear as a correlated deviation across all 12 symbols simultaneously." + - "Cross-issuer reading: this bench shares its metric contract with the Robinhood Chain tokenized stock bench (issuer label). Same equity, same reference, two issuers: the pages are directly comparable, with the depth difference disclosed (Solana xStocks pools run 10x to 100x deeper)." + +findings: + - "{{best_name}} tracks its reference tightest at {{best_p50}} (p50, 24h) across {{count}} measured xStocks." + - "{{name:tsla}} deviates {{p50:tsla}} (p50, 24h) against a Raydium pool holding about $2.1M, roughly 50x the depth of the equivalent Robinhood Chain pool, and the tighter tracking that buys is exactly what the cross-issuer comparison exists to show." + - "{{name:hood}} ({{p50:hood}}) is the page's inside joke made measurable: the tokenized Robinhood stock trades on Solana via a third-party issuer while Robinhood's own chain does not tokenize HOOD." + - "{{name:spy}} ({{p50:spy}}) and {{name:qqq}} ({{p50:qqq}}) give the bench its index rows, useful because index arbitrage against deep ETF markets should in theory be the tightest peg of all." + - "The number to watch on weekends: pools trade around the clock while the reference freezes at Friday close, and the closed-session drift measured here is the honest answer to what a 24/7 stock is worth on a Sunday." + +faq: + - q: "Do xStocks track their real stock price?" + a: "During regular hours the liquid names track within tens of basis points, and this page shows the live per-symbol number measured as an executable Jupiter mid, not a theoretical pool spot. Outside market hours the peg loosens by construction, and the closed-session series quantifies exactly how much." + - q: "How is this different from the Robinhood Chain tokenized stock bench?" + a: "Same methodology, different issuer and chain: Backed's xStocks on Solana measured through Jupiter, Robinhood's tokens on their own chain measured through Uniswap v4. The two benches share a metric contract, so the same equity can be compared across issuers directly, depth differences disclosed." + - q: "Why measure the Jupiter quote instead of a specific pool?" + a: "Because it is what a user actually gets: the aggregated executable route across every venue with the fee baked into the round trip. Taking the mid of both directions removes the fee component and leaves the tracking signal. A single pool spot can sit anywhere inside its fee band without being arbitrageable." + - q: "What is the ScaledUiAmount correction?" + a: "Seven xStocks mints use Token-2022 scaled amounts, where raw token units map to UI shares through a live multiplier (about 1.0009 at verification). The harness reads the multiplier every tick from Jupiter's price API and applies it, without which those symbols would carry a permanent ~9bp phantom deviation." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/xstocks-peg + +prometheus: + window: 24h + freshness_metric: tsp_deviation_bps + +providers: + - slug: tsla + name: TSLAx + tag: "Tesla xStock, deepest pool of the cohort (~$2.1M Raydium)" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for TSLAx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="tsla", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="tsla", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="tsla", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="tsla", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="tsla"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="tsla", market_state="regular"} + - slug: nvda + name: NVDAx + tag: "Nvidia xStock, ~$2.7M Raydium pool" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for NVDAx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="nvda", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="nvda", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="nvda", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="nvda", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="nvda"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="nvda", market_state="regular"} + - slug: aapl + name: AAPLx + tag: "Apple xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for AAPLx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="aapl", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="aapl", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="aapl", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="aapl", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="aapl"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="aapl", market_state="regular"} + - slug: msft + name: MSFTx + tag: "Microsoft xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for MSFTx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="msft", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="msft", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="msft", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="msft", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="msft"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="msft", market_state="regular"} + - slug: amzn + name: AMZNx + tag: "Amazon xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for AMZNx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="amzn", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="amzn", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="amzn", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="amzn", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="amzn"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="amzn", market_state="regular"} + - slug: googl + name: GOOGLx + tag: "Alphabet xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for GOOGLx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="googl", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="googl", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="googl", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="googl", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="googl"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="googl", market_state="regular"} + - slug: meta + name: METAx + tag: "Meta xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for METAx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="meta", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="meta", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="meta", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="meta", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="meta"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="meta", market_state="regular"} + - slug: hood + name: HOODx + tag: "Robinhood stock, tokenized by Backed, not by Robinhood" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for HOODx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="hood", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="hood", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="hood", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="hood", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="hood"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="hood", market_state="regular"} + - slug: spy + name: SPYx + tag: "S&P 500 ETF xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for SPYx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="spy", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="spy", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="spy", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="spy", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="spy"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="spy", market_state="regular"} + - slug: qqq + name: QQQx + tag: "Nasdaq 100 ETF xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for QQQx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="qqq", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="qqq", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="qqq", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="qqq", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="qqq"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="qqq", market_state="regular"} + - slug: coin + name: COINx + tag: "Coinbase xStock" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for COINx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="coin", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="coin", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="coin", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="coin", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="coin"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="coin", market_state="regular"} + - slug: pltr + name: PLTRx + tag: "Palantir xStock, thinnest route of the cohort (1.8bp impact)" + formula: "p50 over 24h of the absolute deviation (bps) between the executable Jupiter mid for PLTRx on Solana and the Yahoo reference, regular market hours only." + queries: + p50: quantile_over_time(0.50, tsp_deviation_bps{issuer="xstocks", asset="pltr", market_state="regular"}[24h]) + p90: quantile_over_time(0.90, tsp_deviation_bps{issuer="xstocks", asset="pltr", market_state="regular"}[24h]) + p99: quantile_over_time(0.99, tsp_deviation_bps{issuer="xstocks", asset="pltr", market_state="regular"}[24h]) + mean: avg_over_time(tsp_deviation_bps{issuer="xstocks", asset="pltr", market_state="regular"}[24h]) + success: avg_over_time(tsp_health{asset="pltr"}[24h]) + series: tsp_deviation_bps{issuer="xstocks", asset="pltr", market_state="regular"} diff --git a/harnesses/usdy-nav-basis/Dockerfile b/harnesses/usdy-nav-basis/Dockerfile new file mode 100644 index 00000000..63108cfc --- /dev/null +++ b/harnesses/usdy-nav-basis/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/monitor ./cmd/script + +FROM debian:bookworm-slim + +WORKDIR /app +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/monitor /app/monitor + +EXPOSE 2112 + +CMD ["/app/monitor"] diff --git a/harnesses/usdy-nav-basis/cmd/script/loghub.go b/harnesses/usdy-nav-basis/cmd/script/loghub.go new file mode 100644 index 00000000..9dd74488 --- /dev/null +++ b/harnesses/usdy-nav-basis/cmd/script/loghub.go @@ -0,0 +1,114 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "net/http" + "os" + "strconv" + "sync" + "time" +) + +// Auto-generated by the loghub inline pattern. Captures stdout/stderr into a +// bounded ring buffer and exposes GET /logs?tail=N protected by X-Logs-Token +// matching the LOGS_TOKEN env var. +// +// Keep in sync across miniapps (was previously the shared/loghub package; we +// inline because Railway's per-harness Docker build context can't reach a +// sibling shared module via go.mod replace). + +const logRingMax = 5000 + +type logRing struct { + mu sync.Mutex + lines []string + max int +} + +var globalLogRing = &logRing{max: logRingMax} + +func (b *logRing) push(line string) { + entry := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + " " + line + b.mu.Lock() + if len(b.lines) >= b.max { + b.lines = append(b.lines[1:], entry) + } else { + b.lines = append(b.lines, entry) + } + b.mu.Unlock() +} + +func (b *logRing) snapshot(tail int) []string { + b.mu.Lock() + defer b.mu.Unlock() + if tail <= 0 || tail >= len(b.lines) { + out := make([]string, len(b.lines)) + copy(out, b.lines) + return out + } + start := len(b.lines) - tail + out := make([]string, tail) + copy(out, b.lines[start:]) + return out +} + +var logSetupOnce sync.Once + +// installLogCapture replaces os.Stdout (and os.Stderr) with the write-end of a +// pipe, then spawns a goroutine that fan-outs every line to the original +// stdout AND the in-memory ring buffer. Call exactly once, very early in +// main(). +func installLogCapture() { logSetupOnce.Do(doInstallLogCapture) } + +func doInstallLogCapture() { + originalStdout := os.Stdout + originalStderr := os.Stderr + r, w, err := os.Pipe() + if err != nil { + fmt.Fprintf(originalStdout, "[loghub] pipe failed: %v (/logs will be empty)\n", err) + return + } + os.Stdout = w + os.Stderr = w + + go func() { + scanner := bufio.NewScanner(r) + buf := make([]byte, 0, 1024*1024) + scanner.Buffer(buf, 1024*1024) + for scanner.Scan() { + line := scanner.Text() + fmt.Fprintln(originalStdout, line) + globalLogRing.push(line) + } + _, _ = io.Copy(originalStdout, r) + _ = originalStderr + }() +} + +// logsHandler returns an http.Handler for GET /logs?tail=N. Requires header +// X-Logs-Token to match the LOGS_TOKEN env var. Returns 403 if env unset. +func logsHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expected := os.Getenv("LOGS_TOKEN") + if expected == "" { + http.Error(w, "logs disabled: LOGS_TOKEN unset", http.StatusForbidden) + return + } + if r.Header.Get("X-Logs-Token") != expected { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + tail := 500 + if t := r.URL.Query().Get("tail"); t != "" { + if n, err := strconv.Atoi(t); err == nil && n > 0 { + tail = n + } + } + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + for _, l := range globalLogRing.snapshot(tail) { + fmt.Fprintln(w, l) + } + }) +} diff --git a/harnesses/usdy-nav-basis/cmd/script/main.go b/harnesses/usdy-nav-basis/cmd/script/main.go new file mode 100644 index 00000000..d3d3e39b --- /dev/null +++ b/harnesses/usdy-nav-basis/cmd/script/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "math" + "math/big" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// usdy-nav-basis: live basis between USDY's (Ondo tokenized treasury) +// onchain market price and its official redemption rate (NAV). +// +// NAV leg: Pyth Hermes "Crypto.USDY/USD.RR" redemption-rate feed. The +// same Hermes call also carries the Pyth USDY/USD market composite, +// which doubles as a cross-check venue row. Market leg: the Orca +// whirlpool USDY/USDC on Solana (deepest genuine pool, ~$2.9M), +// decoded straight from getAccountInfo (sqrtPrice u128 LE at bytes +// 65..81, both mints 6 decimals so no scale factor). +// +// Excluded, verified 2026-07-13: the Arbitrum Camelot pool holds 232 +// USDY against 7M USDC (drained), its price sits ~345 bps off NAV and +// moves nothing; kept out of the ranking, documented in the spec as +// the cautionary example of why pool depth gates peg quality. +// +// All legs keyless. One Hermes GET + one Solana RPC POST per tick. + +const ( + navFeedID = "e3d1723999820435ebab53003a542ff26847720692af92523eea613a9a28d500" + marketFeedID = "e393449f6aff8a4b6d3e1165a7c9ebec103685f3b41e60db4277b5b6d10e7326" + orcaPool = "AGXrswVDRoUf62UX9voTXv6TCGw6fBUEwDpyUd9YdZfD" + + pollInterval = 60 * time.Second + httpTimeout = 15 * time.Second +) + +var ( + basisBps = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "usdy_basis_bps", + Help: "Signed basis of the venue's USDY price vs the official redemption rate, in bps (positive = premium over NAV).", + }, []string{"venue"}) + + navGauge = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "usdy_nav_usd", + Help: "USDY official redemption rate from the Pyth RR feed.", + }) + + marketPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "usdy_market_price_usd", + Help: "USDY market price per venue.", + }, []string{"venue"}) + + sourceCall = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "usdy_source_call_total", + Help: "Fetch outcomes per source.", + }, []string{"source", "result"}) + + healthGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "usdy_health", + Help: "1 when the venue produced a basis sample on the last tick.", + }, []string{"venue"}) +) + +func envDefault(key, def string) string { + if v := strings.TrimSpace(os.Getenv(key)); v != "" { + return v + } + return def +} + +func main() { + installLogCapture() + fmt.Println("=== USDY NAV Basis Harness ===") + fmt.Println("OpenChainBench — USDY market price vs official redemption rate, keyless.") + + go func() { + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.Handle("/logs", logsHandler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + }) + if err := http.ListenAndServe(envDefault("LISTEN_ADDR", ":2112"), mux); err != nil { + fmt.Printf("[fatal] metrics server: %v\n", err) + } + }() + + client := &http.Client{Timeout: httpTimeout} + tick := func() { + nav, market := fetchHermes(client) + orca := fetchOrca(client) + + if nav <= 0 { + healthGauge.WithLabelValues("orca-solana").Set(0) + healthGauge.WithLabelValues("pyth-market").Set(0) + return + } + navGauge.Set(nav) + + emit := func(venue string, price float64) { + if price <= 0 { + healthGauge.WithLabelValues(venue).Set(0) + return + } + marketPrice.WithLabelValues(venue).Set(price) + bps := (price - nav) / nav * 10000 + basisBps.WithLabelValues(venue).Set(bps) + healthGauge.WithLabelValues(venue).Set(1) + fmt.Printf("[%s] price=%.6f nav=%.6f basis=%+.1fbps\n", venue, price, nav, bps) + } + emit("orca-solana", orca) + emit("pyth-market", market) + } + + tick() + t := time.NewTicker(pollInterval) + defer t.Stop() + for range t.C { + tick() + } +} + +// fetchHermes returns (nav, market) from one Hermes call. +func fetchHermes(client *http.Client) (float64, float64) { + url := "https://hermes.pyth.network/v2/updates/price/latest?ids[]=0x" + navFeedID + "&ids[]=0x" + marketFeedID + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)") + resp, err := client.Do(req) + if err != nil { + sourceCall.WithLabelValues("hermes", "network").Inc() + return 0, 0 + } + defer resp.Body.Close() + raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if resp.StatusCode != 200 { + sourceCall.WithLabelValues("hermes", fmt.Sprintf("http_%d", resp.StatusCode)).Inc() + return 0, 0 + } + var envel struct { + Parsed []struct { + ID string `json:"id"` + Price struct { + Price string `json:"price"` + Expo int `json:"expo"` + } `json:"price"` + } `json:"parsed"` + } + if err := json.Unmarshal(raw, &envel); err != nil { + sourceCall.WithLabelValues("hermes", "parse").Inc() + return 0, 0 + } + nav, market := 0.0, 0.0 + for _, p := range envel.Parsed { + v, err := strconv.ParseFloat(p.Price.Price, 64) + if err != nil { + continue + } + val := v * math.Pow10(p.Price.Expo) + switch strings.TrimPrefix(strings.ToLower(p.ID), "0x") { + case navFeedID: + nav = val + case marketFeedID: + market = val + } + } + sourceCall.WithLabelValues("hermes", "ok").Inc() + return nav, market +} + +// fetchOrca decodes the whirlpool sqrtPrice (u128 LE at bytes 65..81); +// USDY and USDC are both 6 decimals so price = (sqrtPrice/2^64)^2. +func fetchOrca(client *http.Client) float64 { + body := []byte(fmt.Sprintf( + `{"jsonrpc":"2.0","id":%d,"method":"getAccountInfo","params":["%s",{"encoding":"base64"}]}`, + time.Now().UnixNano(), orcaPool, + )) + req, _ := http.NewRequest("POST", envDefault("USDY_SOLANA_RPC", "https://solana-rpc.publicnode.com"), bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "OpenChainBench/1.0 (+https://openchainbench.com)") + resp, err := client.Do(req) + if err != nil { + sourceCall.WithLabelValues("orca", "network").Inc() + return 0 + } + defer resp.Body.Close() + raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if resp.StatusCode != 200 { + sourceCall.WithLabelValues("orca", fmt.Sprintf("http_%d", resp.StatusCode)).Inc() + return 0 + } + var envel struct { + Result struct { + Value struct { + Data []string `json:"data"` + } `json:"value"` + } `json:"result"` + } + if err := json.Unmarshal(raw, &envel); err != nil || len(envel.Result.Value.Data) == 0 { + sourceCall.WithLabelValues("orca", "parse").Inc() + return 0 + } + acct, err := base64.StdEncoding.DecodeString(envel.Result.Value.Data[0]) + if err != nil || len(acct) < 81 { + sourceCall.WithLabelValues("orca", "decode").Inc() + return 0 + } + lo := binary.LittleEndian.Uint64(acct[65:73]) + hi := binary.LittleEndian.Uint64(acct[73:81]) + sqrtPrice := new(big.Float).SetPrec(200).SetInt(new(big.Int).Add( + new(big.Int).Lsh(new(big.Int).SetUint64(hi), 64), + new(big.Int).SetUint64(lo), + )) + q64 := new(big.Float).SetPrec(200).SetInt(new(big.Int).Lsh(big.NewInt(1), 64)) + ratio := new(big.Float).Quo(sqrtPrice, q64) + price := new(big.Float).Mul(ratio, ratio) + f, _ := price.Float64() + sourceCall.WithLabelValues("orca", "ok").Inc() + return f +} diff --git a/harnesses/usdy-nav-basis/go.mod b/harnesses/usdy-nav-basis/go.mod new file mode 100644 index 00000000..1284070e --- /dev/null +++ b/harnesses/usdy-nav-basis/go.mod @@ -0,0 +1,18 @@ +module usdy-nav-basis + +go 1.24.0 + +require github.com/prometheus/client_golang v1.23.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect +) diff --git a/harnesses/usdy-nav-basis/go.sum b/harnesses/usdy-nav-basis/go.sum new file mode 100644 index 00000000..d6b8ca98 --- /dev/null +++ b/harnesses/usdy-nav-basis/go.sum @@ -0,0 +1,46 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/harnesses/xstocks-peg/Dockerfile b/harnesses/xstocks-peg/Dockerfile new file mode 100644 index 00000000..63108cfc --- /dev/null +++ b/harnesses/xstocks-peg/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/monitor ./cmd/script + +FROM debian:bookworm-slim + +WORKDIR /app +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/monitor /app/monitor + +EXPOSE 2112 + +CMD ["/app/monitor"] diff --git a/harnesses/xstocks-peg/cmd/script/config.go b/harnesses/xstocks-peg/cmd/script/config.go new file mode 100644 index 00000000..b151be10 --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/config.go @@ -0,0 +1,60 @@ +package main + +import ( + "os" + "strings" + "time" +) + +// xstocks-peg: Backed's xStocks tokenized equities on Solana vs their +// Yahoo reference, deviation in bps, session-labeled. Same metric +// contract as the Robinhood harness (tsp_* family) with +// issuer="xstocks", so cross-issuer comparisons are pure PromQL. +// +// Price read: Jupiter lite-api swap quotes in BOTH directions per +// symbol (sell 1 share to USDC, buy with the equivalent USDC); the mid +// is the executable peg price, immune to the price/v3 drift observed +// on thin routes (PLTRx v3 was $1.93 off its executable quote). +// A single batched price/v3 call per tick supplies the Token-2022 +// ScaledUiAmount multiplier (7 of 12 mints carry one, ~1.0009), which +// converts raw 1e8 quote units to exactly one UI share. +// +// Cohort: 12 xStocks with verified Jupiter routes at 1-share impact +// under 2bp (2026-07-13). All mints 8 decimals. + +const ( + usdcMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + oneShareRaw = 100000000 // 1e8 = 1 share at 8 decimals, pre-multiplier + pollInterval = 60 * time.Second + httpTimeout = 15 * time.Second + quoteGap = 1100 * time.Millisecond // lite tier: stay well under 60 req/min + issuerLabel = "xstocks" + logThresholdBps = 100.0 +) + +type Asset struct { + Symbol string // Yahoo ticker (HOODx maps to HOOD, etc.) + Mint string +} + +var assets = []Asset{ + {Symbol: "TSLA", Mint: "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB"}, + {Symbol: "NVDA", Mint: "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh"}, + {Symbol: "AAPL", Mint: "XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp"}, + {Symbol: "MSFT", Mint: "XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX"}, + {Symbol: "AMZN", Mint: "Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg"}, + {Symbol: "GOOGL", Mint: "XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN"}, + {Symbol: "META", Mint: "Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu"}, + {Symbol: "HOOD", Mint: "XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg"}, + {Symbol: "SPY", Mint: "XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W"}, + {Symbol: "QQQ", Mint: "Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ"}, + {Symbol: "COIN", Mint: "Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu"}, + {Symbol: "PLTR", Mint: "XsoBhf2ufR8fTyNSjqfU71DYGaE6Z3SUGAidpzriAA4"}, +} + +func listenAddr() string { + if v := strings.TrimSpace(os.Getenv("LISTEN_ADDR")); v != "" { + return v + } + return ":2112" +} diff --git a/harnesses/xstocks-peg/cmd/script/jupiter.go b/harnesses/xstocks-peg/cmd/script/jupiter.go new file mode 100644 index 00000000..a34a4708 --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/jupiter.go @@ -0,0 +1,143 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" +) + +// Jupiter lite-api legs. Two swap quotes per symbol per tick (sell one +// share, buy one share worth of USDC back), spaced quoteGap apart to +// stay far under the lite tier's 60 req/min. The mid of the two +// implied prices is the executable peg price. One batched price/v3 +// call per tick provides the ScaledUiAmount multiplier per mint. + +const jupUA = "OpenChainBench/1.0 (+https://openchainbench.com)" + +func jupGet(client *http.Client, url string) ([]byte, string) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, "request_build" + } + req.Header.Set("User-Agent", jupUA) + req.Header.Set("Accept", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, "network" + } + defer resp.Body.Close() + raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return nil, "read" + } + if resp.StatusCode != 200 { + return nil, fmt.Sprintf("http_%d", resp.StatusCode) + } + return raw, "ok" +} + +// fetchMultipliers reads price/v3 for every mint in one call and +// derives the ScaledUiAmount multiplier as usdPrice / usdPricePrescaled +// (1.0 when the mint carries no scaling config). +func fetchMultipliers(client *http.Client) map[string]float64 { + ids := make([]string, 0, len(assets)) + for _, a := range assets { + ids = append(ids, a.Mint) + } + raw, status := jupGet(client, "https://lite-api.jup.ag/price/v3?ids="+strings.Join(ids, ",")) + if raw == nil { + tspSourceCall.WithLabelValues("jup_price", status).Inc() + return nil + } + var flat map[string]struct { + USDPrice float64 `json:"usdPrice"` + USDPricePrescaled float64 `json:"usdPricePrescaled"` + } + if err := json.Unmarshal(raw, &flat); err != nil { + tspSourceCall.WithLabelValues("jup_price", "parse").Inc() + return nil + } + out := make(map[string]float64, len(flat)) + for mint, v := range flat { + m := 1.0 + if v.USDPricePrescaled > 0 && v.USDPrice > 0 { + m = v.USDPrice / v.USDPricePrescaled + } + out[mint] = m + } + tspSourceCall.WithLabelValues("jup_price", "ok").Inc() + return out +} + +type quoteResp struct { + OutAmount string `json:"outAmount"` +} + +func quoteOut(client *http.Client, inMint, outMint string, amount int64) (float64, bool) { + url := fmt.Sprintf( + "https://lite-api.jup.ag/swap/v1/quote?inputMint=%s&outputMint=%s&amount=%d&slippageBps=100", + inMint, outMint, amount, + ) + raw, status := jupGet(client, url) + if raw == nil { + tspSourceCall.WithLabelValues("jup_quote", status).Inc() + return 0, false + } + var q quoteResp + if err := json.Unmarshal(raw, &q); err != nil || q.OutAmount == "" { + tspSourceCall.WithLabelValues("jup_quote", "parse").Inc() + return 0, false + } + n, err := strconv.ParseFloat(q.OutAmount, 64) + if err != nil || n <= 0 { + tspSourceCall.WithLabelValues("jup_quote", "decode").Inc() + return 0, false + } + tspSourceCall.WithLabelValues("jup_quote", "ok").Inc() + return n, true +} + +// fetchOnchainPrices returns the executable mid price in USDC per UI +// share for every symbol. Sequential with quoteGap spacing: ~26s for +// the 12-symbol cohort, comfortably inside the 60s tick. +func fetchOnchainPrices(client *http.Client, multipliers map[string]float64) map[string]float64 { + prices := make(map[string]float64, len(assets)) + start := time.Now() + for _, a := range assets { + mult := 1.0 + if m, ok := multipliers[a.Mint]; ok && m > 0 { + mult = m + } + + // Sell leg: 1 raw share -> USDC. + sellOut, okSell := quoteOut(client, a.Mint, usdcMint, oneShareRaw) + time.Sleep(quoteGap) + sellPx := 0.0 + if okSell { + sellPx = sellOut / 1e6 * mult + } + + // Buy leg: spend the sell proceeds, see how many shares return. + buyPx := 0.0 + if okSell { + gotRaw, okBuy := quoteOut(client, usdcMint, a.Mint, int64(sellOut)) + if okBuy && gotRaw > 0 { + buyPx = (sellOut / 1e6) / (gotRaw / 1e8 / mult) + } + time.Sleep(quoteGap) + } + + switch { + case sellPx > 0 && buyPx > 0: + prices[strings.ToLower(a.Symbol)] = (sellPx + buyPx) / 2 + case sellPx > 0: + prices[strings.ToLower(a.Symbol)] = sellPx + } + } + tspSourceLatency.WithLabelValues("onchain").Set(float64(time.Since(start).Milliseconds())) + return prices +} diff --git a/harnesses/xstocks-peg/cmd/script/loghub.go b/harnesses/xstocks-peg/cmd/script/loghub.go new file mode 100644 index 00000000..9dd74488 --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/loghub.go @@ -0,0 +1,114 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "net/http" + "os" + "strconv" + "sync" + "time" +) + +// Auto-generated by the loghub inline pattern. Captures stdout/stderr into a +// bounded ring buffer and exposes GET /logs?tail=N protected by X-Logs-Token +// matching the LOGS_TOKEN env var. +// +// Keep in sync across miniapps (was previously the shared/loghub package; we +// inline because Railway's per-harness Docker build context can't reach a +// sibling shared module via go.mod replace). + +const logRingMax = 5000 + +type logRing struct { + mu sync.Mutex + lines []string + max int +} + +var globalLogRing = &logRing{max: logRingMax} + +func (b *logRing) push(line string) { + entry := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + " " + line + b.mu.Lock() + if len(b.lines) >= b.max { + b.lines = append(b.lines[1:], entry) + } else { + b.lines = append(b.lines, entry) + } + b.mu.Unlock() +} + +func (b *logRing) snapshot(tail int) []string { + b.mu.Lock() + defer b.mu.Unlock() + if tail <= 0 || tail >= len(b.lines) { + out := make([]string, len(b.lines)) + copy(out, b.lines) + return out + } + start := len(b.lines) - tail + out := make([]string, tail) + copy(out, b.lines[start:]) + return out +} + +var logSetupOnce sync.Once + +// installLogCapture replaces os.Stdout (and os.Stderr) with the write-end of a +// pipe, then spawns a goroutine that fan-outs every line to the original +// stdout AND the in-memory ring buffer. Call exactly once, very early in +// main(). +func installLogCapture() { logSetupOnce.Do(doInstallLogCapture) } + +func doInstallLogCapture() { + originalStdout := os.Stdout + originalStderr := os.Stderr + r, w, err := os.Pipe() + if err != nil { + fmt.Fprintf(originalStdout, "[loghub] pipe failed: %v (/logs will be empty)\n", err) + return + } + os.Stdout = w + os.Stderr = w + + go func() { + scanner := bufio.NewScanner(r) + buf := make([]byte, 0, 1024*1024) + scanner.Buffer(buf, 1024*1024) + for scanner.Scan() { + line := scanner.Text() + fmt.Fprintln(originalStdout, line) + globalLogRing.push(line) + } + _, _ = io.Copy(originalStdout, r) + _ = originalStderr + }() +} + +// logsHandler returns an http.Handler for GET /logs?tail=N. Requires header +// X-Logs-Token to match the LOGS_TOKEN env var. Returns 403 if env unset. +func logsHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expected := os.Getenv("LOGS_TOKEN") + if expected == "" { + http.Error(w, "logs disabled: LOGS_TOKEN unset", http.StatusForbidden) + return + } + if r.Header.Get("X-Logs-Token") != expected { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + tail := 500 + if t := r.URL.Query().Get("tail"); t != "" { + if n, err := strconv.Atoi(t); err == nil && n > 0 { + tail = n + } + } + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + for _, l := range globalLogRing.snapshot(tail) { + fmt.Fprintln(w, l) + } + }) +} diff --git a/harnesses/xstocks-peg/cmd/script/main.go b/harnesses/xstocks-peg/cmd/script/main.go new file mode 100644 index 00000000..5cc18415 --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + "math" + "net/http" + "strings" + "time" +) + +func main() { + installLogCapture() + fmt.Println("=== xStocks Peg Harness ===") + fmt.Println("OpenChainBench — Backed xStocks on Solana vs Nasdaq reference.") + fmt.Printf("Cohort: %d assets | poll: %s | Jupiter lite-api\n", len(assets), pollInterval) + for _, a := range assets { + fmt.Printf(" - %-6s mint=%s\n", a.Symbol, a.Mint) + } + + go func() { + if err := startMetricsServer(listenAddr()); err != nil { + fmt.Printf("[fatal] metrics server: %v\n", err) + } + }() + + client := &http.Client{Timeout: httpTimeout} + var periods *tradingPeriods + + tick := func() { + now := time.Now() + if periods == nil || now.Sub(periods.FetchedAt) > 30*time.Minute { + if tp := fetchTradingPeriods(client); tp != nil { + periods = tp + } + } + state := periods.state(now) + for _, s := range []string{"pre", "regular", "post", "closed", "unknown"} { + v := 0.0 + if s == state { + v = 1.0 + } + tspMarketState.WithLabelValues(s).Set(v) + } + + refs := fetchReferencePrices(client) + multipliers := fetchMultipliers(client) + onchain := fetchOnchainPrices(client, multipliers) + + for _, a := range assets { + sym := strings.ToLower(a.Symbol) + ref, hasRef := refs[sym] + pool, hasPool := onchain[sym] + if hasRef { + tspPriceReference.WithLabelValues(sym).Set(ref.Price) + if ref.AsOfSec > 0 { + tspRefAge.WithLabelValues(sym).Set(float64(now.Unix() - ref.AsOfSec)) + } + } + if hasPool { + tspPriceOnchain.WithLabelValues(sym, issuerLabel).Set(pool) + } + if hasRef && hasPool && ref.Price > 0 { + dev := math.Abs(pool-ref.Price) / ref.Price * 10000 + tspDeviationBps.WithLabelValues(sym, state, issuerLabel).Set(dev) + tspHealth.WithLabelValues(sym).Set(1) + flag := "" + if dev > logThresholdBps && state == "regular" { + flag = " <-- wide" + } + fmt.Printf("[%s][%s] pool=%.2f ref=%.2f dev=%.1fbps%s\n", sym, state, pool, ref.Price, dev, flag) + } else { + tspHealth.WithLabelValues(sym).Set(0) + } + } + } + + tick() + t := time.NewTicker(pollInterval) + defer t.Stop() + for range t.C { + tick() + } +} diff --git a/harnesses/xstocks-peg/cmd/script/metrics.go b/harnesses/xstocks-peg/cmd/script/metrics.go new file mode 100644 index 00000000..82072a68 --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/metrics.go @@ -0,0 +1,67 @@ +package main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + // Headline: absolute deviation of the onchain pool price from the + // Yahoo reference, in basis points, labeled with the market session + // the sample was taken in. The bench pins its ranking to + // market_state="regular"; the closed-state series is the weekend / + // overnight drift panel. + tspDeviationBps = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_deviation_bps", + Help: "Absolute onchain vs reference price deviation per tokenized stock, in bps, labeled by market session state.", + }, []string{"asset", "market_state", "issuer"}) + + tspPriceOnchain = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_price_onchain_usdg", + Help: "Venue spot price of the tokenized stock, in the venue quote stable.", + }, []string{"asset", "issuer"}) + + tspPriceReference = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_price_reference_usd", + Help: "Reference equity price from Yahoo Finance (regularMarketPrice; last close when the market is closed).", + }, []string{"asset"}) + + tspRefAge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_reference_age_seconds", + Help: "Age of the reference price sample (now minus regularMarketTime). Large outside regular hours by design.", + }, []string{"asset"}) + + tspMarketState = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_market_session", + Help: "1 for the currently active market session label, 0 otherwise.", + }, []string{"market_state"}) + + tspSourceLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_source_latency_milliseconds", + Help: "Round-trip latency of the last fetch per source.", + }, []string{"source"}) + + tspSourceCall = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "tsp_source_call_total", + Help: "Fetch outcomes per source (onchain batch, yahoo spark, yahoo chart).", + }, []string{"source", "result"}) + + tspHealth = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tsp_health", + Help: "1 when the last tick produced a deviation sample for the asset, 0 otherwise.", + }, []string{"asset"}) +) + +func startMetricsServer(addr string) error { + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.Handle("/logs", logsHandler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + }) + return http.ListenAndServe(addr, mux) +} diff --git a/harnesses/xstocks-peg/cmd/script/reference.go b/harnesses/xstocks-peg/cmd/script/reference.go new file mode 100644 index 00000000..3ebc5c3b --- /dev/null +++ b/harnesses/xstocks-peg/cmd/script/reference.go @@ -0,0 +1,207 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// Reference leg: Yahoo Finance, keyless. One spark batch call fetches +// the latest price for every symbol; one chart call (AAPL as the +// bellwether) fetches the day's exact session windows, which Yahoo +// publishes holiday-aware so the harness never maintains an NYSE +// calendar. Verified 2026-07-13 from the harness host: clean 200s with +// a browser User-Agent (datacenter IP), 70-130ms. +// +// Market state is derived from currentTradingPeriod epochs: +// pre / regular / post / closed. Deviation samples carry the state as +// a label so the bench can pin its headline to regular hours and read +// the weekend drift from the closed-state series. + +const ( + sparkHost = "https://query1.finance.yahoo.com/v8/finance/spark" + chartHost = "https://query1.finance.yahoo.com/v8/finance/chart/AAPL" + yahooUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36" +) + +type refQuote struct { + Price float64 + AsOfSec int64 +} + +type tradingPeriods struct { + PreStart, PreEnd int64 + RegularStart, RegularEnd int64 + PostStart, PostEnd int64 + FetchedAt time.Time +} + +// fetchReferencePrices returns the freshest Yahoo price per symbol +// (lowercased) from one spark batch call. When the market is closed +// the spark meta still carries regularMarketPrice = last close, which +// is exactly the weekend reference we want. +func fetchReferencePrices(client *http.Client) map[string]refQuote { + syms := make([]string, 0, len(assets)) + for _, a := range assets { + syms = append(syms, a.Symbol) + } + url := sparkHost + "?symbols=" + strings.Join(syms, ",") + "&range=1d&interval=1m" + raw, status := yahooGet(client, url) + if raw == nil { + tspSourceCall.WithLabelValues("yahoo", status).Inc() + // Rotate to query2 once before giving up this tick. + raw, status = yahooGet(client, strings.Replace(url, "query1", "query2", 1)) + if raw == nil { + tspSourceCall.WithLabelValues("yahoo", status).Inc() + return nil + } + } + tspSourceCall.WithLabelValues("yahoo", "ok").Inc() + + // Spark response: {"spark":{"result":[{"symbol":"AAPL","response":[{"meta":{...}}]}]}} + // or the flatter {"AAPL":{...}} shape depending on edge; handle both. + prices := make(map[string]refQuote, len(syms)) + var envel struct { + Spark struct { + Result []struct { + Symbol string `json:"symbol"` + Response []struct { + Meta struct { + RegularMarketPrice float64 `json:"regularMarketPrice"` + RegularMarketTime int64 `json:"regularMarketTime"` + } `json:"meta"` + } `json:"response"` + } `json:"result"` + } `json:"spark"` + } + if err := json.Unmarshal(raw, &envel); err == nil { + for _, r := range envel.Spark.Result { + if len(r.Response) == 0 || r.Response[0].Meta.RegularMarketPrice <= 0 { + continue + } + prices[strings.ToLower(r.Symbol)] = refQuote{ + Price: r.Response[0].Meta.RegularMarketPrice, + AsOfSec: r.Response[0].Meta.RegularMarketTime, + } + } + } + if len(prices) == 0 { + // Flat spark shape (observed live 2026-07-13): + // {"MSFT":{"timestamp":[...],"close":[...],"previousClose":X},...} + // Price = last non-null close; previousClose is the fallback when + // the close array is empty (market closed all day). + var flat map[string]struct { + Timestamp []int64 `json:"timestamp"` + Close []*float64 `json:"close"` + PreviousClose *float64 `json:"previousClose"` + } + if err := json.Unmarshal(raw, &flat); err == nil { + for sym, v := range flat { + q := refQuote{} + for i := len(v.Close) - 1; i >= 0; i-- { + if v.Close[i] != nil && *v.Close[i] > 0 { + q.Price = *v.Close[i] + if i < len(v.Timestamp) { + q.AsOfSec = v.Timestamp[i] + } + break + } + } + if q.Price == 0 && v.PreviousClose != nil && *v.PreviousClose > 0 { + q.Price = *v.PreviousClose + } + if q.Price > 0 { + prices[strings.ToLower(sym)] = q + } + } + } + } + if len(prices) == 0 { + tspSourceCall.WithLabelValues("yahoo", "parse_empty").Inc() + head := string(raw) + if len(head) > 400 { + head = head[:400] + } + fmt.Printf("[yahoo] spark yielded no symbols; body head: %s\n", head) + return nil + } + return prices +} + +// fetchTradingPeriods reads currentTradingPeriod from one chart call. +// Refreshed every 30 minutes; between refreshes marketState() reuses +// the cached windows. +func fetchTradingPeriods(client *http.Client) *tradingPeriods { + raw, status := yahooGet(client, chartHost+"?range=1d&interval=5m") + if raw == nil { + tspSourceCall.WithLabelValues("yahoo_chart", status).Inc() + return nil + } + var envel struct { + Chart struct { + Result []struct { + Meta struct { + CurrentTradingPeriod struct { + Pre struct{ Start, End int64 } `json:"pre"` + Regular struct{ Start, End int64 } `json:"regular"` + Post struct{ Start, End int64 } `json:"post"` + } `json:"currentTradingPeriod"` + } `json:"meta"` + } `json:"result"` + } `json:"chart"` + } + if err := json.Unmarshal(raw, &envel); err != nil || len(envel.Chart.Result) == 0 { + tspSourceCall.WithLabelValues("yahoo_chart", "parse").Inc() + return nil + } + m := envel.Chart.Result[0].Meta.CurrentTradingPeriod + tspSourceCall.WithLabelValues("yahoo_chart", "ok").Inc() + return &tradingPeriods{ + PreStart: m.Pre.Start, PreEnd: m.Pre.End, + RegularStart: m.Regular.Start, RegularEnd: m.Regular.End, + PostStart: m.Post.Start, PostEnd: m.Post.End, + FetchedAt: time.Now(), + } +} + +func (tp *tradingPeriods) state(now time.Time) string { + if tp == nil { + return "unknown" + } + u := now.Unix() + switch { + case u >= tp.RegularStart && u < tp.RegularEnd: + return "regular" + case u >= tp.PreStart && u < tp.PreEnd: + return "pre" + case u >= tp.PostStart && u < tp.PostEnd: + return "post" + default: + return "closed" + } +} + +func yahooGet(client *http.Client, url string) ([]byte, string) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, "request_build" + } + req.Header.Set("User-Agent", yahooUA) + req.Header.Set("Accept", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, "network" + } + defer resp.Body.Close() + raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<22)) + if err != nil { + return nil, "read" + } + if resp.StatusCode != 200 { + return nil, fmt.Sprintf("http_%d", resp.StatusCode) + } + return raw, "ok" +} diff --git a/harnesses/xstocks-peg/go.mod b/harnesses/xstocks-peg/go.mod new file mode 100644 index 00000000..815ee407 --- /dev/null +++ b/harnesses/xstocks-peg/go.mod @@ -0,0 +1,18 @@ +module xstocks-peg + +go 1.24.0 + +require github.com/prometheus/client_golang v1.23.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect +) diff --git a/harnesses/xstocks-peg/go.sum b/harnesses/xstocks-peg/go.sum new file mode 100644 index 00000000..d6b8ca98 --- /dev/null +++ b/harnesses/xstocks-peg/go.sum @@ -0,0 +1,46 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/lib/spec.ts b/src/lib/spec.ts index eced326f..0621639a 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -273,7 +273,7 @@ const loadBenchmarkUnfilteredCached = unstable_cache( // buckets (gap rendering fix). Cached v26 entries hold the old // hole-compressed arrays whose indices no longer map onto the nominal // step grid the chart back-computes timestamps from. - ["bench-unfiltered-v30", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["bench-unfiltered-v31", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); @@ -428,7 +428,7 @@ const loadAllBenchmarksCached = unstable_cache( // gated catalog to /products for 30+ min after the deploy). // v29: bumped with bench-unfiltered-v26 (monad-rpc + megaeth-rpc ship). // v30: bumped with bench-unfiltered-v27 (dense series with nulls). - ["all-benchmarks-v33", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["all-benchmarks-v34", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] }, ); export const loadAllBenchmarks = cache(loadAllBenchmarksCached); @@ -507,7 +507,7 @@ const loadBenchmarkFiltered = unstable_cache( // v16: bumped with the bench 074 ship (lockstep rule, see all-benchmarks-v28). // v17: bumped with the monad-rpc + megaeth-rpc ship (lockstep rule). // v18: bumped with bench-unfiltered-v27 (dense series with nulls). - ["bench-filters-v21", process.env.VERCEL_ENV === "production" ? "prod" : "all"], + ["bench-filters-v22", process.env.VERCEL_ENV === "production" ? "prod" : "all"], { revalidate: 300, tags: ["benchmarks"] } );