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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions benchmarks/tokenized-stock-peg.yml

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions harnesses/tokenized-stock-peg/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
72 changes: 72 additions & 0 deletions harnesses/tokenized-stock-peg/cmd/script/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"os"
"strings"
"time"
)

// Tokenized-stock-peg harness: onchain price of Robinhood Chain
// tokenized equities (Uniswap v4 pools vs USDG) against the real
// Nasdaq/NYSE price from Yahoo Finance, deviation in basis points,
// labeled by market session state.
//
// Cohort: the 11 official "<Company> • Robinhood Token" equities whose
// USDG pool has real liquidity and swap activity (verified 2026-07-13
// via Blockscout + PoolManager extsload sweep). Excluded and why:
// SPCX (SpaceX is not listed, no reference price exists), SNDK + QQQ
// (pool depth under $2k, pure noise), CRCL (pool has zero liquidity),
// the ~80 other official tokens (issuer-seeded placeholder pools at
// 90-95% fee, zero swaps), HOOD (never issued onchain, only spam).
//
// Orientation: Uniswap v4 orders currencies by address; USDGIsC0 says
// whether USDG (6 decimals) is currency0 in that pool. Stocks are 18
// decimals, so the raw sqrtPriceX96 price converts with a 1e12 factor
// whose direction depends on the ordering.

const (
rpcDefault = "https://rpc.mainnet.chain.robinhood.com"
stateView = "0xF3334192D15450CdD385c8B70e03f9A6bD9E673b"
getSlot0Sel = "0xc815641c" // StateView.getSlot0(bytes32) — live-verified
pollInterval = 60 * time.Second
httpTimeout = 15 * time.Second
// A tokenized stock more than this far from its reference during
// regular hours is displayed but flagged; used only for logging.
logThresholdBps = 100.0
)

type Asset struct {
Symbol string // Yahoo ticker == display slug (lowercased for labels)
Token string
PoolID string
FeePPM int
USDGIsC0 bool
}

var assets = []Asset{
{Symbol: "NVDA", Token: "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC", PoolID: "0x3bb34a44f1b2b5f32c034c38a53065a521a47b199700fa9bd19d60985ff24bf1", FeePPM: 3000, USDGIsC0: true},
{Symbol: "AAPL", Token: "0xaF3D76f1834A1d425780943C99Ea8A608f8a93f9", PoolID: "0xda4116b5894ee7479e64eae9276e1a2944ef0e5ce863a299d296a15618deee01", FeePPM: 10000, USDGIsC0: true},
{Symbol: "GOOGL", Token: "0x2e0847E8910a9732eB3fb1bb4b70a580ADAD4FE3", PoolID: "0xef22239f96c6ac95dcd57b90c6b14c0cc8c3c16844def34daef68dc9dd945344", FeePPM: 10000, USDGIsC0: false},
{Symbol: "TSLA", Token: "0x322F0929c4625eD5bAd873c95208D54E1c003b2d", PoolID: "0x8517f8071ae5b831b738052f12125e8e3d6c158b78728aa44ce3b25e5104d32e", FeePPM: 3000, USDGIsC0: false},
{Symbol: "PLTR", Token: "0x894E1EC2D74FFE5AEF8Dc8A9e84686acCB964F2A", PoolID: "0xee430ee1003e1985e1828a01b9a20dad67ad4302994fe2abb4a173de4ac54623", FeePPM: 10000, USDGIsC0: true},
{Symbol: "META", Token: "0xc0D6457C16Cc70d6790Dd43521C899C87ce02f35", PoolID: "0x5875d407a42965b0e768c8925cea290e06fa50603ef34fc99eb92a1050e6ae36", FeePPM: 3000, USDGIsC0: true},
{Symbol: "AMD", Token: "0x86923f96303D656E4aa86D9d42D1e57ad2023fdC", PoolID: "0xde9f85fdd9e05a943a52f2c69ffafe3064a3287df03d02c9b431bc92d4781274", FeePPM: 10000, USDGIsC0: true},
{Symbol: "MSFT", Token: "0xe93237C50D904957Cf27E7B1133b510C669c2e74", PoolID: "0xace02af66d24427b162f80329e039b78c226fb9a79669f5e18d5feec2aa0c056", FeePPM: 20000, USDGIsC0: true},
{Symbol: "AMZN", Token: "0x12f190a9F9d7D37a250758b26824B97CE941bF54", PoolID: "0xa3280c768df670a535d14af8c22ad3907f2acfc0277c03309fb4d5fc8d43447e", FeePPM: 20000, USDGIsC0: false},
{Symbol: "SPY", Token: "0x117cc2133c37B721F49dE2A7a74833232B3B4C0C", PoolID: "0x7eeda68cd84620339e6ad4bf054af9b19878ac13139991c7aaec018c40a8bb6a", FeePPM: 10000, USDGIsC0: false},
{Symbol: "MU", Token: "0xfF080c8ce2E5feadaCa0Da81314Ae59D232d4afD", PoolID: "0x6fa3ee0048e78bf0a513eb0ab56f482944a767c21db990fcf555605e69f05659", FeePPM: 10000, USDGIsC0: true},
}

func rpcURL() string {
if v := strings.TrimSpace(os.Getenv("TSP_RPC_URL")); v != "" {
return v
}
return rpcDefault
}

func listenAddr() string {
if v := strings.TrimSpace(os.Getenv("LISTEN_ADDR")); v != "" {
return v
}
return ":2112"
}
114 changes: 114 additions & 0 deletions harnesses/tokenized-stock-peg/cmd/script/loghub.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
82 changes: 82 additions & 0 deletions harnesses/tokenized-stock-peg/cmd/script/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"fmt"
"math"
"net/http"
"strings"
"time"
)

func main() {
installLogCapture()
fmt.Println("=== Tokenized Stock Peg Harness ===")
fmt.Println("OpenChainBench — Robinhood Chain tokenized equities vs Nasdaq reference.")
fmt.Printf("Cohort: %d assets | poll: %s | RPC: %s\n", len(assets), pollInterval, rpcURL())
for _, a := range assets {
fmt.Printf(" - %-6s token=%s pool=%s… fee=%.2f%%\n", a.Symbol, a.Token[:10]+"…", a.PoolID[:14], float64(a.FeePPM)/10000)
}

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)
onchain := fetchOnchainPrices(client)

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).Set(pool)
}
if hasRef && hasPool && ref.Price > 0 {
dev := math.Abs(pool-ref.Price) / ref.Price * 10000
tspDeviationBps.WithLabelValues(sym, state).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()
}
}
67 changes: 67 additions & 0 deletions harnesses/tokenized-stock-peg/cmd/script/metrics.go
Original file line number Diff line number Diff line change
@@ -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"})

tspPriceOnchain = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "tsp_price_onchain_usdg",
Help: "Uniswap v4 pool spot price of the tokenized stock, in USDG.",
}, []string{"asset"})

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)
}
Loading
Loading