# Appendix A · Database

Complete V1 Postgres schema. Only team-submitted tokens enter the database; there is no whole-chain scan. Names match the code. SQL identifiers and enum values below are retained from the source.

Conventions:

- Primary keys use `bigserial` unless specified otherwise. All tables have `created_at timestamptz not null default now()`. Mutable tables also have `updated_at`.
- EVM addresses are lowercase `text`, with a `check (addr ~ '^0x[0-9a-f]{40}$')` constraint.
- On-chain amounts use `numeric(78,0)` (raw units, like `uint256`). USD amounts use `numeric(20,6)`.
- All times are UTC.
- Migrations are managed by one tool (Drizzle, Prisma, or node-pg-migrate in the source) and versioned in the repository.

## Enumerated types

```sql
create type token_status as enum ('submitted', 'analyzed', 'validated', 'rejected', 'candidate', 'cto_live', 'sending', 'revived', 'dead_again');
create type pons_version as enum ('v1', 'v2');
create type token_phase as enum ('curve', 'graduated');
create type social_type as enum ('x', 'telegram', 'website', 'docs', 'whitepaper', 'github', 'dex', 'pons');
create type social_status as enum ('active', 'silent', 'dead', 'unknown');
create type cycle_status as enum ('draft', 'published', 'voting', 'closed', 'cancelled');
create type operation_status as enum ('pending', 'running', 'paused', 'done', 'failed', 'stopped');
create type step_status as enum ('waiting', 'queued', 'running', 'awaiting_validation', 'done', 'failed', 'skipped');
create type agent_name as enum ('scanner', 'designer', 'webdev', 'cm', 'tg_bot', 'builder');
create type validation_status as enum ('pending', 'approved', 'rejected', 'expired');
create type slice_status as enum ('planned', 'executing', 'done', 'skipped', 'failed');
create type buyback_target as enum ('cto_token', 'vivra');
create type ledger_type as enum ('fees_in', 'cto_fees_in', 'split', 'buyback_slice', 'vivra_burn', 'ai_spend', 'refill');
create type cto_request_status as enum ('draft', 'submitted', 'accepted', 'refused', 'recipient_pending', 'recipient_active');
create type verdict as enum ('revived', 'dead_again');
```

## Analyzed projects

