1.8 ComplianceGate
ComplianceGate enforces Polymarket's terms-of-service access policy on every OrderIntent before it reaches execution. It validates (1) that the originating wallet has completed Polymarket onboarding, (2) that the user's jurisdiction is not on the blocked list, (3) that the wallet address does not appear on OFAC or other configured sanctions lists, and (4) that the target market is eligible for trading (not restricted by category policy). When any check fails the order is rejected outright; for geopolitical markets the bot applies NegRisk-aware category rules before passing. ComplianceGate is fail-closed: any inability to verify policy results in a hard reject.
v3 readiness
A bot is done when all four scores are. What does done mean?
risk.compliancegate
Validates builderCode length, signer authority. SEARCH_SPACE declared. Fixture pack pending.
Source: @polytraders/bots · src/risk/compliancegate.js · Impl 11/15 · Backtest 3/4
1. Bot Identity
| Layer | Risk Risk |
|---|---|
| Bot class | Guardrail |
| Authority | RejectReshape |
| Status | LIVE |
| Readiness | General live |
| Runs before | ExecutionPlan emit |
| Runs after | Strategy OrderIntent |
| Applies to | Every OrderIntent — checks the originating user wallet and target market for jurisdiction, KYC, sanctions, and market-eligibility policy |
| Default mode | general_live |
| User-visible | summary-only |
| Developer owner | Polytraders core — Risk pod |
Operational profile
| Modes supported | quarantine |
|---|
2. Purpose
ComplianceGate enforces Polymarket's terms-of-service access policy on every OrderIntent before it reaches execution. It validates (1) that the originating wallet has completed Polymarket onboarding, (2) that the user's jurisdiction is not on the blocked list, (3) that the wallet address does not appear on OFAC or other configured sanctions lists, and (4) that the target market is eligible for trading (not restricted by category policy). When any check fails the order is rejected outright; for geopolitical markets the bot applies NegRisk-aware category rules before passing. ComplianceGate is fail-closed: any inability to verify policy results in a hard reject.
3. Why This Bot Matters
Sanctioned-address order submitted
Allowing a wallet on an OFAC or other sanctions list to trade exposes the operator to regulatory and legal liability, and may result in asset freezing or platform shutdown.
Blocked-jurisdiction user bypasses geo check
Trading from a restricted jurisdiction violates Polymarket's terms and applicable law; unchecked this can lead to regulatory action against the platform.
Non-onboarded wallet places order
Polymarket's KYC/AML process has not been completed; the user has not accepted terms and the platform has no identity verification for the account.
Market category restriction not enforced
Certain market categories (e.g. restricted financial instruments, region-specific political events) may not be available to all users; ignoring category eligibility creates unequal treatment and compliance gaps.
Fail-open on policy-service outage
If compliance checks succeed silently during a data outage, ineligible users can trade undetected. ComplianceGate must fail-closed to prevent this.
4. Required Polymarket Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| Gamma API market metadata — negRisk flag and category | gamma | Yes | Determine whether the market is a NegRisk category (e.g. geopolitical) and whether category-level trading restrictions apply. |
| Polymarket onboarding status for the linked wallet | internal | Yes | Confirm the wallet has completed Polymarket KYC/AML onboarding before any order is allowed. |
5. Required Internal Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| User jurisdiction metadata (from account profile or IP-geolocation) | internal | Yes | Check the user's detected jurisdiction against the blocked_jurisdictions list. |
| Sanctions list snapshot (OFAC SDN + configured providers) | internal | Yes | Screen the originating wallet address against current sanctions lists; hard-reject on any match. |
| KillSwitch active flag | KillSwitch | Yes | If KillSwitch is active, reject all orders immediately without running policy checks. |
| Market eligibility override map | Admin UI | No | Allow compliance team to whitelist or blacklist specific markets outside of automated category rules. |
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|---|---|---|---|
| require_polymarket_onboarded | True | None | True | Whether the originating wallet must have completed Polymarket's onboarding flow before any order is permitted. |
| blocked_jurisdictions | ['US', 'GB', 'IR', 'KP', 'SY', 'CU'] | ['US', 'GB', 'IR', 'KP', 'SY', 'CU', 'UA'] | ['US', 'GB', 'IR', 'KP', 'SY', 'CU'] | ISO-3166 country codes that are prohibited from trading on Polymarket per platform policy. Warning threshold: list narrower than the recommended 7-country minimum triggers a WARN annotation. |
| sanctions_list_source | OFAC_SDN | None | OFAC_SDN | Which sanctions screening provider(s) to use. Options: OFAC_SDN, CHAINALYSIS, ELLIPTIC, COMBINED. |
| close_only_on_violation | False | None | False | If true, a user who fails a non-sanctions policy check (e.g. jurisdiction change post-onboarding) may still submit close/reduce-only orders to exit existing positions rather than being fully hard-rejected. |
7. Detailed Parameter Instructions
require_polymarket_onboarded
What it means
Whether the originating wallet must have completed Polymarket's onboarding flow before any order is permitted.
Default
{ "require_polymarket_onboarded": true }
Why this default matters
Polymarket's terms require onboarding acceptance before trading. Disabling this check would allow unauthenticated wallets to trade, violating ToS and AML obligations.
Threshold logic
| Condition | Action |
|---|---|
| onboarded = true | APPROVE (this check) |
| onboarded = false | REJECT — COMPLIANCE_GATE_NOT_ONBOARDED |
Developer check
if (require_polymarket_onboarded && !user.polymarket_onboarded) return reject('COMPLIANCE_GATE_NOT_ONBOARDED');
User-facing English
Your account must complete Polymarket onboarding before placing orders.
blocked_jurisdictions
What it means
ISO-3166 country codes that are prohibited from trading on Polymarket per platform policy. Warning threshold: list narrower than the recommended 7-country minimum triggers a WARN annotation.
Default
{ "blocked_jurisdictions": ["US", "GB", "IR", "KP", "SY", "CU"] }
Why this default matters
Polymarket does not permit trading from these jurisdictions due to regulatory restrictions. The list is reviewed by the compliance team; hardcoded minimum set cannot be removed via config.
Threshold logic
| Condition | Action |
|---|---|
| configured list has >= 7 entries | No warning — list meets minimum recommended coverage |
| configured list has 4–6 entries | WARN — COMPLIANCE_GATE_JURISDICTION_LIST_NARROW: list is below recommended minimum |
| user jurisdiction not in blocked_jurisdictions | APPROVE (this check) |
| user jurisdiction in blocked_jurisdictions | REJECT — COMPLIANCE_GATE_JURISDICTION_BLOCKED |
Developer check
if (blocked_jurisdictions.includes(user.country_code)) return reject('COMPLIANCE_GATE_JURISDICTION_BLOCKED');
User-facing English
Trading is not available in your region due to regulatory restrictions.
sanctions_list_source
What it means
Which sanctions screening provider(s) to use. Options: OFAC_SDN, CHAINALYSIS, ELLIPTIC, COMBINED.
Default
{ "sanctions_list_source": "OFAC_SDN" }
Why this default matters
OFAC SDN is the minimum legal requirement for US-linked operators. Additional providers increase coverage.
Threshold logic
| Condition | Action |
|---|---|
| wallet not on any screened list | APPROVE (this check) |
| wallet matches any entry | REJECT — COMPLIANCE_GATE_SANCTIONS_HIT |
Developer check
const hit = sanctionsScreener.check(intent.wallet, params.sanctions_list_source); if (hit) return reject('COMPLIANCE_GATE_SANCTIONS_HIT');
User-facing English
This wallet cannot be used for trading on this platform.
close_only_on_violation
What it means
If true, a user who fails a non-sanctions policy check (e.g. jurisdiction change post-onboarding) may still submit close/reduce-only orders to exit existing positions rather than being fully hard-rejected.
Default
{ "close_only_on_violation": false }
Why this default matters
Defaulting to false (full reject) is the conservative compliance posture. Enable only with legal review, as it may be required to allow position exits in some jurisdictions.
Threshold logic
| Condition | Action |
|---|---|
| close_only_on_violation = false AND check fails | REJECT — COMPLIANCE_GATE_JURISDICTION_BLOCKED |
| close_only_on_violation = true AND check fails AND order is close/reduce | RESHAPE — set constraints.close_only = true |
Developer check
if (policyFailed && params.close_only_on_violation && isCloseOrder(intent)) return reshape({ close_only: true });
User-facing English
You may only close existing positions in this market.
8. Default Configuration
{
"bot_id": "risk.compliance_gate",
"version": "2.0.0",
"mode": "hard_guard",
"defaults": {
"require_polymarket_onboarded": true,
"blocked_jurisdictions": [
"US",
"GB",
"IR",
"KP",
"SY",
"CU"
],
"sanctions_list_source": "OFAC_SDN",
"close_only_on_violation": false
},
"locked": {
"require_polymarket_onboarded": {
"min": true
},
"sanctions_list_source": {
"allowed": [
"OFAC_SDN",
"CHAINALYSIS",
"ELLIPTIC",
"COMBINED"
]
}
}
}9. Implementation Flow
- Receive OrderIntent from Strategy layer including market_id, wallet address, and user metadata.
- Check KillSwitch active flag; if active, return REJECT with KILL_SWITCH_ACTIVE immediately.
- Screen the originating wallet address against the configured sanctions list (OFAC SDN + additional providers). On any match, return HARD_REJECT with COMPLIANCE_GATE_SANCTIONS_HIT — this check cannot be bypassed by close_only_on_violation.
- Check user jurisdiction against blocked_jurisdictions. If blocked and close_only_on_violation=false, return HARD_REJECT with COMPLIANCE_GATE_JURISDICTION_BLOCKED. If blocked and close_only_on_violation=true and this is a close/reduce order, return RESHAPE with constraints.close_only=true.
- If require_polymarket_onboarded=true, verify the wallet's onboarding status from the internal onboarding cache. If not onboarded, return HARD_REJECT with COMPLIANCE_GATE_NOT_ONBOARDED.
- Fetch target market metadata from Gamma API to determine category and negRisk flag. If the market category is restricted for this user (e.g. geopolitical events in certain jurisdictions), return HARD_REJECT with COMPLIANCE_GATE_MARKET_INELIGIBLE.
- Check the admin-configurable market eligibility override map. If market is explicitly blacklisted, return HARD_REJECT with COMPLIANCE_GATE_MARKET_INELIGIBLE.
- All checks passed — return APPROVE with inputs_used list and checked_at timestamp.
10. Reference Implementation
Runs four sequential policy gates — KillSwitch, sanctions screening, jurisdiction check, and market eligibility — before emitting a RiskVote. All gates are fail-closed: any data-source error produces HARD_REJECT.
Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.
FUNCTION evaluateCompliance(intent):
// --- 0. KillSwitch gate ---
ks = FETCH internal.killswitch.status
IF ks.active:
EMIT RiskVote(decision=HARD_REJECT, reason=KILL_SWITCH_ACTIVE)
RETURN
// --- 1. Sanctions screening ---
sanctionsResult = FETCH internal.sanctions.check(intent.wallet, params.sanctions_list_source)
IF sanctionsResult IS NULL:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_DATA_UNAVAILABLE)
RETURN
IF sanctionsResult.hit:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_SANCTIONS_HIT)
RETURN
// --- 2. Jurisdiction check ---
user = FETCH internal.user.profile(intent.user_id)
IF user IS NULL:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_DATA_UNAVAILABLE)
RETURN
IF params.blocked_jurisdictions.contains(user.country_code):
IF params.close_only_on_violation AND isCloseOrder(intent):
EMIT RiskVote(decision=RESHAPE_REQUIRED,
reason=COMPLIANCE_GATE_JURISDICTION_CLOSE_ONLY,
constraints={close_only: true})
ELSE:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_JURISDICTION_BLOCKED)
RETURN
// --- 3. Onboarding check ---
IF params.require_polymarket_onboarded:
onboarding = FETCH internal.onboarding.status(intent.wallet)
IF onboarding IS NULL:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_DATA_UNAVAILABLE)
RETURN
IF NOT onboarding.completed:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_NOT_ONBOARDED)
RETURN
// --- 4. Market eligibility check ---
market = FETCH gamma.getMarketByConditionId(intent.market_id)
IF market IS NULL:
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_DATA_UNAVAILABLE)
RETURN
override = FETCH internal.market_eligibility_overrides.get(intent.market_id)
IF override == 'BLOCKED':
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_MARKET_INELIGIBLE)
RETURN
IF isCategoryRestrictedForUser(market.category, market.negRisk, user):
EMIT RiskVote(decision=HARD_REJECT, reason=COMPLIANCE_GATE_MARKET_INELIGIBLE)
RETURN
// --- 5. All checks passed ---
EMIT RiskVote(decision=APPROVE,
inputs_used=['internal.sanctions', 'internal.user.profile',
'internal.onboarding', 'gamma.market'],
checked_at=now_iso())
SDK calls used
gamma.getMarketByConditionId(market_id)internal.sanctions.check(wallet, provider)internal.onboarding.status(wallet)internal.user.profile(user_id)internal.market_eligibility_overrides.get(market_id)internal.killswitch.status()
Complexity: O(1) — constant number of policy checks per intent, each backed by in-memory cache with periodic refresh
11. Wire Examples
Input — what arrives on the wire
OrderIntent from strategy — user in blocked jurisdiction — internal
{
"intent_id": "int_c1d2e3f4a5b6c7d8",
"market_id": "0x7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a",
"side": "BUY",
"outcome": "YES",
"size_usd": 500,
"price": 0.55,
"wallet": "0xDeAdBeEf1234567890AbCdEf1234567890AbCdEf",
"user_id": "usr_9a8b7c6d",
"generated_at_ms": 1746780000000
}
User profile snapshot — internal
{
"user_id": "usr_9a8b7c6d",
"country_code": "GB",
"polymarket_onboarded": true,
"profile_fetched_at_ms": 1746779950000
}
Output — what the bot emits
RiskVote — HARD_REJECT (jurisdiction blocked)
{
"guard_id": "risk.compliance_gate",
"decision": "HARD_REJECT",
"severity": "HARD",
"reason_code": "COMPLIANCE_GATE_JURISDICTION_BLOCKED",
"message": "Wallet jurisdiction GB is on the blocked list. Order rejected per platform compliance policy.",
"constraints": {},
"inputs_used": [
"internal.sanctions.OFAC_SDN",
"internal.user.profile",
"internal.onboarding.status",
"gamma.market.category"
],
"checked_at": "2026-05-09T10:22:01Z"
}
RiskVote — APPROVE (all checks pass)
{
"guard_id": "risk.compliance_gate",
"decision": "APPROVE",
"severity": "INFO",
"reason_code": "COMPLIANCE_GATE_PASS",
"message": "All compliance checks passed for wallet 0xAb12...Cd34.",
"constraints": {},
"inputs_used": [
"internal.sanctions.OFAC_SDN",
"internal.user.profile",
"internal.onboarding.status",
"gamma.market.category"
],
"checked_at": "2026-05-09T10:22:01Z"
}12. Decision Logic
APPROVE
Wallet is not sanctioned, jurisdiction is not blocked, user is onboarded (if required), and the target market is eligible for this user's category and jurisdiction profile.
RESHAPE_REQUIRED
User fails a non-sanctions policy check (e.g. jurisdiction change) but close_only_on_violation=true — emit constraints.close_only=true to allow position exit only.
REJECT
Wallet matches a sanctions list entry, user jurisdiction is in blocked_jurisdictions (with close_only disabled), user is not onboarded, market category is restricted, KillSwitch is active, or any compliance data source is unavailable (fail-closed).
WARNING_ONLY
Not used — ComplianceGate has reject authority and is fail-closed. No compliance check result is advisory-only.
13. Standard Decision Output
This bot returns a RiskVote object. See RiskVote schema.
{
"guard_id": "risk.compliance_gate",
"decision": "HARD_REJECT",
"severity": "HARD",
"reason_code": "COMPLIANCE_GATE_JURISDICTION_BLOCKED",
"message": "Wallet jurisdiction GB is on the blocked list. Order rejected.",
"constraints": {},
"inputs_used": [
"internal.user.jurisdiction",
"internal.sanctions.OFAC_SDN",
"internal.onboarding.status",
"gamma.market.category"
],
"checked_at": "2026-05-09T10:22:00Z"
}14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|---|---|---|---|
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch is active; no orders may proceed. | Immediately return HARD_REJECT without running any compliance check. | Trading is currently paused. Please try again later. |
COMPLIANCE_GATE_SANCTIONS_HIT | HARD_REJECT | The originating wallet address matched an entry in the configured sanctions list (e.g. OFAC SDN). | Return HARD_REJECT; do not expose which list triggered the match in user-facing messaging. | This wallet cannot be used for trading on this platform. |
COMPLIANCE_GATE_JURISDICTION_BLOCKED | HARD_REJECT | The user's detected jurisdiction is on the blocked_jurisdictions list and close_only_on_violation is false. | Return HARD_REJECT; log the country_code for compliance reporting. | Trading is not available in your region due to regulatory restrictions. |
COMPLIANCE_GATE_JURISDICTION_CLOSE_ONLY | RESHAPE | The user's jurisdiction is blocked but close_only_on_violation=true and this is a close/reduce order. | Return RESHAPE_REQUIRED with constraints.close_only=true. | You may only close existing positions in this market from your current region. |
COMPLIANCE_GATE_NOT_ONBOARDED | HARD_REJECT | The wallet has not completed Polymarket's required onboarding/KYC flow. | Return HARD_REJECT; direct user to complete onboarding. | Your account must complete Polymarket onboarding before placing orders. |
COMPLIANCE_GATE_MARKET_INELIGIBLE | HARD_REJECT | The target market is blocked by category policy, admin override, or a NegRisk geopolitical restriction for this user. | Return HARD_REJECT; log market_id and category for audit. | This market is not available for trading in your region or account profile. |
COMPLIANCE_GATE_DATA_UNAVAILABLE | HARD_REJECT | One or more compliance data sources (sanctions API, onboarding cache, Gamma market metadata, user profile) returned an error or were unreachable. | Return HARD_REJECT (fail-closed). Log which data source failed and alert on-call if sustained. | We could not verify your eligibility at this time. Please try again shortly. |
COMPLIANCE_GATE_PASS | INFO | All compliance checks passed for this wallet and market. | Emit APPROVE and continue to next guardrail. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|---|---|---|---|
polytraders_risk_compliancegate_decisions_total | counter | count | decision, reason_code | Total RiskVote decisions emitted by ComplianceGate, broken down by decision type and reason code. Monitors block rate per policy dimension. |
polytraders_risk_compliancegate_sanctions_checks_total | counter | count | provider, result | Number of sanctions screenings performed, broken down by provider and result (hit / clean / error). |
polytraders_risk_compliancegate_jurisdiction_blocks_total | counter | count | country_code | Number of hard-rejects due to blocked jurisdiction, by country code. Used for compliance reporting. |
polytraders_risk_compliancegate_data_source_errors_total | counter | count | source | Number of fail-closed rejections caused by unavailable compliance data, by source name. Triggers alert when non-zero. |
polytraders_risk_compliancegate_eval_latency_ms | histogram | milliseconds | Wall-clock time from OrderIntent receipt to RiskVote emit. P99 target < 50ms (cache-backed checks). | |
polytraders_risk_compliancegate_sanctions_cache_age_seconds | gauge | seconds | provider | Age of the cached sanctions list snapshot. Alerts if stale beyond configured refresh interval. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|---|---|---|
ComplianceGateDataSourceError | rate(polytraders_risk_compliancegate_data_source_errors_total[5m]) > 0 | page | #runbook-compliancegate-data-source-error |
ComplianceGateSanctionsCacheStale | polytraders_risk_compliancegate_sanctions_cache_age_seconds > 3600 | page | #runbook-compliancegate-sanctions-cache |
ComplianceGateHighRejectRate | rate(polytraders_risk_compliancegate_decisions_total{decision='HARD_REJECT'}[5m]) / rate(polytraders_risk_compliancegate_decisions_total[5m]) > 0.2 | warn | #runbook-compliancegate-reject-rate |
ComplianceGateHighLatency | histogram_quantile(0.99, rate(polytraders_risk_compliancegate_eval_latency_ms_bucket[5m])) > 100 | warn | #runbook-compliancegate-latency |
Dashboards
- Grafana — Risk overview / ComplianceGate
- Grafana — Compliance audit / jurisdiction and sanctions breakdown
16. Developer Reporting
{
"bot_id": "risk.compliance_gate",
"decision": "HARD_REJECT",
"reason_code": "COMPLIANCE_GATE_JURISDICTION_BLOCKED",
"inputs_used": [
"internal.user.jurisdiction",
"internal.sanctions.OFAC_SDN"
],
"metrics": {
"wallet": "0xaabbcc...redacted",
"jurisdiction": "GB",
"sanctions_hit": false,
"onboarded": true,
"market_category": "politics",
"negrisk": false
},
"checked_at": "2026-05-09T10:22:00Z"
}17. Plain-English Reporting
| Situation | User-facing explanation |
|---|---|
| Order blocked — jurisdiction restricted | Trading is not available in your region due to regulatory restrictions on this platform. |
| Order blocked — account not onboarded | Your account must complete the Polymarket onboarding process before placing orders. Please complete verification and try again. |
| Order blocked — wallet flagged | This wallet cannot be used for trading on this platform. Please contact support if you believe this is an error. |
| Order blocked — market not available in your region | This market is not available for trading in your region due to local regulations. |
| Order limited to close-only | You may only close or reduce existing positions in this market. New positions cannot be opened from your account. |
18. Failure-Mode Block
| main_failure_mode | Failing open during a compliance-service outage, allowing ineligible users or sanctioned wallets to trade undetected. ComplianceGate must never approve when its data sources are unavailable. |
|---|---|
| false_positive_risk | Incorrectly blocking a legitimate user due to a stale jurisdiction record, a cached sanctions-list false positive, or a market-eligibility misconfiguration in the admin override map. |
| false_negative_risk | Approving an order using a stale sanctions-list snapshot that does not reflect a recently added entry, creating a compliance gap. |
| safe_fallback | If any compliance data source (sanctions API, onboarding cache, Gamma market metadata) returns an error or stale data, ComplianceGate hard-rejects the order with COMPLIANCE_GATE_DATA_UNAVAILABLE. It never approves on missing policy data. |
| required_dependencies | Sanctions screening service (OFAC SDN or configured provider), Polymarket onboarding status cache, Gamma API — market category and negRisk metadata, Internal user jurisdiction service, KillSwitch active flag, Admin market eligibility override map |
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|---|---|---|
SANCTIONS_SERVICE_DOWN | Block outbound calls to sanctions provider; let cache TTL expire | Returns to normal within one cache-refresh cycle after connectivity is restored. | |
JURISDICTION_BLOCKED_USER | Set user.country_code = 'US' in internal user profile mock | Immediate on next evaluation once user profile is updated to a non-blocked jurisdiction. | |
SANCTIONS_HIT | Add test wallet address to OFAC SDN mock list | Returns to APPROVE once wallet is removed from the sanctions list and cache is refreshed. | |
GAMMA_API_DOWN | Return 503 from Gamma API mock for market metadata endpoint | Returns to APPROVE once Gamma API is reachable and market metadata is cached. | |
NOT_ONBOARDED_WALLET | Set onboarding.completed = false in onboarding cache mock for test wallet | Returns to APPROVE once onboarding.completed = true is set and cache is refreshed. | |
KILL_SWITCH_ON | Set internal.killswitch.status.active = true | Returns to normal pipeline on manual KillSwitch reset. |
20. State & Persistence
Cold-start recovery
On cold start, all caches are empty. First evaluation per wallet/market triggers a blocking fetch. If the fetch fails, the order is rejected fail-closed until the cache is populated.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|---|
| Execution model | single-threaded event loop |
| Max in-flight | 500 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 50 |
| Backpressure strategy | drop newest |
| Locking / mutual exclusion | per-wallet mutex for onboarding cache writes; sanctions list accessed read-only after refresh |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|---|---|
| risk.kill_switch | Global brake — checked first before any compliance data is read. | RiskVote.HARD_REJECT(KILL_SWITCH_ACTIVE) short-circuits all policy evaluation. |
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|---|---|
| exec.smart_router | Approved RiskVote passes to SmartRouter for ExecutionPlan construction. | APPROVE passes through; HARD_REJECT causes SmartRouter to discard the intent without retry. |
Sibling bots (same OrderIntent)
| Bot | Why | Contract |
|---|---|---|
| risk.portfolio_guard | Sibling guardrail; both must APPROVE or RESHAPE before SmartRouter runs. | |
| risk.liquidity_guard | Sibling guardrail. |
External services
| Service | Endpoint | SLA assumed | On failure |
|---|---|---|---|
| Gamma API (market metadata) | https://gamma-api.polymarket.com | 99.9% / 300ms p99 | |
| OFAC SDN / Sanctions Screening Service | internal sanctions cache refreshed from upstream provider | 99.99% (in-memory cache); upstream refresh every 1h |
23. Security Surfaces
Abuse vectors considered
- VPN / proxy use to circumvent jurisdiction detection
- Presenting a freshly created wallet not yet on sanctions lists
- Timing attack: submitting order during sanctions-list refresh window when cache has just expired
Mitigations
- Jurisdiction check uses platform-side geo signal, not user-declared location
- Sanctions list refresh is asynchronous with overlap: old list is held until new list is fully loaded
- Fail-closed policy: expired or unavailable cache → HARD_REJECT, never APPROVE
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | no |
| Aware of negative-risk markets | yes |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | ComplianceGate inspects market metadata from Gamma V2 to apply negRisk-aware category rules; geopolitical markets have zero platform fees and are flagged as negRisk, which affects eligibility checks. The bot does not sign orders or interact with CTFExchangeV2 directly. |
API surfaces declared
Networks supported
25. Versioning & Migration
| Field | Value |
|---|---|
| spec | 2.0.0 |
| implementation | 2.1.0 |
| schema | 2 |
| released | 2026-04-28 |
Migration history
| Date | From | To | Reason | Action taken |
|---|---|---|---|---|
| 2026-04-28 | v1 | v2 | CLOB V2 cutover | Switched to py-clob-client-v2; all notional amounts now denominated in pUSD. Removed feeRateBps references from compliance-context payloads. Updated Gamma market-metadata fetch to use V2 negRisk and enableNegRisk flags for geopolitical category classification. EIP-712 domain version updated to '2' in downstream intent inspection. |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|---|---|
| Approve when all checks pass | wallet not sanctioned, jurisdiction=DE, onboarded=true, market category=crypto, negrisk=false | APPROVE with no constraints |
| Reject when jurisdiction is blocked | jurisdiction=US, close_only_on_violation=false | HARD_REJECT with reason_code=COMPLIANCE_GATE_JURISDICTION_BLOCKED |
| Reshape to close-only when jurisdiction blocked and close_only_on_violation=true | jurisdiction=US, close_only_on_violation=true, order_type=REDUCE | RESHAPE with constraints.close_only=true |
| Reject when wallet is on OFAC SDN list | wallet address matches OFAC SDN entry | HARD_REJECT with reason_code=COMPLIANCE_GATE_SANCTIONS_HIT, regardless of close_only_on_violation |
| Reject when user not onboarded | require_polymarket_onboarded=true, onboarded=false | HARD_REJECT with reason_code=COMPLIANCE_GATE_NOT_ONBOARDED |
| Reject when market category restricted for user | market category=geopolitical, user jurisdiction eligibility=restricted | HARD_REJECT with reason_code=COMPLIANCE_GATE_MARKET_INELIGIBLE |
| Reject when sanctions service unavailable (fail-closed) | sanctions service returns 503 | HARD_REJECT with reason_code=COMPLIANCE_GATE_DATA_UNAVAILABLE |
Integration Tests
| Test | Expected result |
|---|---|
| Sanctions list refresh picked up within TTL window | HARD_REJECT(COMPLIANCE_GATE_SANCTIONS_HIT) after a wallet is added to the OFAC SDN snapshot and the cache is refreshed |
| NegRisk geopolitical market category check via Gamma API | HARD_REJECT(COMPLIANCE_GATE_MARKET_INELIGIBLE) when Gamma returns negRisk=true and category=geopolitical for a restricted user |
| KillSwitch active bypasses all policy checks | HARD_REJECT(KILL_SWITCH_ACTIVE) without consulting any compliance data source |
Property Tests
| Property | Required behaviour |
|---|---|
| Sanctions-hit wallets are never approved regardless of other checks | Always true — COMPLIANCE_GATE_SANCTIONS_HIT cannot be overridden by close_only_on_violation |
| Unavailable policy data never results in APPROVE | Always true — any data source error produces HARD_REJECT(COMPLIANCE_GATE_DATA_UNAVAILABLE) |
| Reshape close_only is only emitted for non-sanctions violations with close_only_on_violation=true | Always true — sanctions hits are always HARD_REJECT |
27. Operational Runbook
ComplianceGate incidents typically involve a data-source outage causing fail-closed mass rejections, or a sanctions-list staleness alert. Any COMPLIANCE_GATE_SANCTIONS_HIT in production must be escalated to the compliance team immediately and never silently cleared.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|---|---|---|---|
ComplianceGateDataSourceError | ||||
ComplianceGateSanctionsCacheStale | ||||
ComplianceGateHighRejectRate | ||||
ComplianceGateHighLatency |
Manual overrides
——
Healthcheck
GET /internal/health/compliancegate → 200 if all data source caches (sanctions, onboarding, market eligibility) are within TTL and last successful refresh < 3600s ago; red if any cache is expired or last refresh attempt failed, or evaluation latency p99 > 100ms.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
| Gate | How measured | Threshold |
|---|---|---|
| All unit tests pass including fail-closed scenarios | CI test run | 100% pass |
| Sanctions screening integration test with mock OFAC SDN list | Integration test suite | Pass |
Promote to Limited live
| Gate | How measured | Threshold |
|---|---|---|
| Shadow reject rate matches expected baseline within 5% over 48h | Grafana shadow vs live comparison dashboard | < 5% divergence |
| p99 evaluation latency < 50ms (cache-backed) | polytraders_risk_compliancegate_eval_latency_ms histogram | p99 < 50ms |
Promote to General live
| Gate | How measured | Threshold |
|---|---|---|
| Zero COMPLIANCE_GATE_DATA_UNAVAILABLE rejections during normal operating hours over 7 days | ComplianceGateDataSourceError alert history | 0 firings |
| Compliance team sign-off on sanctions list integration | Written approval from compliance team | Approved |
| Fail-closed injection test confirmed in staging | Failure injection test suite | Pass |
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|---|
| 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 |