Skip to content

Latest commit

Β 

History

92 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Glidepath mark β€” one red dump bar splits into green tranches that glide down, paced to organic demand

Glidepath πŸ›¬

You have to sell a token you never meant to own. Glidepath turns it into a dated selling calendar sized to the market's organic demand β€” so you are never the biggest seller on a day the pros are exiting.

Glidepath β€” turns a bag you must sell into a dated selling calendar sized to organic demand; a red day halves that day's tranche

It plans. It never trades. 12 credits per plan on EVM chains, 15 on solana/base, 0 on a cache hit β€” and the 13 recorded plans replay offline byte-for-byte with zero network calls and zero credits (npm run verify).


Live Demo For the Judge Built for Nansen Meridian Submission on X


Next.js TypeScript Nansen tests property cases fixtures license ci release


JUDGE.md Β· DEMO.md Β· SCORING.md Β· DX-REPORT.md Β· ARCHITECTURE.md


πŸ“Έ See it in Action

The PEPE plan, live on 2026-09-19 β€” 12 credits, 9 calls, 2.0 s; a red today (exchange net deposits +$736K) halves the first tranche β€” and every Nansen call streams into the rail on the right as it lands:

Glidepath β€” 13 s demo: token, chain, amount held; the Nansen call rail fills on the right, each call pending then green with credits and ms; a red today halves the first tranche; dump-today vs paced tranches

BONK on solana inside a red regime β€” today is green, but tgm/flow-intelligence 7d shows exchange net deposits of +$3.32M over the week and tgm/flows marks 1 of the last 13 days red; the costs are real trade/quote routes β€” and the rail shows all 12 calls that just landed, 15 credits, 5.3 s:

Glidepath β€” 20B BONK on solana: green today inside a red 7-day exchange-deposit regime, 4 tranches, route-quote costs, the Nansen call rail with 12 live rows

provenance drawer beside the rail β€” the receipt and the live meter, same nine calls mobile β€” the TURBO thin plan, 90 tranches, 30 % still unsold; the rail is the bar at the bottom
Glidepath β€” provenance drawer: every Nansen call behind the plan with fields used, credits and ms, next to the call rail Glidepath on a phone β€” 50M TURBO, a thin plan of 90 tranches with 30 % still unsold after 90 days
Glidepath share card β€” BONK on solana: dump today vs 4 tranches, the OG image of the share page

πŸ’‘ The Problem & Solution

The Problem

A nonprofit's finance officer receives a $40K memecoin donation. A freelancer is paid in a project's token. They have never used a DEX, and they must turn the tokens into dollars without crashing the price or competing with the funds on the day the funds are dumping.

The Solution