```sql
create table tokens (
  address        text primary key,
  ticker         text not null,
  name           text not null,
  pons_version   pons_version not null,
  launched_at    timestamptz not null,
  launch_block   bigint not null,
  creator        text not null,
  fee_recipient  text not null,
  creator_tax_bps int not null default 0,
  pair_asset     text not null,              -- 'ETH' or address
  phase          token_phase not null,
  pool_id        text,                       -- v4 pool ID after graduation
  status         token_status not null default 'submitted',
  submitted_by   text,                       -- team wallet that submitted the address
  submitted_links jsonb not null default '{}', -- X, Telegram, docs, GitHub provided at submission
  analyzed_at    timestamptz,
  review_reason  text,                       -- reason for overriding rules or rejecting
  tracking       text not null default 'none', -- 'none' | 'candidate' | 'live' | 'hourly' | 'fees_daily'
  avatar_url     text,                       -- project profile picture, copied from X
  banner_url     text,                       -- project banner, copied from X
  category       text,                       -- e.g. 'Yield vaults', proposed by Scanner
  ath_price_usd  numeric(30,12),
  ath_market_cap_usd numeric(20,6),
  ath_at         timestamptz,
  died_at        timestamptz,                -- first Dead snapshot
  is_tech        boolean,                    -- null = not reviewed; proposed by Scanner, confirmed by team
  updated_at     timestamptz not null default now(),
  created_at     timestamptz not null default now()
);
create index on tokens (status);
create index on tokens (lower(ticker));

create table token_snapshots (
  id             bigserial primary key,
  token          text not null references tokens(address),
  taken_at       timestamptz not null,
  price_usd      numeric(30,12),
  market_cap_usd numeric(20,6),
  liquidity_usd  numeric(20,6),
  volume_24h_usd numeric(20,6),
  volume_7d_usd  numeric(20,6),
  holders        int,                        -- balance > dust threshold (parameter)
  top10_pct      numeric(6,3),
  dev_bag_pct    numeric(6,3),
  curve_progress_pct numeric(6,3),          -- null if graduated
  unique (token, taken_at)
);
create index on token_snapshots (token, taken_at desc);

create table dev_wallets (
  token          text not null references tokens(address),
  address        text not null,
  relation       text not null,              -- 'creator' | 'funded_by_creator' | 'funded_creator'
  last_tx_at     timestamptz,
  primary key (token, address)
);

create table contract_checks (
  token          text primary key references tokens(address),
  has_mint       boolean not null,
  can_sell       boolean not null,
  has_owner      boolean not null,
  owner_functions text[] not null default '{}',
  liquidity_locked boolean not null,
  checked_at     timestamptz not null
);

create table socials (
  id             bigserial primary key,
  token          text not null references tokens(address),
  type           social_type not null,
  url            text not null,
  metric         int,                        -- followers, members, commits...
  last_activity_at timestamptz,               -- latest account tweet, latest commit...
  account_exists boolean,                    -- deleted or suspended X account = false
  status         social_status not null default 'unknown',
  checked_at     timestamptz,
  unique (token, type)
);

create table dead_checks (
  token          text primary key references tokens(address),
  is_dead        boolean not null,
  volume_ok      boolean not null,           -- < $1,000/day over 7 days
  drawdown_ok    boolean not null,           -- drawdown >= 85%
  dev_inactive_ok boolean not null,          -- 14 days without a transaction
  target_ok      boolean not null,           -- Vivra target filters (high, market cap, X, docs, tech)
  safety_ok      boolean not null,           -- safety rules
  eligible       boolean not null,           -- target_ok and safety_ok
  exclusion_reasons text[] not null default '{}',
  computed_at    timestamptz not null
);

create table holder_positions (               -- for underwater holders and holding age
  token          text not null references tokens(address),
  wallet         text not null,
  balance        numeric(78,0) not null,
  bought_amount  numeric(78,0) not null default 0,
  avg_cost_usd   numeric(30,12),             -- average cost of purchases read during analysis
  first_held_at  timestamptz not null,
  updated_at     timestamptz not null default now(),
  primary key (token, wallet)
);

create table recovery_scores (
  id             bigserial primary key,
  token          text not null references tokens(address),
  score          int not null check (score between 0 and 100),
  components     jsonb not null,             -- criterion breakdown, see Appendix E
  weights_version int not null,
  computed_at    timestamptz not null
);
create index on recovery_scores (token, computed_at desc);

create table settings (
  key            text primary key,           -- e.g. 'dead.volume_max_usd'
  value          jsonb not null,
  updated_by     bigint,
  updated_at     timestamptz not null default now()
);
```

## Users, cycles, and voting

```sql
create table users (
  id             bigserial primary key,
  email_hash     bytea not null unique,      -- HMAC-SHA256(normalized email, server key), for uniqueness
  email_enc      bytea not null,             -- encrypted email (AES-GCM), never exposed by the API
  is_team        boolean not null default false,
  signup_ip_hash bytea,
  device_hash    bytea,
  blocked        boolean not null default false,
  created_at     timestamptz not null default now()
);

create table auth_codes (
  id             bigserial primary key,
  email_hash     bytea not null,
  code_hash      bytea not null,             -- hashed six-digit code or magic-link token
  expires_at     timestamptz not null,       -- 15 min
  attempts       int not null default 0,     -- blocked at 5
  used_at        timestamptz,
  created_at     timestamptz not null default now()
);
create index on auth_codes (email_hash, created_at desc);

create table sessions (
  id             uuid primary key default gen_random_uuid(),
  user_id        bigint not null references users(id),
  expires_at     timestamptz not null,       -- 30 days
  revoked_at     timestamptz,
  created_at     timestamptz not null default now()
);

create table cycles (
  id             bigserial primary key,
  number         int not null unique,
  status         cycle_status not null default 'draft',
  publish_at     timestamptz not null,
  vote_opens_at  timestamptz not null,
  vote_closes_at timestamptz not null,       -- = Day 0
  min_account_age_hours int not null default 24, -- 0 for cycle 1
  winner_candidate_id bigint,
  created_at     timestamptz not null default now(),
  check (vote_closes_at > vote_opens_at)
);

create table candidates (
  id             bigserial primary key,
  cycle_id       bigint not null references cycles(id),
  token          text not null references tokens(address),
  position       int not null check (position between 1 and 5),
  tagline        text not null,
  thesis_short   text not null,              -- one sentence
  thesis_long    text not null,              -- one paragraph
  reasons        text[] not null,            -- 3 reasons
  plan           jsonb not null,             -- identity, website, utility, X, Telegram
  risks          text[] not null,            -- 1–3
  scanner_report_id bigint,                  -- pre-report token_files.id
  recovery_score_id bigint references recovery_scores(id), -- frozen at publication
  published_at   timestamptz,
  unique (cycle_id, token),
  unique (cycle_id, position)
);

create table votes (
  cycle_id       bigint not null references cycles(id),
  user_id        bigint not null references users(id),
  candidate_id   bigint not null references candidates(id),
  updated_at     timestamptz not null default now(),
  created_at     timestamptz not null default now(),
  primary key (cycle_id, user_id)            -- one vote per email per cycle
);

create table supports (
  candidate_id   bigint not null references candidates(id),
  user_id        bigint not null references users(id),
  created_at     timestamptz not null default now(),
  primary key (candidate_id, user_id)
);
```

## Operations and agents

```sql
create table operations (
  id             bigserial primary key,
  cycle_id       bigint not null references cycles(id) unique,
  token          text not null references tokens(address),
  status         operation_status not null default 'pending',
  j0             timestamptz not null,       -- voting closes
  budget_usd     numeric(20,6) not null,     -- global cap
  spent_usd      numeric(20,6) not null default 0,
  verdict        verdict,
  verdict_at     timestamptz,
  handover_at    timestamptz,                -- Day 30
  created_at     timestamptz not null default now()
);

create table operation_steps (
  id             bigserial primary key,
  operation_id   bigint not null references operations(id),
  key            text not null,              -- e.g. 'designer.directions', see Appendix C
  agent          agent_name,                 -- null for non-AI steps
  due_at         timestamptz not null,       -- T+x
  status         step_status not null default 'waiting',
  attempts       int not null default 0,
  depends_on     text[] not null default '{}',
  output_file_id bigint,
  error          text,
  started_at     timestamptz,
  finished_at    timestamptz,
  unique (operation_id, key)
);

create table token_files (                   -- versioned token dossier
  id             bigserial primary key,
  operation_id   bigint references operations(id), -- null for a candidate pre-report
  token          text not null references tokens(address),
  version        int not null,
  section        text not null,              -- 'scan', 'identity', 'site', 'x', 'telegram', 'utility', 'verdict'
  content        jsonb not null,
  author         text not null,              -- agent or 'team:<id>'
  created_at     timestamptz not null default now(),
  unique (token, section, version)
);

create table assets (
  id             bigserial primary key,
  operation_id   bigint not null references operations(id),
  kind           text not null,              -- 'logo', 'pfp', 'banner_x', 'banner_tg', 'og', 'meme'
  url            text not null,              -- object storage
  meta           jsonb not null default '{}',
  created_at     timestamptz not null default now()
);

create table log_entries (                   -- public log
  id             bigserial primary key,
  operation_id   bigint not null references operations(id),
  step_id        bigint references operation_steps(id),
  agent          agent_name,
  kind           text not null,              -- 'llm_call', 'tool_call', 'step', 'validation', 'alert'
  summary        text not null,              -- one readable line displayed in the dapp
  model          text,
  input_tokens   int,
  output_tokens  int,
  cost_usd       numeric(20,6) not null default 0,
  payload        jsonb,                      -- detail, not public
  created_at     timestamptz not null default now()
);
create index on log_entries (operation_id, created_at);

create table validations (
  id             bigserial primary key,
  operation_id   bigint references operations(id),
  kind           text not null,              -- 'x_post', 'site', 'tg_pinned', 'asset', 'cto_dossier'
  ref_id         bigint,
  preview        jsonb not null,
  status         validation_status not null default 'pending',
  decided_by     bigint references users(id),
  decided_at     timestamptz,
  reason         text,                       -- rejection reason, returned to the agent
  created_at     timestamptz not null default now()
);

create table social_posts (
  id             bigserial primary key,
  account        text not null,              -- 'vivra' or CTO token address
  platform       social_type not null,
  template       text,
  body           text not null,
  media_urls     text[] not null default '{}',
  validation_id  bigint references validations(id),
  scheduled_at   timestamptz,
  published_at   timestamptz,
  external_id    text,
  created_at     timestamptz not null default now()
);
```

## Buyback, treasury, and Pons CTO

```sql
create table wallets (
  name           text primary key,           -- 'collecte', 'equipe', 'buyback_reserve', 'buyback_hot', 'ia', 'frais_cto'
  address        text not null unique,
  min_balance_wei numeric(78,0),
  max_balance_wei numeric(78,0)
);

create table buyback_plans (
  id             bigserial primary key,
  target         buyback_target not null,
  token          text not null,              -- CTO token or $VIVRA
  operation_id   bigint references operations(id),
  envelope_wei   numeric(78,0) not null,
  cap_wei        numeric(78,0),
  starts_at      timestamptz not null,
  ends_at        timestamptz not null,
  stopped_at     timestamptz,
  stop_reason    text,
  created_at     timestamptz not null default now()
);

create table buyback_slices (
  id             bigserial primary key,
  plan_id        bigint not null references buyback_plans(id),
  index          int not null,
  scheduled_at_enc bytea not null,           -- encrypted time, published after execution
  executed_at    timestamptz,
  status         slice_status not null default 'planned',
  amount_in_wei  numeric(78,0),
  min_out        numeric(78,0),
  amount_burned  numeric(78,0),
  liquidity_usd_at_exec numeric(20,6),
  slippage_bps   int,
  tx_hash        text,
  error          text,
  unique (plan_id, index)
);

create table ledger_entries (
  id             bigserial primary key,
  type           ledger_type not null,
  token          text,                       -- transferred token (null = ETH)
  amount         numeric(78,0) not null,
  amount_usd     numeric(20,6),
  from_wallet    text,
  to_wallet      text,
  operation_id   bigint references operations(id),
  tx_hash        text,
  occurred_at    timestamptz not null,
  created_at     timestamptz not null default now()
);
create index on ledger_entries (occurred_at desc);

create table cto_requests (
  id             bigserial primary key,
  operation_id   bigint not null references operations(id) unique,
  status         cto_request_status not null default 'draft',
  dossier        jsonb not null,             -- Pons form fields
  verification_tweet_url text,
  submitted_by   bigint references users(id),
  submitted_at   timestamptz,
  pending_recipient_seen_at timestamptz,
  recipient_active_at timestamptz,
  updated_at     timestamptz not null default now()
);

create table team_wallets (                  -- back-office access
  address        text primary key,
  label          text not null,
  active         boolean not null default true
);

create table admin_audit (
  id             bigserial primary key,
  actor          text not null,              -- team address
  action         text not null,
  target         text,
  detail         jsonb,
  created_at     timestamptz not null default now()
);
```

## Rules to enforce in code

- Reject a vote if the cycle is not `voting`, `now() > vote_closes_at`, the user is `is_team` or `blocked`, or the account is younger than `min_account_age_hours`.
- Token status changes follow the Token lifecycle table. Reject all other transitions.
- A candidate’s `recovery_score_id` is frozen at dossier publication: the displayed score does not change during voting. The live score remains visible beside it in a separate block.
- Never delete rows from `log_entries`, `ledger_entries`, `buyback_slices`, or `admin_audit`.
