5fb1d52026
Big-picture changes since b941223:
KOL pipeline (new) — Substack/podcast/blog RSS → AI ticker extraction →
on-chain wallet diff → talks-vs-trades divergence detection. Daily polls,
19 feeds, divergence emits Post + Telegram fan-out.
Telegram push (new) — walletless free tier + wallet-linked Pro upgrade,
in-bot preference commands (/trump /btc /funding /kol /conf /quiet),
signed-envelope API for dashboard. Disconnect-wallet keeps free
subscription.
BTC funding-rate reversal scanner (new) — hourly cron, 30d cumulative
funding threshold + mean-revert + 7d price confirm, emits via
/api/signals/ingest. BTC bottom-reversal scanner promoted to System 2.
WS broadcast rewrite — per-client send timeout + parallel fan-out
(asyncio.gather). Fixes "Binance WS no close frame" reconnect storms +
APScheduler 11-min job misses, both caused by one slow client stalling
the kline loop.
Error visibility — three silent-error sites (trumpstruth/truth_social
fetchers, funding_reversal scanner) now include exception type name so
httpx ConnectError-style empty-message errors stop logging blank lines.
Telegram bot loop now classifies ReadTimeout vs network vs unknown +
logger.exception for the unknown bucket.
Security hygiene — trumpsignal.db untracked from git (held subscriber
wallets + encrypted HL keys + 22 bot trades); .gitignore now blocks
*.db/.next/backups. CORS only allows FRONTEND_URL in production.
New ops scripts —
- scripts/preflight.py: env/DB/Telegram/AI auth verification gate
- scripts/backup_db.sh: cron-friendly daily DB backup (SQLite + Postgres)
- scripts/seed_kol_wallets.py: idempotent KOL on-chain wallet seeder
15 new Alembic migrations (007-021) covering convex strategy fields,
phase-1 safety, two-system frozen exits, invalidation prices, dynamic
SYS2 leverage, staged de-risk + pyramiding, peak gain tracking, risk
mode, auto-trade + grow flags, KOL module, KOL on-chain, KOL divergence,
Telegram bindings + walletless.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
163 lines
6.9 KiB
Python
Executable File
163 lines
6.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Seed the kol_wallets table with publicly attributed on-chain addresses.
|
|
|
|
Why this exists:
|
|
The talks-vs-trades divergence module can only fire on KOLs whose wallets
|
|
we know. As of 2026-05-24 only 3 KOLs had wallets configured, which is
|
|
why we had ~3 divergence detections across the whole dataset.
|
|
|
|
Adding a new wallet here REQUIRES:
|
|
1. Public attribution — Arkham label, the KOL's own X bio, a public
|
|
investigation by ZachXBT / Inspex / similar, or the KOL's ENS clearly
|
|
visible in transactions.
|
|
2. The `handle` field MUST exactly match a `handle` value in
|
|
app/services/kol_substack.py KOL_FEEDS — otherwise the divergence
|
|
scanner cannot match "post by handle X" against "wallet activity by
|
|
handle X".
|
|
3. The `source_url` must link to that public attestation. Don't add
|
|
speculative addresses, even if "everyone knows" — misattribution
|
|
damages both the KOL and our credibility.
|
|
|
|
Idempotent: runs UPSERT-style. Existing rows for (handle, address) are
|
|
preserved; only new ones are inserted.
|
|
|
|
Usage:
|
|
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
|
venv/bin/python scripts/seed_kol_wallets.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from sqlalchemy import select
|
|
from app.database import AsyncSessionLocal
|
|
from app.models import KolWallet
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Verified seed list.
|
|
#
|
|
# Each entry MUST have a working source_url. If you can't link to a public
|
|
# attestation, DON'T add it.
|
|
#
|
|
# To find more candidates yourself:
|
|
# • https://platform.arkhamintelligence.com/ — search by handle, click
|
|
# "labels" tab. Labels prefixed with "Entity:" are Arkham-verified.
|
|
# • https://etherscan.io/labelcloud — Etherscan public label registry.
|
|
# • ZachXBT investigations on X — he posts the underlying tx evidence.
|
|
# • Many KOLs put their address in their X bio or pinned tweet.
|
|
#
|
|
# When the KOL's handle in KOL_FEEDS doesn't match the on-chain handle here,
|
|
# add it under the handle that's IN KOL_FEEDS — divergence join is by handle.
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
SEED_WALLETS: list[dict] = [
|
|
# ── Already in DB (kept here so the script is self-documenting) ──────
|
|
{
|
|
"handle": "cryptohayes",
|
|
"chain": "ethereum",
|
|
"address": "0xa86e3d1c80a750a310b484fb9bdc470753a7506f",
|
|
"label": "Arthur Hayes (main)",
|
|
"source_url": "https://etherscan.io/address/0xa86e3d1c80a750a310b484fb9bdc470753a7506f",
|
|
},
|
|
{
|
|
"handle": "cryptohayes",
|
|
"chain": "ethereum",
|
|
"address": "0x534a0076fb7c2b1f83fa21497429ad7ad3bd7587",
|
|
"label": "Arthur Hayes (secondary)",
|
|
"source_url": "https://etherscan.io/address/0x534a0076fb7c2b1f83fa21497429ad7ad3bd7587",
|
|
},
|
|
{
|
|
"handle": "andrewkang",
|
|
"chain": "ethereum",
|
|
"address": "0xff3879b8a363aed92a6eaba8f61f1a96a9ec3c1e",
|
|
"label": "Andrew Kang (beanwhale.eth)",
|
|
"source_url": "https://etherscan.io/address/0xff3879b8a363aed92a6eaba8f61f1a96a9ec3c1e",
|
|
},
|
|
{
|
|
"handle": "murad",
|
|
"chain": "ethereum",
|
|
"address": "0x93f019699ef400df7dc3477dbb6400ed9445a657",
|
|
"label": "Murad Mahmudov (via ZachXBT investigation)",
|
|
"source_url": "https://www.blocmates.com/news-posts/24million-in-memecoins-zachxbt-unveils-murad-mahmudov-s-alleged-wallets",
|
|
},
|
|
|
|
# ── Add new VERIFIED entries below this line ─────────────────────────
|
|
#
|
|
# Template — copy, replace fields, push only after verifying source_url:
|
|
#
|
|
# {
|
|
# "handle": "<matches handle in KOL_FEEDS>",
|
|
# "chain": "ethereum", # or "solana", "base", "arbitrum"
|
|
# "address": "0x...",
|
|
# "label": "<KOL display name (annotation)>",
|
|
# "source_url": "<public link proving attribution>",
|
|
# },
|
|
#
|
|
# Candidates to research (NOT seeded — verify before adding):
|
|
#
|
|
# • niccarter — Nic Carter has spoken openly about his wallet
|
|
# history on podcasts; check Coin Center filings.
|
|
# • pomp — Pompliano has a public BTC-only treasury; less
|
|
# useful for ETH-side divergence detection.
|
|
# • dragonfly — Dragonfly Capital portfolio wallets often
|
|
# labeled on Arkham as "Dragonfly Fund".
|
|
# • placeholder — Placeholder VC fund wallets per their public
|
|
# investment disclosures.
|
|
# • eugene — Eugene Ng Ah Sio — verify before adding.
|
|
#
|
|
# Also worth tracking even if not in KOL_FEEDS (would need to add the
|
|
# corresponding feed entry first or they'll never have post-side data):
|
|
#
|
|
# • justinsuntron — Tron founder, very active ETH trader, well-labeled.
|
|
# • cobie — Jordan Fish, address known via Arkham labels.
|
|
# • gcr / sam — anon traders, no reliably verifiable address.
|
|
]
|
|
|
|
|
|
async def main() -> int:
|
|
inserted = 0
|
|
skipped = 0
|
|
async with AsyncSessionLocal() as session:
|
|
for entry in SEED_WALLETS:
|
|
# Idempotency: skip if (handle, address) already present.
|
|
existing = await session.execute(
|
|
select(KolWallet).where(
|
|
KolWallet.handle == entry["handle"],
|
|
KolWallet.address == entry["address"].lower(),
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
skipped += 1
|
|
continue
|
|
row = KolWallet(
|
|
handle=entry["handle"],
|
|
chain=entry["chain"],
|
|
address=entry["address"].lower(),
|
|
label=entry["label"],
|
|
source_url=entry["source_url"],
|
|
active=True,
|
|
added_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
|
)
|
|
session.add(row)
|
|
inserted += 1
|
|
print(f" + {entry['handle']:18s} {entry['address']} ({entry['label']})")
|
|
await session.commit()
|
|
|
|
print()
|
|
print(f"Inserted {inserted} wallets, skipped {skipped} existing.")
|
|
print()
|
|
print("Next step: edit SEED_WALLETS above and re-run. Each new wallet")
|
|
print("MUST cite a public attestation in source_url — see the docstring.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(asyncio.run(main()))
|