Paste token Β· chain Β· amount held. Glidepath:

  1. Computes organic daily buy volume β€” DEX buys over the last 7 days by wallets that carry none of Nansen's Smart Money / Fund / Whale / Exchange / sniper-bot labels β€” via tgm/who-bought-sold label filters and tgm/token-information.
  2. Prints the dump-today line: position value, estimated impact, and what share of a full day's organic buying you would be.
  3. Sizes daily tranches to k Γ— organic/day, where k slides from 10 % to 3 % as Nansen's peer-percentile risk indicators rise (tgm/indicators), capped at 1 % of liquidity_usd.
  4. Flags red days: today from tgm/flow-intelligence (Smart Money net-selling, or net deposits to exchanges), the 13 complete days before it from tgm/flows daily cohort history (a 14-day strip with today). A red today halves the first tranche; the observed red-day rate stretches the expected finish.
  5. Shows dump today vs glidepath cost β€” constant-product from liquidity_usd, or a real routed trade/quote on solana/base, labelled as such.
  6. Exports the plan as ICS (one calendar event per tranche, with the go/no-go rule inside) and CSV, plus a share card with an OG image.
  7. Streams every Nansen call into the call rail on the right as it happens (endpoint Β· params Β· credits Β· ms Β· hash), then opens a provenance drawer: every call, the fields used, credits (from Nansen's response headers), cached or live, ms β€” and a "computed Ns ago" badge.

It plans. It never trades.

πŸ—οΈ Architecture & Tech Stack

One plan is resolve β†’ facts β†’ computePlan β†’ applyQuotes; the plan is a pure function of (facts, input, now), so a recorded fixture replays to the same calendar dates, the same red history and the same decision hash. Full module map and data flow: ARCHITECTURE.md.

Glidepath architecture β€” views (web page with the live call rail, /api/plan?stream=1 behind a spend guard, share page, CLI) β†’ resolve β†’ facts β†’ plan.ts computePlan β†’ impact/export β†’ Plan; a read-through cache in front of seven Nansen endpoints with their credits; recorded fixtures replay offline

Mermaid source β€” expand to see the diagram as text (renders on GitHub)
flowchart LR
  IN["token Β· chain Β· amount"] --> R["resolve.ts<br/>address passes through Β· ticker β†’ search/general (0 credits)"]
  R --> F["facts.ts<br/>Promise.all over 8 calls, every failure lands in errors[term]"]
  F <--> C["cache.ts<br/>key = sha256(method + endpoint + canonical body)<br/>hit = 0 credits Β· NANSEN_OFFLINE=1 never touches the network"]
  C <--> N["Nansen API<br/>tgm/token-information Β· tgm/who-bought-sold Γ—2<br/>tgm/flow-intelligence Γ—2 Β· tgm/flows Γ—2 Β· tgm/indicators"]
  F --> P["plan.ts<br/>price β†’ organic/day β†’ risk dial k β†’ tranches<br/>β†’ red-day rule (today + 13 days) β†’ impact β†’ decision hash"]
  P --> Q["impact.ts<br/>constant-product cost(V) = VΒ²/(L/2+V)<br/>or trade/quote Γ—3 on solana/base"]
  Q <--> N
  P --> X["export.ts<br/>ICS (one VEVENT per tranche, go/no-go rule inside) Β· CSV"]
  P --> CLI["packages/cli<br/>npm run glidepath -- &lt;token&gt; --chain --amount"]
  P --> WEB["apps/web (Next.js 15)<br/>/api/plan?stream=1 β†’ the call rail Β· /api/export Β· /api/og Β· /p share page"]
  N -. "CallEvent start Β· end (the same Call objects)" .-> WEB
Loading
layer what where
Engine TypeScript. NansenClient: token bucket (8 rps), per-call timeout, 1 retry on 429/5xx/timeout honouring Retry-After, credits read from response headers; CachedNansenClient read-through cache (1 h TTL on the web, failures never cached) packages/core
CLI npm run glidepath -- <token> --chain <chain> --amount <n> with --json --explain --ics --csv --no-cache --no-quotes packages/cli
Web Next.js 15 App Router, plain CSS, server-side key; memory + /tmp disk cache on Vercel; OG card 1200Γ—630 apps/web
Data Nansen API β€” 7 endpoints Β· 11 calls per plan (table below) packages/core/src/nansen.ts
Proof 258 vitest tests (60,000 fast-check property cases on the planner, key-boundary tests) Β· 13 recorded fixtures replayed offline Β· 60 Playwright checks on the built app Β· bench.ts p50/p95 Β· check_submission_readiness.ts packages/core/test, apps/web/test, e2e/, fixtures/, scripts/
CI GitHub Actions, 7 stages, no key anywhere: quality (prettier, eslint, tsc Γ—2, vitest + coverage, offline verify, readiness) β†’ security (TruffleHog, npm audit, licenses) β†’ build + bundle budget β†’ Playwright E2E β†’ Lighthouse β†’ deploy gate β†’ Vercel production deploy (prebuilt, main only); CodeQL, gitleaks (full history), Dependabot, semantic releases alongside .github/workflows/

πŸ† Nansen Integration

The data drives the logic β€” every on-screen number traces to a named Nansen field.

endpoint credits fields used what it drives
search/general 0 tokens[].symbol/name/chain/address/rank ticker β†’ address on the chosen chain; "exists on other chains" when not
tgm/token-information (7d) 1 market_cap_usd, circulating_supply, buy_volume_usd, unique_buyers, liquidity_usd, total_holders, total_supply price, total DEX buys, liquidity cap, impact model, sanity warnings
tgm/who-bought-sold BUY 7d, include_smart_money_labels = pro labels 1 data[].bought_volume_usd, address_label the pro share subtracted from total buys β†’ organic/day
tgm/who-bought-sold BUY 7d, exclude_smart_money_labels, page 1 of 1000 1 data[].bought_volume_usd single-buyer dependence (top organic buyer > 25 %) β†’ k βˆ’ 2 pts
tgm/flow-intelligence 1d 1 smart_trader_net_flow_usd, exchange_net_flow_usd is today red (halve tranche 1)
tgm/flow-intelligence 7d 1 same 7-day regime β€” a week of net-selling past 3Γ— the daily thresholds, shown beside today
tgm/flows label smart_money, 14d 1 date, is_complete, price_usd, total_inflows_count, total_outflows_count daily Smart Money net flow β†’ red-day history
tgm/flows label exchange, 14d 1 same daily exchange net deposits β†’ red-day history and rate
tgm/indicators 5 liquidity-risk, concentration-risk, btc-reflexivity scores the participation rate k (10 % β†’ 3 %)
trade/quote Γ—3 (solana, base) 1 each toTokenDecimals, inUsdValue, outUsdValue, priceImpactPct real routed cost of one tranche and of the whole bag

12 credits per plan on EVM chains, 15 on solana/base, 0 on a cache hit. Formulas with the real PEPE numbers: docs/SCORING.md.

You watch it happen. The right-hand Nansen call rail streams every call as the plan is computed β€” /api/plan?stream=1 emits an NDJSON line when a call leaves (a pending row) and one when it lands (the recorded Call: endpoint, params, credits from the response header, ms, sha256 of the body) β€” the same objects the provenance drawer prints afterwards, so the rail's counters and the drawer's totals always agree. On load the rail shows the recorded example's calls labelled replayed Β· 0 cr; the CLI prints the same rows with --explain.

Why only Nansen

"Organic demand" is a who, not a how much. An RPC node or an explorer gives you volume; it cannot tell you which buyers are funds, Smart Money, exchanges or sniper-bot users. On Nansen that split exists as a server-side label filter on tgm/who-bought-sold β€” and the spike showed the filter acts on labels the row's address_label does not even display, so there is no client-side substitute. The red-day rule needs cohort net flows (tgm/flows, tgm/flow-intelligence); the risk dial needs peer-percentile scores (tgm/indicators); the route quote needs an aggregator that returns decimals with the price (trade/quote). Remove Nansen and Glidepath degrades to a TWAP calculator on raw volume β€” exactly the number a forced seller must not trust.

Impact-model caveat

The constant-product estimate treats liquidity_usd as one pool with the token on one side. It is optimistic for tokens whose depth sits in a single thin pool, and it ignores MEV and gas. On solana and base the routed trade/quote replaces it and the label changes to "route quote". Not financial advice.

πŸ“Š Engineering Rigor

metric value how to reproduce
tests 258 vitest tests, green β€” 18 of them regression tests named for the defect each pins npm test
spend guard every credit-spending route (/api/plan, /api/export, /p, /api/og) capped at 6 requests/min per address per route (429) and a shared 3,000 live credits/day (honest 503 past it, before any Nansen call) apps/web/lib/guard.ts, apps/web/test/guard.test.ts
property-based verification 60,000 generated cases (fast-check, 6 properties Γ— 10,000) on the tranche planner β€” found and fixed one real defect npm test (plan.property.test.ts)
key boundary the nsn_ key never reaches a client: engine JSON/ICS/CSV, the route handler, the built pages β€” all asserted key-free; validation runs before any fetch npm test + npm run e2e
E2E 60 Playwright checks (30 tests Γ— chromium + Pixel 7) against the production build with no key npm run e2e
fixtures 13 recorded, 13/13 reproduced offline β€” zero network calls, zero credits npm run verify
bench, cold (fresh cache, live Nansen) p50 3.5 s / p95 6.0 s npm run bench (7 tokens Γ— 3 runs)
bench, warm (second call, same cache) p50 5 ms / p95 356 ms same
credits / plan 13.0 (bench average; 12 on EVM, 15 on solana/base, 0 on a cache hit) same
calls / plan 9.3 same
who-bought-sold pages / plan 2.0 same
failed calls 7 of 195 (6 deterministic: tgm/flows refuses stablecoins) same
readiness npm run check β€” files, secrets, kitchen leaks, fixtures, verify, tests, README claims npm run check
  • 258 vitest tests (npm test): tranche-sizing table, red-day rule, risk dial vs indicator scores, impact model, decision-hash stability, pagination cap (the 20,000-buyer case), cache and offline mode, client retries/timeouts/header credits, ICS/CSV, resolver, end-to-end on a fake Nansen, one replay test per fixture β€” and three high-signal categories:
    • 18 regression tests, each named for the defect it pins (qa round …, regression (…)): the one-tranche "expect ~2 days" copy, the ICS \; no-op, the error body cut mid-word, the FLOKI empty symbol, liquidity_usd = 0, the 7-day regime that was fetched but never shown, the echoed-key redaction, the zero-token tranche; and from the 2026-09-23 audit: a non-JSON 200 recorded twice and cached for an hour, a socket error never retried, a pro list ending exactly on page 3 called truncated, a cross-model "savings" figure, an unbounded memory cache.
    • One property-based verification, 60,000 generated cases (packages/core/test/plan.property.test.ts, fast-check, 6 properties Γ— 10,000 runs): Ξ£ tranches + remainder = amount held; every tranche ≀ k Γ— organic/day and ≀ 1 % of liquidity; a red today halves tranche 1 and only tranche 1; consecutive UTC dates, ≀ 90 of them, expectedDays β‰₯ days β‰₯ 1; the decision hash is deterministic and invariant to timing and context fields. Run 1 found a real defect β€” a liquidity cap underflowing at a dust price produced 90 tranches of zero tokens β€” now a guarded, fully-unsold thin plan with its own regression test.
    • Key-boundary tests (packages/core/test/boundary.test.ts, apps/web/test/api-boundary.test.ts, e2e/): the server key travels in one request header and nowhere else β€” plan JSON, provenance, ICS/CSV, the route handler's 400/500 bodies and the built pages are asserted free of nsn_; an upstream body that echoes the key is redacted; /api/plan validates input before the key check and before any fetch.
  • 13 fixtures, 13/13 reproduced offline (npm run verify): each stores the raw Nansen responses byte-for-byte, the plan and the clock; replay must match the hash with zero network calls and zero credits.
  • Bench (npm run bench, 7 tokens Γ— 3 runs): cold p50 3.5 s / p95 6.0 s, warm p50 5 ms / p95 356 ms, 13.0 credits/plan, 9.3 calls/plan, 2.0 who-bought-sold pages/plan, 7 of 195 calls failed (6 deterministic: tgm/flows refuses stablecoins). Full table and reproduce steps: DEMO.md.

Every number traces to a named Nansen field β€” the provenance drawer and --explain list them per call. Nothing is estimated silently; when a call fails, the term is null, the warning names the endpoint, and the plan degrades (no history strip, no route quote, k treated as medium) instead of inventing a value. Honest states: no organic demand (< $50/day or < 5 buyers) β†’ "there is nobody to sell to at any pace", numbers shown, no calendar. Calendar beyond 90 days β†’ "N % still unsold after 90 days". Ticker unknown on the chain β†’ the chains where it exists.

Honest limits (12)

  1. The impact model is an approximation β€” see the caveat: one pool, token on one side, optimistic for a single thin pool, no MEV or gas. Only solana/base get a real route quote.
  2. The pro share is small. On the 11 tokens tried, Smart Money / funds / whales / exchanges were 0–3.6 % of DEX buys β€” Glidepath shows that split rather than dramatising it (DX-REPORT.md).
  3. Future days cannot be known red or green. Today is live; the 13 complete days before it are real; each ICS event carries the rule to re-check on the morning.
  4. A well-formed but nonexistent address still spends 12 credits β€” the parallel fan-out in facts.ts has no existence gate before it; the plan comes back no-price with no crash (independent review 2026-09-16, left as is: gating on token-information would serialise the hero path).
  5. The 12 s organic-breadth timeout is the wall-time ceiling on a slow Nansen day β€” it timed out once during review (plan took 14.8 s, shown as a red "failed: timeout" row in provenance, "concentration not applied"); not a crash, but visible (independent review 2026-09-16).
  6. Bug found and fixed β€” /p and /api/export had no try/catch and sat outside the spend guard. A missing server key turned a share link into a 500 page, and a loop on either GET URL could spend credits around the per-IP rate and the daily ceiling. One admit() gate now fronts all four credit-spending routes; failures render a banner or a plain-text status (audit 2026-09-23).
  7. Bug found and fixed β€” one-tranche plans said "fits in one day" beside "expect ~2 days at the 15 % red-day rate". expectedDays is now 1 when today's colour is already known; regression in packages/core/test/plan.test.ts (independent review 2026-09-16).
  8. Bug found and fixed β€” ICS escaping was a no-op ("\;") so ; was never escaped, and content lines were not folded at 75 octets (RFC 5545). Both fixed without splitting UTF-8; regression in packages/core/test/export.test.ts (independent review 2026-09-16).
  9. Bug found and fixed β€” Nansen JSON error bodies were cut mid-word on screen ("…is a st"). The body's message is now surfaced as a sentence ("…is a stablecoin. The TGM flows endpoint…") (independent review 2026-09-16).
  10. Bug found and fixed β€” seed --rederive overwrote the USDC fixture's live 422 reason with an offline-cache-miss string. The script now preserves recorded errors; the fixture was restored from the original recording (independent review 2026-09-16).
  11. Bug found and fixed β€” the share-page "computed Ns ago" badge hydrated with a server clock (server/client mismatch), and CLI --explain did not label who-bought-sold rows as (pros)/(organic). Both fixed (independent review 2026-09-16).
  12. Docs bug found and fixed β€” the history strip was described as "the last 14 days". Nansen's tgm/flows from is exclusive, so it is 13 complete days + live today (14 cells); wording fixed here and in SCORING.md (independent review 2026-09-16).

πŸš€ Getting Started

Prerequisites

  • Node β‰₯ 20
  • NANSEN_API_KEY β€” an nsn_… key from https://app.nansen.ai/api. Only the live steps need it; without a key, npm run verify replays the 13 recorded plans offline.

Installation

Quickstart β€” runs in under 10 minutes:

git clone https://github.com/edycutjong/glidepath && cd glidepath    # 0:10
npm install                                                          # 0:40  (Node β‰₯ 20)
export NANSEN_API_KEY=nsn_...                                        # from https://app.nansen.ai/api
npm run glidepath -- PEPE --chain ethereum --amount 12000000000      # 0:05  β†’ the plan, 12 credits
npm run dev                                                          # 0:20  β†’ http://localhost:3000, click "PEPE Β· 12B"

Timed on a clean clone (macOS, Node 22, warm npm cache, 2026-09-16): clone + npm install 8 s, first live plan 2 s, npm run verify 1 s, web build 12 s β€” under a minute; budget 10 minutes on a cold npm cache and a slow link. Without a key, npm run verify replays the 13 recorded plans offline.

CLI flags: --json (full plan + provenance) Β· --explain (every tranche and every call) Β· --ics plan.ics Β· --csv plan.csv Β· --no-cache Β· --no-quotes.

πŸ§ͺ Testing & CI

7-stage pipeline: Quality β†’ Security β†’ Build β†’ E2E β†’ Performance β†’ Deploy gate β†’ Production Deploy (prebuilt vercel deploy to glidepath.edycu.dev, main only, after every gate) β€” no Nansen key anywhere; the only secret is VERCEL_TOKEN. The product is only exercised live by a human with a key.

# ── Code Quality ────────────────────────────
npm run lint          # ESLint (flat config, TS + React hooks)
npm run format:check  # Prettier
npm run typecheck     # engine + CLI + scripts   Β·   npm run typecheck:web
npm test              # 258 vitest tests incl. 60,000 property cases
npm run test:coverage # + v8 coverage (100% statements/branches/functions/lines, engine + API)
npm run verify        # 13/13 recorded plans replay offline β€” no key, no network, 0 credits
npm run check         # submission-readiness audit (files, secrets, kitchen leaks, fixtures, verify, tests, README claims)
npm run ci            # audit + format + lint + typecheck Γ—2 + coverage + verify + check

# ── Advanced Testing ────────────────────────
npm run e2e           # Playwright, 4 suites Γ— chromium + Pixel 7, built app, no key
npm run e2e:ui        # Playwright interactive mode
npm run lighthouse    # Lighthouse CI on / and /judge (a11y β‰₯ 0.9 is a hard gate)
npm run ci:full       # ci + next build + e2e

# ── Security / live ─────────────────────────
npm run audit         # npm audit on production dependencies (high+)
npm run bench         # live, ~270 credits for 7 tokens Γ— 3 runs
Layer Tool Status
Code Quality ESLint 9 (typescript-eslint, react-hooks) + Prettier + tsc (engine, web) βœ…
Unit Testing vitest, 258 tests, v8 coverage 100% statements/branches/functions/lines βœ…
Property-based fast-check β€” 60,000 generated cases on the tranche planner βœ…
Key boundary engine + route handler + built pages asserted nsn_-free βœ…
Offline proof 13/13 fixtures replay byte-for-byte (verify) βœ…
E2E Testing Playwright β€” 5 suites (home, plan flow, responsive, /judge, call rail), 60 checks, no key βœ…
Security (SAST) CodeQL (javascript-typescript) βœ…
Security (SCA) Dependabot (4 npm manifests + actions, grouped monthly, no majors) + npm audit + license-checker βœ…
Secret Scanning TruffleHog (verified only) + gitleaks (full history) + npm run check history grep βœ…
Performance Lighthouse CI (advisory), bundle budget 1.5/2 MB βœ…
Releases Semantic versions from conventional commits (release.yml), v1.0.0 β†’ v1.1.0 tagged by the workflow βœ…
Community CoC Β· Contributing Β· Security policy Β· issue + PR templates βœ…

πŸ“ Project Structure

packages/core   engine (client, cache, facts, plan, impact, export, fixtures)   packages/cli   the CLI
apps/web        Next.js 15 planner (/api/plan, /api/export, /api/og, /p)       scripts/       spike Β· seed Β· verify Β· bench Β· check
fixtures/       13 recorded live runs                                           docs/          SCORING.md Β· DX-REPORT.md Β· screenshots
e2e/            Playwright suites (no key)                                      JUDGE.md       the /judge page, mirrored for the repo reader

πŸ“½οΈ Demo Materials

  • Live: https://glidepath.edycu.dev β€” no wallet, no account; five example chips (PEPE Β· BONK Β· BRETT Β· TURBO Β· SHIB2), ICS/CSV export, share card with OG image.
  • Reproduce every number: DEMO.md β€” the recorded PEPE plan, offline replay, tests, bench table, re-recording the fixtures, the day-one spike, and the credits spent.
  • Formulas with the real PEPE numbers: docs/SCORING.md Β· API findings from the spike: docs/DX-REPORT.md.

πŸ“„ License

MIT β€” see LICENSE. Built for the Nansen Meridian Buildathon (Sep 2026) by @edycutjong.

About

πŸ›¬ Paste a token, a chain and the amount you hold β†’ a dated selling calendar paced to organic demand. Nansen labels decide what is organic. It plans; it never trades.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages