Commit Graph
9 Commits
Author SHA1 Message Date
Kevin Codex 1f96280b84 home: rank by buyers, not launch order (live sort + ranking engine)
The home list defaulted to launch order, so the first screen was whatever was launched last:
duplicates from two wallets and zero-holder rows. Replace the Trending tab with Live and make it
the default. One rule, in lib/launchpad/ranking.ts and mirrored in SQL so paging stays correct:

  A token is live when an outside wallet traded it in the last 24h. Outside = not the launcher and
  not a buy inside the sniper window (launch block + 3, the holders panel's definition; one bot
  sniped 121 launches within 2s and would otherwise have made every token live). Live tokens rank
  by outside wallets this hour, then last trade, then holders. Then launches under 1h old, then
  quiet launches under a divider, one row per launcher (the rest are folded into it).

- queries: per-row `traders_1h_ex` / `traders_24h_ex` (outside wallets) in both windows, a `live`
  sort built as a CTE with the tier and a per-launcher row_number, `launcher_collapsed` on the kept
  row; `trending` stays accepted and orders like `live`. Nothing hidden: New, search and the API
  keep every launch. No schema change.
- ranking.ts absorbs trending.ts: tier + chip helpers for the list, strip score now weighs distinct
  outside wallets first (trades log-scaled), day window needs an outside wallet.
- list UI: Live tab first and default, rank numbers only for live rows, a chip per row saying why it
  sits there, a "Quiet launches" divider; the URL writes sort=new explicitly now.
- docs: llms.txt, agents page, rules FAQ ("Why is my token under quiet launches?"); smoke checks the
  live sort and its fields.

Verified against a local production copy: 51 live / 103 quiet rows at a pinned clock, one row per
launcher outside live, no overlap between pages, no self-traded token counted as live.
2026-09-10 14:49:48 +08:00
Kevin Codex c54e7f7871 Merge pull request #10 from beardthelion/fix/edit-rate-limit-nonce-ordering
fix: verify signature before consuming edit nonce; rate-limit on proven wallet
2026-09-09 21:19:44 +08:00
Kevin Codex 81e53e8157 GITLAWB badge: planet logo as a navy PNG tile
public/gitlawb-mark.png is now generated from the web repo's planet logo: the mark recoloured
white, the blue satellite kept, cropped and squared on a navy ground with rounded corners
baked into the alpha. Badge pill is the same navy so tile and pill read as one piece.
2026-09-08 16:39:18 +08:00
beardthelion a6e4407a94 Address PR #10 review: cross-platform tests, RPC outage 503, e2e gaps
Four points from the CHANGES_REQUESTED review, plus the test-key concern:

1. Cross-platform test command. `npm test` used inline `TEST_MOCK_DB=1
   NODE_OPTIONS=...` which fails on Windows (cmd does not expand inline
   env vars). The mock swap is actually done by tsconfig.test.json paths,
   so TEST_MOCK_DB was vestigial; the only need is the react-server
   condition for `server-only`. Pass `--conditions=react-server` straight
   to tsx, which is portable with no env var and no new dependency.
   Removed `test:unit`: plain `node --test` cannot run the server test
   (needs tsconfig path mocks + react-server), so it was broken, and
   `test` now runs the whole suite cross-platform.

2. RPC outage classification. viem 2.55.19's verifyMessage returns false
   (rather than throwing) when the RPC transport is unreachable, so an
   outage surfaced as 401 "signature does not match" instead of 503.
   Added a getChainId liveness probe on the false branch: if the probe
   throws, the false came from an outage (503); otherwise it is a genuine
   mismatch (401). The nonce is intact either way. The probe only runs on
   the failure path, so the happy path keeps its cost.

3. Transport-level outage test through viem itself. The chain mock now
   returns a real viem client with an unreachable transport when
   transportOutage is set, so the outage path runs through viem's actual
   verifyErc6492 path (the same one smart-wallet verification uses) rather
   than a hand-rolled stub. Added a unit test asserting 503 + nonce intact.

4. e2e IP-limit test. The IP check used a fresh IP per request, so it
   passed even with no limiter. Kept the cross-IP isolation check and
   added a single repeated IP past the 30/min limit, asserting it
   eventually gets 429.

Test key: replaced the hardcoded TEST_PRIVATE_KEY with a freshly
generated disposable key (never funded, public by design) and labelled
it. The old key is in git history; if it was ever used for real funds
those should be moved, since it is now public.
2026-09-08 01:08:27 -05:00
beardthelion 11b70df124 fix: remove wallet rate limit from nonce endpoint; strengthen test mock
CodeRabbit review caught two issues:

1. The wallet rate limit added to issueNonce was still abusable. The nonce
   endpoint is unauthenticated (no signature), so the launcher check only
   confirms the supplied wallet matches the public on-chain launcher
   address. An attacker who knows that address passes the launcher check
   and spends the wallet bucket. Removed the wallet rate limit from
   issueNonce entirely; the IP limit in the route is the only limit on
   this endpoint.

2. The db-mock only checked nonce + used_at, not the full predicate set
   (chain_id, token, wallet, expires_at). The begin() did not
   snapshot/restore on rollback, and the metadata branch could not fail,
   so nonce rollback on a failed INSERT was untested. Fixed: the mock now
   checks all predicates, snapshots state before the transaction callback
   and restores on rejection, and supports a configurable metadata-write
   failure.

Updated tests: the nonce DoS test now uses the creator's wallet (the real
attack vector), added predicate-match tests (wrong wallet, wrong chain_id,
expired), and a transaction rollback test.
2026-09-07 16:27:41 -05:00
beardthelion c01409e376 test: add e2e test for edit nonce/rate-limit fix
Hits the real Next.js dev server with a real Postgres DB to verify the
fix end-to-end. Covers: non-creator rejection, creator nonce issuance,
10 non-creator requests not freezing the creator's budget (finding 1),
invalid signature not burning the nonce (finding 2), valid signature
consuming the nonce and writing metadata, nonce reuse rejection, 10
invalid-sig edit requests not freezing the creator's edit budget
(finding 1), non-creator edit rejection, and IP-keyed rate limiting.

RED-GREEN verified: 3 tests fail on the original code for the right
reasons (nonce burned on invalid sig, wallet frozen by attacker, wallet
bucket spent before creator check), all 17 pass after the fix.
2026-09-07 15:47:04 -05:00
Kevin Codex 3d8ce76840 fix(review 2): metadata key ownership, independent deploy secrets, locked repair scripts
- Metadata ownership is per (chain, launcher, meta_key), the thing the public URI is keyed by:
  the first registration under a key fixes its content; later rows under the same key must match
  exactly (the salt search) or get 409. Serialized with a transaction-scoped advisory lock so two
  concurrent first writes cannot both win (tested: 10/10 races → one 200, one 409). Reads prefer the
  launched row, then the FIRST registration, never a newer one.
- fly-secrets.sh reads every key independently from the secrets file (no RPC derivation), fails on
  missing required keys, supports --dry-run; verified against mocked files.
- rebuild-launch-totals.sql / rebuild-holders.sql run in one transaction holding SHARE locks on the
  raw event tables and EXCLUSIVE on the target table, so ingestion waits instead of racing (tested:
  an ingestion started mid-rebuild blocked 3s, then applied on top; totals consistent).
2026-09-07 12:51:08 +08:00
Kevin Codex 9a65ad4edf fix: signed-or-insert-only metadata, salt-search-stable URI, atomic trade/fee accounting; CI, contributing, security policy
- Metadata registration is INSERT-only for unsigned callers: the creator registers before broadcasting
  (salt still private); replaying the public launch params returns 409 and cannot overwrite image /
  description / website. Later changes go through the wallet-signed edit flow. (review finding 1)
- The on-chain metadataURI is keyed by a client-chosen meta_key instead of the salt, so findSalt's
  result stays valid for USDG / stock quotes. Proven on the live Robinhood factory: 17/100 deterministic
  USDG launches reverted with the old scheme, 0/100 with the new one. (finding 2)
- Swap and fee ingestion commit the event row and the launch totals in one transaction; a rebuild script
  recomputes totals from raw events if ever needed (production checked: no drift). (finding 3)
- Fresh checkout: npm run migrate reads DATABASE_URL from .env.local/.env; fly-secrets.sh is self-contained
  (FLY_APP, SECRETS_ENV); README run + deploy steps corrected.
- CI workflow (lint, typecheck, unit tests, production build; forge build + unit tests), CONTRIBUTING.md,
  SECURITY.md with private reporting.
2026-09-07 11:48:37 +08:00
Kevin Codex 312489a7b4 Initial commit
openlaunch — launch a token. Free. Open source. On Base or Robinhood Chain.
Contracts (LaunchFactory / LaunchLocker / LaunchToken, Foundry), the site (Next.js),
brand assets. No platform fee, liquidity locked forever.
2026-09-07 11:28:24 +08:00