vivraDocumentationV1Comprendre le vote

M1 · Project analysis (T10–T16)

Exit gate: the team pastes a testnet token address in the back-office and gets, in under 10 minutes, a complete analysis: on-chain facts, target checks, safety checks, socials and docs, recovery score. Nothing scans the whole chain.


T10 · Pons token reader ⚠️ Pons#

Goal Read everything Vivra needs about ONE Pons token from the chain.

Depends on T02

Read first annexe-contrats.md, analyse-des-projets.md (Ce que le Scanner lit), token-vivra-et-tresorerie.md

Build packages/chain/src/abi/{ponsFactoryV2,ponsEscrow,ponsMemeHook,ponsCurve}.json, packages/chain/src/read/pons.ts, packages/chain/src/read/contractCheck.ts

Steps

  1. Fetch verified ABIs from the Robinhood Chain explorer for the factory, escrow and hook addresses of annexe D, and for one example curve. Save as JSON. If a contract is not verified: stop and ask.
  2. readTokenInfo(address) → { ponsVersion, launchedAt, launchBlock, creator, feeRecipient, pendingFeeRecipient, creatorTaxBps, pairAsset, phase: 'curve'|'graduated', poolId?, curveProgressPct? } using factory events/views (TokenLaunched, pendingCreatorFeeRecipient, …). Exact function names come from the ABI; map them in one place.
  3. contractCheck(address) → { hasMint, canSell, hasOwner, ownerFunctions[], liquidityLocked }:
    • hasMint: ABI or bytecode selector scan for mint-like selectors (mint, _mint exposed) on the token.
    • canSell: simulateContract of a small sell on the pool/curve from a funded fork account or eth_call with state override; failure reasons recorded.
    • ownerFunctions: list of non-view functions guarded by owner (ABI names containing pause, blacklist, setFee, setTax, mint…).
    • liquidityLocked: graduated Pons v2 → true by design (pool owned by hook); curve → true.
  4. quoteBuy(token, amountInWei) → expected tokens out + price impact %, used by the recovery score (buyback impact).

Done when

  • Fixture test: readTokenInfo on recorded RPC responses returns the expected object for one graduated and one curve token
  • TESTNET=1 test: readTokenInfo on a real testnet Pons token succeeds
  • contractCheck flags a fixture ERC-20 with an exposed mint(address,uint256) as hasMint: true
  • quoteBuy returns a positive impact that grows with amountIn (property test with 3 amounts)

Out of scope History, holders, price series (T11).


T11 · Token history loader#

Goal Build holders, positions, prices and volumes for ONE token from its own logs since launch.

Depends on T01, T02, T10

Read first analyse-des-projets.md, annexe-base-de-donnees.md (token_snapshots, holder_positions), annexe-score-de-reprise.md (prix de revient, holders sous l'eau)

Build packages/chain/src/read/history.ts, packages/core/src/positions.ts, apps/workers/src/jobs/projects.loadHistory.ts

Steps

  1. Fetch Transfer logs of the token and swap/trade logs of its curve or pool from launchBlock to head with getLogsChunked.
  2. In packages/core/positions.ts (pure): replay events to compute per-wallet balance, first-held time, bought amount and average cost in USD (ETH price per block from a provided price function), excluding pools, burn address and the curve.
  3. Compute and store: holder_positions rows; daily token_snapshots for the last 120 days (price, market cap, liquidity, volume 24h/7d, holders, top10 %, dev bag %); ATH price and ATH market cap; died_at (first day the Dead rules are all true).
  4. Dust threshold, excluded addresses and ETH/USD source are settings (history.dustRaw, history.excluded, prices.ethUsdSource).

Done when

  • Unit test: replaying a 6-event fixture gives exact balances, first-held times and avg costs
  • Unit test: pools, burn and curve addresses are never counted as holders
  • Integration test on a recorded log fixture of ~10k events: runs in < 30s and writes snapshots
  • TESTNET=1: loading a real testnet token completes and holders > 0

Out of scope Continuous tracking (T41/T31 use this code later).


T12 · Dev wallet tracer#

Goal Find the creator's linked wallets and their share of supply.

Depends on T10, T11

Read first fiche-scanner.md, annexe-agents-prompts-outils.md (chain_trace_funding)

Build packages/chain/src/read/funding.ts, packages/core/src/devBag.ts

Steps

  1. traceFunding(wallet, depth=2): native ETH transfers in/out of the wallet (via explorer API txlist if available, else eth_getBlock scanning is NOT allowed — stop and ask). Return edges {from,to,value,block}.
  2. Linked wallets = creator + wallets funded by the creator + wallets that funded the creator, depth 2, excluding known exchanges/bridges (setting funding.ignore).
  3. devBag(positions, linked) → % of supply held by creator + linked wallets. Store dev_wallets.

Done when

  • Unit test on a 7-node fixture graph returns exactly the expected linked set
  • An exchange address in funding.ignore is never followed
  • devBag on fixture positions = expected % to 3 decimals

Out of scope Labelling insiders by behaviour (later).


T13 · Socials and docs checker#

Goal For ONE project: X account status and stats, Telegram size, website/docs/whitepaper/GitHub availability, plus avatar and banner.

Depends on T01

Read first analyse-des-projets.md (cible, données lues), annexe-score-de-reprise.md (pilier 4)

Build packages/core/src/socials.ts (types + rules), apps/workers/src/lib/{xApi.ts,http.ts,github.ts,telegram.ts}, apps/workers/src/jobs/projects.checkSocials.ts

Steps

  1. Inputs: links provided at submission (tokens.submitted_links) + links found in token metadata / website.
  2. X (API v2, app token): user lookup by handle → exists, suspended, followers, last tweet date (/users/:id/tweets?max_results=5), profile image + banner URLs. Count mentions of the ticker from other accounts in 7 days (recent search). Respect rate limits; cache 24h.
  3. HTTP checks with a 10s timeout and a real UA: site, docs (GitBook/Notion/docs.*), whitepaper (PDF) → status active|dead|unknown.
  4. GitHub API: repo exists, public, commit count (default branch), last commit date.
  5. Telegram: member count from the public t.me/<group> page meta when available; activity only if our bot is in the group.
  6. Download avatar and banner and upload to object storage (S3/R2); store URLs in tokens.avatar_url, tokens.banner_url.
  7. Write socials rows (one per type) with metric, last_activity_at, account_exists, status.

Done when

  • Unit tests on recorded API fixtures: deleted X account → account_exists=false; silent 40 days → last_activity_at 40 days ago
  • A 404 docs URL → status='dead'; a 200 → active
  • Avatar is re-hosted (URL points to our bucket, not to X)
  • No API key appears in logs (grep test on captured logs)

Out of scope Posting anything (M2/M4).


T14 · Dead, target and safety evaluation#

Goal Pure functions that turn the collected data into dead_checks with reasons, using settings.

Depends on T11, T12, T13

Read first analyse-des-projets.md (Statut Dead, cible, règles de sécurité)

Build packages/core/src/evaluate.ts, packages/core/src/settings.ts (registry with defaults), tests

Steps

  1. evaluateDead(snapshots, devActivity, settings) → { isDead, volumeOk, drawdownOk, devInactiveOk } (avg volume < $1,000/day over 7d, drawdown ≥ 85% from ATH, no creator tx for 14 days).
  2. evaluateTarget(token, snapshots, socials, settings) → each rule pass/fail: ATH mcap ≥ $500K, current mcap $15K–$50K, X exists, X silent ≥ 30 days, ≥ 1 docs source active, is_tech confirmed (null = "pending review").
  3. evaluateSafety(contractCheck, positions, devBag, settings) → holders > 300, no mint, can sell, liquidity locked, dev bag < 5%; bonuses (v2, creator tax > 0) reported but not blocking.
  4. Combine: eligible = target_ok && safety_ok; exclusion_reasons = human strings for every failed rule (shown in back-office and on the public dossier if published anyway).
  5. Settings registry: key, type, default, description. Defaults exactly from the spec.

Done when

  • One test per rule, pass and fail, at the threshold boundaries (e.g. 15,000 and 14,999.99)
  • A token with is_tech = null is target_ok = false with reason "tech status pending review"
  • Changing a setting value changes the result without code change (test)

Out of scope Recovery score (T15).


T15 · Recovery score#

Goal recoveryScore(input, weights) exactly as annexe E, with stored components.

Depends on T14

Read first annexe-score-de-reprise.md (entire page)

Build packages/core/src/recoveryScore.ts, packages/core/src/recoveryScore.test.ts, apps/workers/src/jobs/projects.score.ts

Steps

  1. Implement the 5 pillars and every criterion with the formulas, caps and log scales of annexe E. Missing data → 0 points + missing: true in components.
  2. Return { score: number (rounded int), pillars: [{ key, points, max, criteria: [{ key, raw, ratio, points, missing }] }], weightsVersion }.
  3. Weights and bounds from settings recovery.*; bumping any → new weights_version.
  4. Job writes recovery_scores after T11–T14 for the token.

Done when

  • The worked example of annexe E returns 68 with the exact per-pillar values (13.6, 15.5, 15.2, 12.1, 11.7) ± 0.1
  • Edge cases tested: 0 holders, all data missing (score 0, no NaN), values above every bound (score 100)
  • Changing one weight produces a new weights_version and does not alter stored frozen scores

Out of scope Display (T42).


T16 · Submit and review in the back-office#

Goal The team submits an address, watches the analysis run, then validates or rejects.

Depends on T03, T04, T10–T15

Read first analyse-des-projets.md (Le parcours), annexe-api.md (Back-office), cycles-candidats-et-vote.md (Back-office des cycles)

Build apps/api/src/routes/admin/{auth,projects}.ts, apps/admin/app/(auth)/login/page.tsx, apps/admin/app/projects/{page.tsx,[address]/page.tsx}, apps/workers/src/jobs/projects.analyze.ts (pipeline)

Steps

  1. Admin auth: SIWE (/admin/auth/nonce, /admin/auth/verify), only team_wallets.active addresses; cookie vivra_admin 12h; every write logs admin_audit.
  2. POST /admin/projects { address, links? } → creates token submitted, enqueues projects.analyze (T10 → T11 → T12 → T13 → T14 → T15, each step updating a progress field), status → analyzed.
  3. GET /admin/projects?status= list with score, eligibility, reasons. GET /admin/projects/:address full analysis.
  4. POST /admin/projects/:address/review { decision: validate|reject|rerun, reason? }; reject requires a reason; validating with a failed blocking rule requires a reason too.
  5. Admin UI: submit form, list with filters, detail page showing every check (green / red / "not found"), score breakdown, socials with links, avatar/banner, and the three action buttons. Progress bar while analysis runs (poll every 3s).

Done when

  • Non-whitelisted wallet cannot log in (test)
  • Submitting a testnet address shows a full analysis in < 10 min (manual, record the time in the PR)
  • Rejecting without a reason → 400; rejected tokens never appear in any public API route (test)
  • Every admin write creates an admin_audit row (test)

Out of scope Building cycles (T41), Scanner AI write-up (T24).

↑ ↓ pour parcourir · Entrée pour ouvrirVIVRA DOCS