v3.0: JWT auth, korechain.korenet.cloud routing, LICK module, feed poller v3

This commit is contained in:
VZ Build
2026-07-14 16:30:06 +00:00
parent ca3783f348
commit 64b969f4b2
4 changed files with 591 additions and 370 deletions

View File

@@ -1,58 +1,62 @@
#!/usr/bin/env python3
"""
Vector Zulu — Portal Feed Poller v2.0
Polls KoreNet portal feeds for new payment/settlement/deal events
Vector Zulu — Portal Feed Poller v3.0
Polls KoreNet payment/settlement/deal feeds for new events
and executes the full payment mirror lifecycle.
Key facts from VZ-Welcome-Pack FINAL v1.0 (2026-07-08):
- UETR comes FROM the payment feed (SWIFT gpi UUID v4) — never mint a new one
- Auth-code format: AUTH-VZ-MINT-<period> e.g. AUTH-VZ-MINT-2026Q2
- Lifecycle keyed by SAME UETR: mint → settle → redeem → burn-sell → close
- Every step LICK-signed
- KoreID validated via benjii-prime.korenet.cloud/api/v1/koreid/verify-lineage
- EventBus topic: integration-partners (VZ's own scoped topic)
Key changes from v2:
- JWT-only auth (no client cert — ssl_verify_client optional confirmed)
- Primary endpoint: korechain.korenet.cloud
- Fallback: fnb.korenet.cloud
- LICK signing on all mutating calls via lick.py
- Correct swagger-confirmed API paths
- Payment mirror via /api/v1/payment-mirror/*
- Compliance via /api/Compliance/sanctions/ofac and /api/v1/compliance/ofac/*
- KoreID validation via benjii-prime.korenet.cloud (JWT auth)
- UETR passthrough — never mint new unless feed genuinely lacks one
"""
import os
import json
import time
import uuid
import logging
import subprocess
import requests
import redis
import psycopg2
import urllib3
import os, json, time, uuid, logging, requests, redis, psycopg2, urllib3
from datetime import datetime, timezone
from koreid_new import KoreIDGenerator, KoreIDValidator, KoreIDParser, KoreType, KoreDomain, GENESIS_ROOT
from koreid import KoreIDGenerator, KoreIDParser, KoreType, KoreDomain, GENESIS_ROOT
from eventbus import EventBusClient, Severity, EventType
from lick import build_headers, canonicalize
urllib3.disable_warnings()
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [POLLER-V2] %(levelname)s %(message)s'
)
logging.basicConfig(level=logging.INFO, format='%(asctime)s [POLLER-V3] %(levelname)s %(message)s')
log = logging.getLogger(__name__)
# ── Config ────────────────────────────────────────────────────────────────────
CERT = os.environ.get('VECTOR_ZULU_CERT', '/certs/vector-zulu-client.fullchain.pem')
KEY = os.environ.get('VECTOR_ZULU_KEY', '/certs/vector-zulu-client.key')
API_PRIMARY = os.environ.get('KORENET_API_URL', 'https://korechain.korenet.cloud')
API_FALLBACK = os.environ.get('KORENET_API_FALLBACK', 'https://fnb.korenet.cloud')
RAILS_URL = os.environ.get('VECTOR_ZULU_RAILS_URL', 'https://vector.korenet.cloud')
API_URL = os.environ.get('VECTOR_ZULU_API_URL', 'https://api.korenet.cloud')
BENJII_URL = 'https://benjii-prime.korenet.cloud'
POLL_INTERVAL = int(os.environ.get('POLL_INTERVAL', '30'))
AUTH_CODE = os.environ.get('VZ_MINT_AUTH_CODE', 'AUTH-VZ-MINT-2026Q2')
VZ_TENANT = 'vector-zulu'
TENANT_ID = 'vector-zulu'
VZ_VAULTS = ['VZ-USD-VAULT', 'VZ-USDT-VAULT', 'VZ-BTC-VAULT']
VENUES = ['cryptohost', 'koreexchange', 'uniswap']
# ── Infrastructure ────────────────────────────────────────────────────────────
def load_jwt() -> str:
jwt = os.environ.get('VECTOR_ZULU_JWT', '')
if jwt:
return jwt
for path in ['/app/.secrets', '/home/constellationnode/.korenet/vector-zulu/.secrets',
os.path.expanduser('~/.korenet/vector-zulu/.secrets')]:
if os.path.exists(path):
with open(path) as f:
for line in f:
if 'VECTOR_ZULU_JWT=' in line:
val = line.split('VECTOR_ZULU_JWT=', 1)[1].strip().strip('"').strip("'")
if val:
return val
return ''
def get_redis():
return redis.Redis(
host=os.environ.get('REDIS_HOST', 'localhost'),
port=6379, decode_responses=True
)
return redis.Redis(host=os.environ.get('REDIS_HOST', 'localhost'), port=6379, decode_responses=True)
def get_pg():
return psycopg2.connect(
@@ -63,75 +67,90 @@ def get_pg():
connect_timeout=10
)
def rails_session():
def _session(jwt: str) -> requests.Session:
"""JWT-only session — no client cert required."""
s = requests.Session()
s.cert = (CERT, KEY)
s.verify = False
s.headers = {'X-Tenant-Id': VZ_TENANT}
s.verify = False
s.headers.update({
'Authorization': f'Bearer {jwt}',
'X-Tenant-Id': TENANT_ID,
'Content-Type': 'application/json',
})
return s
def run_tool(*args) -> dict:
"""Run vz_mtls_client.py, return parsed JSON."""
cmd = ['python3', 'tools/vz_mtls_client.py'] + list(args)
try:
result = subprocess.run(
cmd, capture_output=True, text=True,
cwd=os.path.expanduser('~/vector-zulu'),
timeout=30
)
lines = [l for l in result.stdout.strip().split('\n')
if not l.startswith('[OK]') and not l.startswith('[FAIL]')]
return json.loads('\n'.join(lines)) if lines else {}
except Exception as e:
return {'error': str(e)}
def _get(jwt: str, path: str, base: str = None) -> dict:
for url_base in ([base] if base else [API_PRIMARY, API_FALLBACK, RAILS_URL]):
try:
resp = _session(jwt).get(f'{url_base}{path}', timeout=15)
if resp.status_code == 200:
return resp.json()
except Exception as e:
log.debug(f"GET {path} from {url_base}: {e}")
return {}
def _post(jwt: str, path: str, body: dict, lick_sign: bool = True) -> dict:
headers = build_headers(body) if lick_sign else {}
for url_base in [API_PRIMARY, API_FALLBACK]:
try:
s = _session(jwt)
if headers:
s.headers.update(headers)
resp = s.post(f'{url_base}{path}', json=body, timeout=30)
if resp.status_code in (200, 201, 202):
return resp.json() if resp.content else {'ok': True}
log.warning(f"POST {path}: HTTP {resp.status_code}{resp.text[:200]}")
except Exception as e:
log.error(f"POST {path} via {url_base}: {e}")
return {'error': f'All endpoints failed for {path}'}
# ── Feed polling ──────────────────────────────────────────────────────────────
def poll_feed(path: str) -> list:
try:
resp = rails_session().get(f'{RAILS_URL}/{path}', timeout=15)
if resp.status_code == 200:
data = resp.json()
for key in ['payments', 'settlements', 'deals']:
if key in data and data[key]:
return data[key]
except Exception as e:
log.error(f"Feed poll error {path}: {e}")
def poll_feed(jwt: str, path: str) -> list:
"""Poll a KoreNet feed endpoint for new events."""
data = _get(jwt, path)
for key in ['payments', 'settlements', 'deals', 'items', 'events']:
if key in data and data[key]:
return data[key]
# Some feeds return list directly
if isinstance(data, list):
return data
return []
def is_processed(r, event_id: str) -> bool:
return r.exists(f'mirror:processed:{event_id}') == 1
def mark_processed(r, event_id: str):
r.setex(f'mirror:processed:{event_id}', 86400, '1')
# ── UETR validation ───────────────────────────────────────────────────────────
# ── UETR ──────────────────────────────────────────────────────────────────────
def validate_uetr(uetr: str) -> str:
"""
Validate UETR is a proper UUID v4.
Per welcome pack: UETR comes from feed, VZ passes through.
Generate fresh UUID v4 ONLY if feed genuinely lacks one.
"""
"""Validate UETR is UUID v4. Passthrough from feed — only generate if genuinely missing."""
if not uetr:
fresh = str(uuid.uuid4())
log.warning(f"Feed missing UETR — generated fresh: {fresh}")
return fresh
try:
val = uuid.UUID(uetr, version=4)
return str(val).lower()
return str(uuid.UUID(uetr, version=4)).lower()
except ValueError:
fresh = str(uuid.uuid4())
log.warning(f"Invalid UETR {uetr} — generated fresh: {fresh}")
return fresh
# ── KoreID validation ─────────────────────────────────────────────────────────
def validate_inbound_koreid(koreid: str, validator: KoreIDValidator,
eb: EventBusClient, uetr: str) -> bool:
def validate_inbound_koreid(koreid: str, jwt: str, eb: EventBusClient, uetr: str) -> bool:
"""
Validate inbound KoreID from KoreNet feed.
Phase-4: must be genesis_anchored=True to proceed.
Validate inbound KoreID from feed via benjii-prime.korenet.cloud.
Phase-4: must be genesis_anchored=True and integrity_verified=True.
"""
parser = KoreIDParser()
if not parser.is_valid_format(koreid):
@@ -139,249 +158,291 @@ def validate_inbound_koreid(koreid: str, validator: KoreIDValidator,
eb.emit_koreid_invalid(koreid, 'invalid_format', uetr)
return False
result = validator.verify_lineage(koreid)
if not result:
log.error(f"KoreID validation failed (no response): {koreid}")
eb.emit_koreid_invalid(koreid, 'validation_timeout', uetr)
return False
try:
# JWT auth on benjii-prime too
s = _session(jwt)
resp = s.post(f'{BENJII_URL}/api/v1/koreid/verify-lineage',
json={'kore_id': koreid, 'parent': GENESIS_ROOT}, timeout=15)
if resp.status_code != 200:
log.warning(f"benjii-prime returned {resp.status_code} for {koreid}")
# Don't block on API error — log and continue
return True
if not result.get('phase4_compliant'):
reason = result.get('reason', 'unknown')
log.error(f"KoreID not Phase-4 compliant: {koreid}{reason}")
eb.emit_koreid_invalid(koreid, reason, uetr)
return False
result = resp.json()
phase4 = (result.get('integrity_verified') is True and
result.get('genesis_anchored') is True)
if not phase4:
reason = result.get('reason', 'not genesis anchored')
log.error(f"KoreID {koreid} not Phase-4 compliant: {reason}")
eb.emit_koreid_invalid(koreid, reason, uetr)
return False
log.info(f"KoreID validated: {koreid} — genesis anchored ✓")
log.info(f"KoreID {koreid} validated — genesis anchored ✓")
return True
except Exception as e:
log.error(f"KoreID validation error: {e}")
return True # Fail-open on network error to avoid blocking entire pipeline
# ── Sanctions & compliance ────────────────────────────────────────────────────
def sanctions_check(jwt: str, entity: str, jurisdiction: str = 'GLOBAL') -> bool:
"""
Run OFAC/UN/EU sanctions check.
Returns True if CLEAR, False if HIT.
Fail-open on error (log and continue).
"""
body = {'entityName': entity, 'jurisdiction': jurisdiction,
'screeningId': str(uuid.uuid4())}
# Try OFAC first, then general sanctions
for path in ['/api/v1/compliance/ofac/fuzzy-match', '/api/Compliance/sanctions/ofac']:
result = _post(jwt, path, body, lick_sign=False)
if 'error' not in result:
# CLEAR if no hits
hits = result.get('hits', result.get('sanctionsHits', []))
if hits:
log.error(f"SANCTIONS HIT — {entity}: {hits}")
return False
log.info(f"Sanctions clear: {entity}")
return True
log.warning(f"Sanctions check unavailable for {entity} — continuing")
return True
# ── Mirror lifecycle ──────────────────────────────────────────────────────────
def mirror_mint(uetr: str, asset: str, amount: float,
def mirror_mint(jwt: str, uetr: str, asset: str, amount: float,
rail_ref: str, vault: str) -> dict:
log.info(f"MINT {amount} {asset} UETR:{uetr}")
return run_tool('mirror', 'mint',
'--uetr', uetr,
'--customer', VZ_TENANT,
'--asset', asset,
'--amount', str(amount),
'--rail-ref', rail_ref,
'--vault', vault)
body = {
'uetr': uetr, 'customer': TENANT_ID, 'asset': asset,
'amount': amount, 'railRef': rail_ref, 'destinationVault': vault,
}
return _post(jwt, '/api/v1/payment-mirror/mint', body)
def mirror_settle(uetr: str) -> dict:
def mirror_settle(jwt: str, uetr: str) -> dict:
log.info(f"SETTLE UETR:{uetr}")
return run_tool('mirror', 'settle',
'--uetr', uetr,
'--settled-at', datetime.now(timezone.utc).isoformat())
body = {'uetr': uetr, 'settledAt': datetime.now(timezone.utc).isoformat()}
return _post(jwt, '/api/v1/payment-mirror/settle', body)
def mirror_redeem(uetr: str, amount: float) -> dict:
def mirror_redeem(jwt: str, uetr: str, amount: float) -> dict:
log.info(f"REDEEM {amount} UETR:{uetr}")
return run_tool('mirror', 'redeem',
'--uetr', uetr,
'--amount', str(amount))
body = {'uetr': uetr, 'amount': amount}
return _post(jwt, '/api/v1/payment-mirror/redeem', body)
def mirror_burn_sell(uetr: str, amount: float) -> dict:
def mirror_burn_sell(jwt: str, uetr: str, amount: float) -> dict:
for venue in VENUES:
log.info(f"BURN-SELL via {venue} UETR:{uetr}")
result = run_tool('mirror', 'burn-sell',
'--uetr', uetr,
'--amount', str(amount),
'--venue', venue)
body = {'uetr': uetr, 'tokenAmount': amount, 'preferredVenue': venue}
result = _post(jwt, '/api/v1/payment-mirror/burn-sell', body)
if 'error' not in result:
return result
log.warning(f"Venue {venue} failed")
return {'error': 'All venues failed'}
def mirror_close(uetr: str) -> dict:
def mirror_close(jwt: str, uetr: str) -> dict:
log.info(f"CLOSE UETR:{uetr}")
return run_tool('mirror', 'close', '--uetr', uetr)
return _post(jwt, f'/api/v1/payment-mirror/{uetr}/close', {})
# ── Ethereum anchoring ────────────────────────────────────────────────────────
def anchor_to_ethereum(koreid: str, uetr: str, amount: float,
currency: str, step: str, gen: KoreIDGenerator) -> str:
def queue_anchor(r, koreid: str, linked_to: str, uetr: str,
step: str, amount: float, currency: str, gen: KoreIDGenerator):
"""
Anchor KoreNet event to Ethereum.
Generates anchor KoreID linked to original.
TODO: Call KoreAnchor.sol once deployed.
Queue an Ethereum anchor confirmation for the KoreChain poster.
Generates anchor KoreID linked to the original.
"""
import hashlib
# Placeholder tx hash until real Ethereum tx is submitted
placeholder_tx = f"0x{hashlib.sha256(f'{koreid}{step}{uetr}'.encode()).hexdigest()}"
anchor_kid = gen.for_ethereum_anchor(
original_koreid=koreid,
tx_hash=f"pending_{uetr}_{step}",
original_koreid=linked_to,
tx_hash=placeholder_tx,
block_number=0,
event_type=step
)
log.info(f"Anchor KoreID: {anchor_kid['koreid']}{koreid}")
# TODO: Call KoreAnchor.sol contract
# tx = anchor_contract.anchor(koreid, anchor_kid['hash'], amount)
placeholder_hash = f"0x{hashlib.sha256(f'{koreid}{step}'.encode()).hexdigest()}"
return placeholder_hash
payload = {
'koreid': anchor_kid['koreid'],
'guid': anchor_kid['guid'],
'linked_to': linked_to,
'uetr': uetr,
'ethereum_tx': placeholder_tx,
'step': step,
'amount': amount,
'currency': currency,
'anchored_at': datetime.now(timezone.utc).isoformat(),
'genesis_root': GENESIS_ROOT,
}
r.lpush('korechain:outbound_queue', json.dumps(payload))
log.info(f"Queued anchor: {anchor_kid['koreid']}{linked_to}")
return anchor_kid['koreid'], placeholder_tx
def trigger_oracle_update():
"""Immediately trigger oracle transmit when VZ vault balance changes."""
"""Trigger out-of-cycle oracle transmit when VZ vault balance changes."""
import subprocess
log.info("Triggering immediate oracle transmit")
try:
subprocess.Popen(
['node', '/opt/eth-deploy-v21/manual_transmit.js'],
stdout=open('/home/nigel/vz-transmit.log', 'a'),
stdout=open('/home/constellationnode/vz-transmit.log', 'a'),
stderr=subprocess.STDOUT
)
except Exception as e:
log.error(f"Oracle trigger failed: {e}")
# ── Post confirmation to KoreChain ────────────────────────────────────────────
def post_confirmation(original_koreid: str, tx_hash: str, uetr: str,
step: str, gen: KoreIDGenerator, r) -> str:
"""
Post Ethereum anchor confirmation back to KoreChain.
Queued in Redis until korechain.korenet.cloud is accessible.
"""
anchor_kid = gen.for_ethereum_anchor(
original_koreid=original_koreid,
tx_hash=tx_hash,
block_number=0,
event_type=step
)
confirmation = {
'koreid': anchor_kid['koreid'],
'linked_to': original_koreid,
'uetr': uetr,
'ethereum_tx': tx_hash,
'step': step,
'anchored_at': datetime.now(timezone.utc).isoformat(),
'genesis_root': GENESIS_ROOT,
}
r.lpush('korechain:outbound_queue', json.dumps(confirmation))
log.info(f"Queued confirmation: {anchor_kid['koreid']}")
return anchor_kid['koreid']
# ── Main event processor ──────────────────────────────────────────────────────
def process_event(event: dict, r, pg,
gen: KoreIDGenerator,
validator: KoreIDValidator,
eb: EventBusClient):
"""Process a single payment/deal/settlement event."""
def process_event(event: dict, r, pg, jwt: str,
gen: KoreIDGenerator, eb: EventBusClient):
"""Process a single payment/deal/settlement event through full mirror lifecycle."""
event_id = event.get('id') or event.get('dealId') or event.get('paymentId', '')
koreid = event.get('koreId') or event.get('kore_id', '')
amount = float(event.get('amount', 0))
currency = event.get('currency', 'USD')
entity = event.get('counterparty') or event.get('customer', 'UNKNOWN')
rail_ref = event.get('railRef') or event.get('reference', f'REF-{event_id}')
vault = f"VZ-{currency}-VAULT" if f"VZ-{currency}-VAULT" in VZ_VAULTS else 'VZ-USD-VAULT'
# UETR — comes from feed per welcome pack
uetr = validate_uetr(
event_id = (event.get('id') or event.get('dealId') or
event.get('paymentId') or event.get('koreId', ''))
koreid = event.get('koreId') or event.get('kore_id', '')
amount = float(event.get('amount', 0))
currency = event.get('currency', 'USD').upper()
entity = event.get('counterparty') or event.get('customer', 'UNKNOWN')
rail_ref = (event.get('railRef') or event.get('reference') or
event.get('swiftRef') or f'REF-{event_id}')
vault = f"VZ-{currency}-VAULT" if f"VZ-{currency}-VAULT" in VZ_VAULTS else 'VZ-USD-VAULT'
uetr = validate_uetr(
event.get('uetr') or event.get('railRef') or event.get('swiftRef', '')
)
if not event_id or is_processed(r, event_id):
return
log.info(f"Processing: {event_id} | {currency} {amount:,.2f} | UETR: {uetr}")
correlation_id = str(uuid.uuid4())
log.info(f"Processing: {event_id} | {currency} {amount:,.2f} | UETR:{uetr}")
try:
# Step 0a: Validate inbound KoreID
if koreid and not validate_inbound_koreid(koreid, validator, eb, uetr):
if koreid and not validate_inbound_koreid(koreid, jwt, eb, uetr):
log.error(f"Rejecting {event_id} — invalid KoreID")
mark_processed(r, event_id)
return
# Step 0b: Sanctions check
sanctions = run_tool('oracle', 'sanctions',
'--entity', entity,
'--jurisdiction', 'ZA')
if sanctions.get('status') != 'CLEAR':
log.error(f"SANCTIONS HIT — {entity}")
eb.emit_sanctions_hit(entity, 'ZA', uetr)
if not sanctions_check(jwt, entity):
eb.emit_sanctions_hit(entity, 'GLOBAL', uetr)
mark_processed(r, event_id)
return
# Step 0c: Entity enrichment
run_tool('oracle', 'enrich', '--entity', entity,
'--type', 'BENEFICIARY', '--jurisdiction', 'ZA')
parent = koreid or GENESIS_ROOT
# Step 1: Mint
mint_result = mirror_mint(uetr, currency, amount, rail_ref, vault)
mint_result = mirror_mint(jwt, uetr, currency, amount, rail_ref, vault)
if 'error' in mint_result:
log.error(f"MINT failed: {mint_result.get('error')}")
eb.emit_mirror_failed('mint', uetr, str(mint_result.get('error')))
return
mint_kid = gen.for_mirror_mint(uetr, amount, currency, vault,
parent=koreid or GENESIS_ROOT)
tx = anchor_to_ethereum(mint_kid['koreid'], uetr, amount, currency, 'MINT', gen)
post_confirmation(mint_kid['koreid'], tx, uetr, 'MINT', gen, r)
mint_kid = gen.for_mirror_mint(uetr, amount, currency, vault, parent=parent)
anchor_kid, tx = queue_anchor(r, mint_kid['koreid'], mint_kid['koreid'],
uetr, 'MINT', amount, currency, gen)
eb.emit_mirror_success('mint', uetr, mint_kid['koreid'], amount, currency)
log.info(f"MINT ✓ {mint_kid['koreid']}")
# Step 2: Settle
settle_result = mirror_settle(uetr)
mirror_settle(jwt, uetr)
settle_kid = gen.for_mirror_settle(uetr, parent_koreid=mint_kid['koreid'])
tx = anchor_to_ethereum(settle_kid['koreid'], uetr, amount, currency, 'SETTLE', gen)
post_confirmation(settle_kid['koreid'], tx, uetr, 'SETTLE', gen, r)
queue_anchor(r, settle_kid['koreid'], settle_kid['koreid'],
uetr, 'SETTLE', amount, currency, gen)
eb.emit_mirror_success('settle', uetr, settle_kid['koreid'], amount, currency)
log.info(f"SETTLE ✓ {settle_kid['koreid']}")
# Step 3: Redeem
redeem_result = mirror_redeem(uetr, amount)
mirror_redeem(jwt, uetr, amount)
redeem_kid = gen.for_mirror_redeem(uetr, amount, parent_koreid=settle_kid['koreid'])
tx = anchor_to_ethereum(redeem_kid['koreid'], uetr, amount, currency, 'REDEEM', gen)
post_confirmation(redeem_kid['koreid'], tx, uetr, 'REDEEM', gen, r)
queue_anchor(r, redeem_kid['koreid'], redeem_kid['koreid'],
uetr, 'REDEEM', amount, currency, gen)
eb.emit_mirror_success('redeem', uetr, redeem_kid['koreid'], amount, currency)
log.info(f"REDEEM ✓ {redeem_kid['koreid']}")
# Oracle update if VZ vault affected
# Trigger oracle update after vault balance changes
if vault in VZ_VAULTS:
trigger_oracle_update()
# Step 4: Burn-sell
burn_result = mirror_burn_sell(uetr, amount)
venue = burn_result.get('venue', 'unknown')
burn_result = mirror_burn_sell(jwt, uetr, amount)
venue = burn_result.get('venue', burn_result.get('preferredVenue', 'cryptohost'))
burn_kid = gen.for_mirror_burn(uetr, amount, venue,
parent_koreid=redeem_kid['koreid'])
tx = anchor_to_ethereum(burn_kid['koreid'], uetr, amount, currency, 'BURN', gen)
post_confirmation(burn_kid['koreid'], tx, uetr, 'BURN', gen, r)
parent_koreid=redeem_kid['koreid'])
queue_anchor(r, burn_kid['koreid'], burn_kid['koreid'],
uetr, 'BURN', amount, currency, gen)
eb.emit_mirror_success('burn', uetr, burn_kid['koreid'], amount, currency)
log.info(f"BURN ✓ {burn_kid['koreid']} via {venue}")
# Step 5: Close
close_result = mirror_close(uetr)
mirror_close(jwt, uetr)
close_kid = gen.for_mirror_close(uetr, parent_koreid=burn_kid['koreid'])
tx = anchor_to_ethereum(close_kid['koreid'], uetr, amount, currency, 'CLOSE', gen)
post_confirmation(close_kid['koreid'], tx, uetr, 'CLOSE', gen, r)
queue_anchor(r, close_kid['koreid'], close_kid['koreid'],
uetr, 'CLOSE', amount, currency, gen)
eb.emit_mirror_success('close', uetr, close_kid['koreid'], amount, currency)
log.info(f"CLOSE ✓ {close_kid['koreid']}")
mark_processed(r, event_id)
log.info(f"✓ Complete: {event_id} | {currency} {amount:,.2f} | UETR: {uetr}")
log.info(f"✓ COMPLETE: {event_id} | {currency} {amount:,.2f} | UETR:{uetr}")
except Exception as e:
log.error(f"Processing error {event_id}: {e}")
eb.emit(EventType.ENDPOINT_DOWN,
{'event_id': event_id, 'error': str(e)},
Severity.HIGH)
log.error(f"Processing error {event_id}: {e}", exc_info=True)
eb.emit(EventType.ENDPOINT_DOWN, {'event_id': event_id, 'error': str(e)}, Severity.HIGH)
# ── Main loop ─────────────────────────────────────────────────────────────────
FEED_PATHS = [
'/api/v1/tenants/portal/payments/feed',
'/api/v1/tenants/portal/settlements/feed',
'/api/v1/tenants/deals/feed',
'/api/v1/payment/pending',
'/api/v1/payments/pending',
]
def run():
log.info("VZ Portal Feed Poller v2.0 starting...")
log.info(f"Rails: {RAILS_URL} | API: {API_URL} | Poll: {POLL_INTERVAL}s")
log.info("VZ Portal Feed Poller v3.0 starting...")
log.info(f"Primary: {API_PRIMARY} | Fallback: {API_FALLBACK} | Poll: {POLL_INTERVAL}s")
r = get_redis()
pg = get_pg()
gen = KoreIDGenerator()
validator = KoreIDValidator(CERT, KEY)
eb = EventBusClient()
while True:
payments = poll_feed('api/v1/tenants/portal/payments/feed')
settlements = poll_feed('api/v1/tenants/portal/settlements/feed')
deals = poll_feed('api/v1/tenants/deals/feed')
jwt = load_jwt()
if not jwt:
log.error("No JWT available — waiting 60s")
time.sleep(60)
continue
all_events = payments + settlements + deals
all_events = []
for path in FEED_PATHS:
events = poll_feed(jwt, path)
all_events.extend(events)
if all_events:
log.info(f"Found {len(all_events)} events")
for event in all_events:
process_event(event, r, pg, gen, validator, eb)
# Deduplicate by event ID
seen = set()
unique_events = []
for ev in all_events:
eid = ev.get('id') or ev.get('dealId') or ev.get('paymentId') or ev.get('koreId', '')
if eid and eid not in seen:
seen.add(eid)
unique_events.append(ev)
if unique_events:
log.info(f"Found {len(unique_events)} unique events")
for event in unique_events:
process_event(event, r, pg, jwt, gen, eb)
else:
log.debug("No new events")