Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerIntelligence4.14 Wallet Flow Classifier

4.14 Wallet Flow Classifier

Intelligence Signal Service Read-only PLANNED Spec started capital · Indirect P2 · Data normalisation pending stub

Score on-chain wallets on historical edge over a long window — emitted as a feature, never as a copy-trade signal.

v3 readiness

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerIntelligence  Intelligence
Bot classSignal Service
AuthorityRead-only
StatusPLANNED
ReadinessSpec started
Runs before
Runs after
Applies to
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

2. Purpose

Score on-chain wallets on historical edge over a long window — emitted as a feature, never as a copy-trade signal.

3. Why This Bot Matters

  • Wallet behaviour treated as a copy-trade signal

    Naively echoing 'smart money' wallets is the textbook way to lose money — the apparent edge is survivor bias, the real wallets front-run, and the implementation amounts to copy-trading. Emitting a feature with explicit non-trigger guidance prevents that misuse.

  • Strategies build their own wallet scorers, inconsistently

    Without a shared classifier, every strategy author writes a slightly different version. The library ends up with three inconsistent scores, none of them auditable.

  • No long-window context for short-term flows

    A wallet's 24-hour activity is meaningless without its multi-month history. Centralising the long-window score gives all consumers the same baseline and prevents recency-driven misclassifications.

  • Compliance unable to flag suspicious counterparties

    Sanctions and abuse reviews need a structured score per wallet. The classifier provides one consistent input that ComplianceGate and Governance can both read.

4. Required Polymarket Inputs

InputSourceRequired?Use
CTFExchangeV2 OrderFilled events from Polygon on-chainonchainYesPrimary source of wallet fill data for flow classification.
Historical wallet fill patternsdataNoSupplement recent fills with historical context for better classification accuracy.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesSuppress all emissions when KillSwitch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
institutional_threshold_pusd1000050001000Minimum pUSD fill volume per block for a wallet to be classified as institutional.
poll_interval_s123060Seconds between Polygon RPC polls for new OrderFilled events. Aligns with Polygon block time.

7. Detailed Parameter Instructions

institutional_threshold_pusd

What it means

Minimum pUSD fill volume per block for a wallet to be classified as institutional.

Default

{ "institutional_threshold_pusd": 10000 }

Why this default matters

10000 pUSD per block distinguishes institutional flow from retail with high confidence.

Threshold logic

ConditionAction
volume >= 10000 pUSDLabel institutional
1000–10000 pUSDWARN — borderline; label retail unless arbitrage pattern
< 1000 pUSDLabel retail

Developer check

if (total_pusd >= p.institutional_threshold_pusd.default) label = 'institutional';

User-facing English

Large wallet flows are classified separately to help strategies assess market participant composition.

poll_interval_s

What it means

Seconds between Polygon RPC polls for new OrderFilled events. Aligns with Polygon block time.

Default

{ "poll_interval_s": 12 }

Why this default matters

12 s matches the Polygon block interval, ensuring every block is checked.

Threshold logic

ConditionAction
interval <= 12 sNormal — one poll per block
12–30 sWARN — may miss fills in high-throughput blocks
> 60 sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.poll_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

On-chain fills are checked every Polygon block to ensure no wallet flow is missed.

8. Default Configuration

{
  "bot_id": "intel.wallet-flow-classifier",
  "version": "0.1.0",
  "mode": "planned",
  "defaults": {
    "institutional_threshold_pusd": 10000,
    "poll_interval_s": 12
  },
  "locked": {
    "institutional_threshold_pusd": {
      "min": 1000
    },
    "poll_interval_s": {
      "max": 60
    }
  }
}

9. Implementation Flow

— not yet authored —

10. Reference Implementation

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.

FUNCTION classifyWalletFlow(block_number):
  // 0. KillSwitch check
  IF FETCH internal.killswitch.status == ACTIVE:
    RETURN

  // 1. Fetch fills for block range
  fills = onchain.getLogs(CTFExchangeV2, 'OrderFilled', block_number)
  IF len(fills) == 0:
    RETURN

  // 2. Group fills by wallet address
  wallet_fills = groupBy(fills, 'makerAddress')

  // 3. Classify each wallet
  FOR wallet, wfills IN wallet_fills:
    total_pusd = sum(toPusdUnits(f.fillAmount) for f in wfills)
    IF total_pusd < retail_threshold:
      label = 'retail'
    ELIF total_pusd > institutional_threshold:
      label = 'institutional'
    ELIF isArbitragePattern(wfills):
      label = 'arbitrage'
    ELSE:
      label = 'unknown'

    // 4. Emit ObservationReport
    EMIT ObservationReport {
      report_id: gen_id(),
      kind: 'ObservationReport',
      wallet_address: wallet,
      flow_label: label,
      total_pusd: total_pusd,
      block_number: block_number,
      emitted_at_ms: now_ms()
    }

SDK calls used

  • onchain.getLogs(CTFExchangeV2, 'OrderFilled', block_number)
  • data.GET('/wallets/<address>/history')
  • toPusdUnits(fillAmount)

Complexity: O(F) per block where F = fills in block for tracked wallets

11. Wire Examples

Input — what arrives on the wire

{
  "label": "CTFExchangeV2 OrderFilled log for wallet flow classification",
  "source": "onchain",
  "payload": {
    "makerAddress": "0xA1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0",
    "fillAmount": "50000000000",
    "tokenId": "99887766",
    "block_number": 72346100,
    "timestamp_ms": 1746703000000
  }
}

Output — what the bot emits

{
  "label": "ObservationReport — wallet classified as institutional",
  "payload": {
    "report_id": "rep_wfc_0xa1b2_1746703000000",
    "trace_id": "trc_0xbeef0102030405060715",
    "bot_id": "intel.wallet-flow-classifier",
    "kind": "ObservationReport",
    "wallet_address": "0xA1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0",
    "flow_label": "institutional",
    "total_pusd": 50000,
    "block_number": 72346100,
    "emitted_at_ms": 1746703010000
  }
}

12. Decision Logic

APPROVE

— not yet authored —

RESHAPE_REQUIRED

— not yet authored —

REJECT

— not yet authored —

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "report_id": "rep_wfc_0xa1b2_1746703000000",
  "trace_id": "trc_0xbeef0102030405060715",
  "bot_id": "intel.wallet-flow-classifier",
  "kind": "ObservationReport",
  "wallet_address": "0xA1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0",
  "flow_label": "institutional",
  "total_pusd": 50000,
  "block_number": 72346100,
  "warnings": [],
  "emitted_at_ms": 1746703010000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
WALLETFLOWCLASSIFIER_LARGE_FLOWINFOWallet classified as institutional based on pUSD volume exceeding institutional_threshold.Emit ObservationReport with flow_label=institutional; strategies may treat as high-signal.A large institutional wallet flow was detected on this market.
WALLETFLOWCLASSIFIER_ARBITRAGE_PATTERNWARNWallet fill pattern matches arbitrage heuristic (multi-market simultaneous fills).Emit ObservationReport with flow_label=arbitrage; strategies apply reduced directional weight.
STALE_DATAWARNRPC provider unresponsive; on-chain fills may be stale.Skip classification for this block; alert on-call if persists.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch active; all WalletFlowClassifier emissions suppressed.Continue classification internally; suppress ObservationReport emissions.Wallet flow classification paused while trading is suspended system-wide.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_intel_walletflowclassifier_observations_emitted_totalcountercountflow_labelObservationReports emitted per flow label (retail, institutional, arbitrage, unknown).
polytraders_intel_walletflowclassifier_total_pusd_classifiedcounterpUSDflow_labelTotal pUSD volume classified per flow label.
polytraders_intel_walletflowclassifier_rpc_block_lag_sgaugesecondsAge of the most recently processed Polygon block.

Alerts

AlertConditionSeverityRunbook
WalletFlowClassifierRPCDownpolytraders_intel_walletflowclassifier_rpc_block_lag_s > 60page#runbook-walletflowclassifier-rpc-down
WalletFlowClassifierHighArbitrageRaterate(polytraders_intel_walletflowclassifier_observations_emitted_total{flow_label='arbitrage'}[10m]) > 5warn#runbook-walletflowclassifier-high-arbitrage

Dashboards

  • Grafana — Intelligence / WalletFlowClassifier volume by flow label

16. Developer Reporting

{
  "bot_id": "intel.wallet-flow-classifier",
  "block_number": 72346100,
  "fills_processed": 12,
  "wallets_classified": 5,
  "institutional_count": 1,
  "arbitrage_count": 0,
  "retail_count": 4,
  "killswitch_active": false
}

17. Plain-English Reporting

SituationUser-facing explanation
Strategy adjusted weighting after institutional flow detectedA large wallet made significant fills on this market. The system treats high-volume wallet flows as a market signal when sizing positions.
Wallet flow classified as arbitrageFill patterns consistent with arbitrage trading were detected. Arbitrage flow is treated as less directionally informative.

18. Failure-Mode Block

main_failure_modePolygon RPC outage causes WalletFlowClassifier to miss fills for one or more blocks, resulting in gaps in wallet flow classification during high-activity periods.
false_positive_riskA single wallet conducting coordinated multi-leg arbitrage is classified as institutional by volume alone, overstating the directional signal from that wallet.
false_negative_riskAn institutional actor using many small wallets below the institutional_threshold evades classification and is labelled retail, understating the informed flow.
safe_fallbackIf RPC is unavailable for > 2x poll_interval_s, emit STALE_DATA WARN and halt new classifications. Retain last classification labels for each wallet in Redis until fresh data is available.
required_dependenciesPolygon RPC (CTFExchangeV2 OrderFilled events), KillSwitch active flag, Redis for wallet state storage

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
RPC_OUTAGEBlock TCP to Polygon RPC for 30 sAutomatic on RPC recovery; classification resumes from next available block
INSTITUTIONAL_FLOWInject mock fills of 50000 pUSD for a single wallet in one blockAutomatic; no action required
KILL_SWITCH_ONSet killswitch.active=true during active classificationAutomatic on KillSwitch reset

20. State & Persistence

Cold-start recovery

On cold start, re-classify from next block; historical backfill not required.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded per-block processing
Max in-flight1
Idempotency keywallet_address + block_number
Per-call timeout (ms)10000
Backpressure strategydrop-after-buffer — skip block if previous block still processing at next poll
Locking / mutual exclusionRedis SETNX on wallet + block_number to prevent duplicate classification

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchSuppress emissions when KillSwitch is active.
intel.onchainwatcherUses OnChainWatcher as upstream for initial wallet detection.

Emits to (downstream consumers)

BotWhyContract
strat.flow_aware_strategies

Sibling bots (same OrderIntent)

BotWhyContract
intel.onchainwatcher

External services

ServiceEndpointSLA assumedOn failure
Polygon RPC (Alchemy / Infura)Polygon mainnet99.9% / 200 ms p99

23. Security Surfaces

Abuse vectors considered

  • Adversary uses multiple wallets to fragment institutional flow and evade institutional classification
  • Wash-trades between related wallets to generate artificial arbitrage pattern signals

Mitigations

  • Classification is probabilistic and informational only; strategies apply independent signal weighting
  • Arbitrage detection requires multi-market simultaneous fills to reduce single-market spoofing

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesClassifies wallet pUSD flow types (retail, institutional, arbitrage) from CTFExchangeV2 on-chain data. Read-only. No order signing.

API surfaces declared

onchaindatainternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Large fill classified as institutional and ObservationReport emittedSingle wallet, total_pusd=50000 in one blockObservationReport emitted with flow_label=institutional
Small fill classified as retailSingle wallet, total_pusd=200 in one blockObservationReport emitted with flow_label=retail
KillSwitch suppresses emissionkillswitch.active=true; institutional fill presentNo ObservationReport; KILL_SWITCH_ACTIVE logged

Integration Tests

TestExpected result
Full lifecycle: institutional fill detected on-chain → ObservationReport → strategy adjusts signal weightStrategy receives flow_label=institutional with total_pusd
RPC outage: STALE_DATA emitted; classification halted; last labels retainedSTALE_DATA WARN; WalletFlowClassifierRPCDown alert fires; last labels in Redis

Property Tests

PropertyRequired behaviour
WalletFlowClassifier never submits or signs ordersAlways true
No ObservationReport emitted when KillSwitch is activeAlways true

27. Operational Runbook

WalletFlowClassifier incidents are most commonly RPC outages. High arbitrage rate alerts indicate unusual market conditions worth investigating but do not block trading.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
WalletFlowClassifierRPCDownCheck rpc_block_lag_s. Verify Polygon RPC endpoint health. Trigger failover if primary is down.Infra on-call immediately; Intelligence pod lead within 5 min
WalletFlowClassifierHighArbitrageRateReview arbitrage-labelled wallets. Check for known arbitrage bots or unusual market conditions.Intelligence pod lead if > 20 arbitrage classifications in 10 min

Manual overrides

  • reset_wallet_state — DEL redis key wallet:<address> to reset classification state for a specific wallet — After known wallet rotation or operator-confirmed wallet re-use

Healthcheck

Endpoint: /internal/health/wallet-flow-classifier | Green: rpc_block_lag_s < 24 AND Redis reachable AND last classification < 30 s ago | Red: rpc_block_lag_s > 60 OR Redis unreachable

28. Promotion Gates

A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.

Promote to Shadow

GateHow measuredThreshold
Unit tests pass for retail/institutional/arbitrage classification and KillSwitch suppressionCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Classification completes within one Polygon block interval (12 s) for p99 of blocksIntegration test on Polygon testnetp99 < 12 s per block

Promote to General live

GateHow measuredThreshold
Zero missed block classifications over 7-day soakBlock audit log vs RPC event log0 missed blocks

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
Purpose defined✓ done
Required inputs listed✓ done
Parameters defined✓ done
Defaults defined✓ done
Warning thresholds defined✓ done
Hard thresholds defined✓ done
Safe fallback defined✓ done
Structured output defined✓ done
Developer log defined✓ done
Plain-English explanation✓ done
Unit tests defined✓ done
Integration tests defined✓ done
Property tests defined✓ done
Failure-mode block complete✓ done
Reference implementation pseudocode✓ done
Wire examples (input + output)✓ done
Reason codes listed✓ done
Metrics & logs defined✓ done
State & persistence defined✓ done
Concurrency & idempotency defined✓ done
Dependencies declared✓ done
Security surfaces declared✓ done
Polymarket V2 compatibility declared✓ done
Version & migration history declared✓ done
Operational runbook defined✓ done
Promotion gates defined✓ done
Failure-injection recipes defined✓ done