feat: wire WalletRepository and FundingRepository into live pipeline (#90)

The live pipeline was receiving trade data and populating Redis caches but
never persisting wallet profiles or funding transfers to Postgres. This
wires the repositories into the _on_trade flow: when a fresh wallet signal
is detected, the wallet profile is upserted to wallet_profiles and the
funding chain is traced and inserted into funding_transfers. Existing
Redis caching behavior is preserved.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Selamy
2026-04-01 09:21:37 -04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 232680a760
commit 1f4fc607a7
2 changed files with 412 additions and 0 deletions
@@ -29,7 +29,14 @@ from polymarket_insider_tracker.ingestor.metadata_sync import MarketMetadataSync
from polymarket_insider_tracker.ingestor.websocket import TradeStreamHandler
from polymarket_insider_tracker.profiler.analyzer import WalletAnalyzer
from polymarket_insider_tracker.profiler.chain import PolygonClient
from polymarket_insider_tracker.profiler.funding import FundingTracer
from polymarket_insider_tracker.storage.database import DatabaseManager
from polymarket_insider_tracker.storage.repos import (
FundingRepository,
FundingTransferDTO,
WalletProfileDTO,
WalletRepository,
)
if TYPE_CHECKING:
from typing import Any
@@ -120,6 +127,7 @@ class Pipeline:
self._alert_formatter: AlertFormatter | None = None
self._alert_dispatcher: AlertDispatcher | None = None
self._trade_stream: TradeStreamHandler | None = None
self._funding_tracer: FundingTracer | None = None
# Synchronization
self._stop_event: asyncio.Event | None = None
@@ -233,6 +241,10 @@ class Pipeline:
redis=self._redis,
)
# Initialize Funding Tracer
logger.debug("Initializing funding tracer...")
self._funding_tracer = FundingTracer(self._polygon_client)
# Initialize Detectors
logger.debug("Initializing detectors...")
self._fresh_wallet_detector = FreshWalletDetector(self._wallet_analyzer)
@@ -365,6 +377,10 @@ class Pipeline:
self._detect_size_anomaly(trade),
)
# Persist wallet profile and funding data when a fresh wallet is detected
if fresh_signal is not None:
await self._persist_wallet_and_funding(fresh_signal)
# Bundle signals
bundle = SignalBundle(
trade_event=trade,
@@ -382,6 +398,63 @@ class Pipeline:
self._stats.errors += 1
self._stats.last_error = str(e)
async def _persist_wallet_and_funding(self, signal: FreshWalletSignal) -> None:
"""Persist wallet profile and funding transfers to Postgres.
Called when a fresh wallet signal is detected. Upserts the wallet
profile and traces/inserts any funding transfers found on-chain.
Args:
signal: The fresh wallet signal containing the wallet profile.
"""
if not self._db_manager:
return
profile = signal.wallet_profile
address = profile.address
try:
async with self._db_manager.get_async_session() as session:
# Persist wallet profile
wallet_repo = WalletRepository(session)
dto = WalletProfileDTO(
address=address,
nonce=profile.nonce,
first_seen_at=profile.first_seen,
is_fresh=profile.is_fresh,
matic_balance=profile.matic_balance,
usdc_balance=profile.usdc_balance,
analyzed_at=profile.analyzed_at,
)
await wallet_repo.upsert(dto)
# Trace and persist funding transfers
if self._funding_tracer:
chain = await self._funding_tracer.trace(address)
if chain.chain:
funding_repo = FundingRepository(session)
funding_dtos = [
FundingTransferDTO(
from_address=t.from_address,
to_address=t.to_address,
amount=t.amount,
token=t.token,
tx_hash=t.tx_hash,
block_number=t.block_number,
timestamp=t.timestamp,
)
for t in chain.chain
]
await funding_repo.insert_many(funding_dtos)
logger.debug(
"Persisted wallet profile and %d funding transfers for %s",
len(chain.chain) if self._funding_tracer and chain.chain else 0,
address[:10] + "...",
)
except Exception as e:
logger.warning("Failed to persist wallet/funding data for %s: %s", address, e)
async def _detect_fresh_wallet(self, trade: TradeEvent) -> FreshWalletSignal | None:
"""Run fresh wallet detection."""
if not self._fresh_wallet_detector: