Update portal_feed_poller.py — correct KoreID format + EventBus + v2 poller
This commit is contained in:
@@ -1,43 +1,36 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Vector Zulu — Payment Mirror Adapter
|
Vector Zulu — Portal Feed Poller v2.0
|
||||||
Polls KoreNet portal feeds for new payment events and executes the full
|
Polls KoreNet portal feeds for new payment/settlement/deal events
|
||||||
payment mirror lifecycle via api.korenet.cloud.
|
and executes the full payment mirror lifecycle.
|
||||||
|
|
||||||
Full lifecycle per VZ runbook:
|
Key facts from VZ-Welcome-Pack FINAL v1.0 (2026-07-08):
|
||||||
INCOMING SWIFT MT103/PACS.008
|
- UETR comes FROM the payment feed (SWIFT gpi UUID v4) — never mint a new one
|
||||||
→ MINT mirror tokens
|
- Auth-code format: AUTH-VZ-MINT-<period> e.g. AUTH-VZ-MINT-2026Q2
|
||||||
→ SETTLE T+0
|
- Lifecycle keyed by SAME UETR: mint → settle → redeem → burn-sell → close
|
||||||
→ REDEEM fiat
|
- Every step LICK-signed
|
||||||
→ BURN-SELL via venue routing
|
- KoreID validated via benjii-prime.korenet.cloud/api/v1/koreid/verify-lineage
|
||||||
→ CLOSE tranche
|
- EventBus topic: integration-partners (VZ's own scoped topic)
|
||||||
→ ANCHOR to Ethereum (ISO 20022 proof)
|
|
||||||
→ POST confirmation KoreID back to KoreChain
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- vz_mtls_client.py (Bryan's official client)
|
|
||||||
- koreid.py (KoreID generator)
|
|
||||||
- Redis (event deduplication)
|
|
||||||
- Postgres (audit trail)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import hashlib
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
import redis
|
import redis
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import urllib3
|
import urllib3
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from koreid import KoreIDGenerator, wrap_ethereum_event
|
from koreid_new import KoreIDGenerator, KoreIDValidator, KoreIDParser, KoreType, KoreDomain, GENESIS_ROOT
|
||||||
|
from eventbus import EventBusClient, Severity, EventType
|
||||||
|
|
||||||
urllib3.disable_warnings()
|
urllib3.disable_warnings()
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s [MIRROR] %(levelname)s %(message)s'
|
format='%(asctime)s [POLLER-V2] %(levelname)s %(message)s'
|
||||||
)
|
)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -48,13 +41,10 @@ KEY = os.environ.get('VECTOR_ZULU_KEY', '/certs/vector-zulu-client.key
|
|||||||
RAILS_URL = os.environ.get('VECTOR_ZULU_RAILS_URL', 'https://vector.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')
|
API_URL = os.environ.get('VECTOR_ZULU_API_URL', 'https://api.korenet.cloud')
|
||||||
POLL_INTERVAL = int(os.environ.get('POLL_INTERVAL', '30'))
|
POLL_INTERVAL = int(os.environ.get('POLL_INTERVAL', '30'))
|
||||||
|
AUTH_CODE = os.environ.get('VZ_MINT_AUTH_CODE', 'AUTH-VZ-MINT-2026Q2')
|
||||||
# Venue routing priority (per runbook)
|
VZ_TENANT = 'vector-zulu'
|
||||||
VENUES = ['cryptohost', 'koreexchange', 'uniswap']
|
VZ_VAULTS = ['VZ-USD-VAULT', 'VZ-USDT-VAULT', 'VZ-BTC-VAULT']
|
||||||
|
VENUES = ['cryptohost', 'koreexchange', 'uniswap']
|
||||||
# VZ vault IDs
|
|
||||||
VZ_VAULTS = ['VZ-USD-VAULT', 'VZ-USDT-VAULT', 'VZ-BTC-VAULT']
|
|
||||||
|
|
||||||
|
|
||||||
# ── Infrastructure ────────────────────────────────────────────────────────────
|
# ── Infrastructure ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -64,7 +54,6 @@ def get_redis():
|
|||||||
port=6379, decode_responses=True
|
port=6379, decode_responses=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_pg():
|
def get_pg():
|
||||||
return psycopg2.connect(
|
return psycopg2.connect(
|
||||||
host=os.environ.get('POSTGRES_HOST', 'localhost'),
|
host=os.environ.get('POSTGRES_HOST', 'localhost'),
|
||||||
@@ -74,17 +63,15 @@ def get_pg():
|
|||||||
connect_timeout=10
|
connect_timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def rails_session():
|
||||||
def session():
|
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
s.cert = (CERT, KEY)
|
s.cert = (CERT, KEY)
|
||||||
s.verify = False
|
s.verify = False
|
||||||
s.headers = {'X-Tenant-Id': 'vector-zulu'}
|
s.headers = {'X-Tenant-Id': VZ_TENANT}
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def run_tool(*args) -> dict:
|
def run_tool(*args) -> dict:
|
||||||
"""Run vz_mtls_client.py with given args, return parsed JSON."""
|
"""Run vz_mtls_client.py, return parsed JSON."""
|
||||||
cmd = ['python3', 'tools/vz_mtls_client.py'] + list(args)
|
cmd = ['python3', 'tools/vz_mtls_client.py'] + list(args)
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@@ -92,24 +79,19 @@ def run_tool(*args) -> dict:
|
|||||||
cwd=os.path.expanduser('~/vector-zulu'),
|
cwd=os.path.expanduser('~/vector-zulu'),
|
||||||
timeout=30
|
timeout=30
|
||||||
)
|
)
|
||||||
# Strip [OK]/[FAIL] prefix line
|
lines = [l for l in result.stdout.strip().split('\n')
|
||||||
lines = result.stdout.strip().split('\n')
|
if not l.startswith('[OK]') and not l.startswith('[FAIL]')]
|
||||||
json_lines = [l for l in lines if not l.startswith('[OK]') and not l.startswith('[FAIL]')]
|
return json.loads('\n'.join(lines)) if lines else {}
|
||||||
return json.loads('\n'.join(json_lines))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Tool error: {e}")
|
|
||||||
return {'error': str(e)}
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
|
||||||
# ── Feed polling ──────────────────────────────────────────────────────────────
|
# ── Feed polling ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def poll_feed(path: str) -> list:
|
def poll_feed(path: str) -> list:
|
||||||
"""Poll a KoreNet portal feed, return new items."""
|
|
||||||
try:
|
try:
|
||||||
resp = session().get(f'{RAILS_URL}/{path}', timeout=15)
|
resp = rails_session().get(f'{RAILS_URL}/{path}', timeout=15)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
# Return whichever list field is present
|
|
||||||
for key in ['payments', 'settlements', 'deals']:
|
for key in ['payments', 'settlements', 'deals']:
|
||||||
if key in data and data[key]:
|
if key in data and data[key]:
|
||||||
return data[key]
|
return data[key]
|
||||||
@@ -117,266 +99,279 @@ def poll_feed(path: str) -> list:
|
|||||||
log.error(f"Feed poll error {path}: {e}")
|
log.error(f"Feed poll error {path}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def is_processed(r, event_id: str) -> bool:
|
def is_processed(r, event_id: str) -> bool:
|
||||||
"""Check Redis deduplication cache."""
|
|
||||||
return r.exists(f'mirror:processed:{event_id}') == 1
|
return r.exists(f'mirror:processed:{event_id}') == 1
|
||||||
|
|
||||||
|
|
||||||
def mark_processed(r, event_id: str):
|
def mark_processed(r, event_id: str):
|
||||||
"""Mark event as processed in Redis (24h TTL)."""
|
|
||||||
r.setex(f'mirror:processed:{event_id}', 86400, '1')
|
r.setex(f'mirror:processed:{event_id}', 86400, '1')
|
||||||
|
|
||||||
|
# ── UETR validation ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# ── Compliance checks ─────────────────────────────────────────────────────────
|
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.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
except ValueError:
|
||||||
|
fresh = str(uuid.uuid4())
|
||||||
|
log.warning(f"Invalid UETR {uetr} — generated fresh: {fresh}")
|
||||||
|
return fresh
|
||||||
|
|
||||||
def sanctions_check(entity: str, jurisdiction: str = 'ZA') -> bool:
|
# ── KoreID validation ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def validate_inbound_koreid(koreid: str, validator: KoreIDValidator,
|
||||||
|
eb: EventBusClient, uetr: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Run sanctions screening before any mirror operation.
|
Validate inbound KoreID from KoreNet feed.
|
||||||
Returns True if CLEAR, False if HIT.
|
Phase-4: must be genesis_anchored=True to proceed.
|
||||||
"""
|
"""
|
||||||
result = run_tool('oracle', 'sanctions',
|
parser = KoreIDParser()
|
||||||
'--entity', entity,
|
if not parser.is_valid_format(koreid):
|
||||||
'--jurisdiction', jurisdiction)
|
log.error(f"Invalid KoreID format: {koreid}")
|
||||||
status = result.get('status', 'UNKNOWN')
|
eb.emit_koreid_invalid(koreid, 'invalid_format', uetr)
|
||||||
if status == 'CLEAR':
|
|
||||||
log.info(f"Sanctions CLEAR: {entity}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
log.warning(f"Sanctions HIT: {entity} — {status}")
|
|
||||||
return False
|
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
|
||||||
|
|
||||||
def entity_enrich(entity: str, entity_type: str = 'BENEFICIARY',
|
if not result.get('phase4_compliant'):
|
||||||
jurisdiction: str = 'ZA') -> dict:
|
reason = result.get('reason', 'unknown')
|
||||||
"""Enrich entity data before mirror mint."""
|
log.error(f"KoreID not Phase-4 compliant: {koreid} — {reason}")
|
||||||
return run_tool('oracle', 'enrich',
|
eb.emit_koreid_invalid(koreid, reason, uetr)
|
||||||
'--entity', entity,
|
return False
|
||||||
'--type', entity_type,
|
|
||||||
'--jurisdiction', jurisdiction)
|
|
||||||
|
|
||||||
|
log.info(f"KoreID validated: {koreid} — genesis anchored ✓")
|
||||||
|
return True
|
||||||
|
|
||||||
# ── Payment mirror lifecycle ──────────────────────────────────────────────────
|
# ── Mirror lifecycle ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def mirror_mint(uetr: str, customer: str, asset: str,
|
def mirror_mint(uetr: str, asset: str, amount: float,
|
||||||
amount: float, rail_ref: str, vault: str) -> dict:
|
rail_ref: str, vault: str) -> dict:
|
||||||
"""
|
log.info(f"MINT {amount} {asset} UETR:{uetr}")
|
||||||
Step 1: Mint mirror tokens against incoming SWIFT payment.
|
|
||||||
"""
|
|
||||||
log.info(f"MINT {amount} {asset} — UETR: {uetr}")
|
|
||||||
return run_tool('mirror', 'mint',
|
return run_tool('mirror', 'mint',
|
||||||
'--uetr', uetr,
|
'--uetr', uetr,
|
||||||
'--customer', customer,
|
'--customer', VZ_TENANT,
|
||||||
'--asset', asset,
|
'--asset', asset,
|
||||||
'--amount', str(amount),
|
'--amount', str(amount),
|
||||||
'--rail-ref', rail_ref,
|
'--rail-ref', rail_ref,
|
||||||
'--vault', vault)
|
'--vault', vault)
|
||||||
|
|
||||||
|
|
||||||
def mirror_settle(uetr: str) -> dict:
|
def mirror_settle(uetr: str) -> dict:
|
||||||
"""Step 2: Record T+0 settlement."""
|
log.info(f"SETTLE UETR:{uetr}")
|
||||||
log.info(f"SETTLE — UETR: {uetr}")
|
|
||||||
settled_at = datetime.now(timezone.utc).isoformat()
|
|
||||||
return run_tool('mirror', 'settle',
|
return run_tool('mirror', 'settle',
|
||||||
'--uetr', uetr,
|
'--uetr', uetr,
|
||||||
'--settled-at', settled_at)
|
'--settled-at', datetime.now(timezone.utc).isoformat())
|
||||||
|
|
||||||
|
|
||||||
def mirror_redeem(uetr: str, amount: float) -> dict:
|
def mirror_redeem(uetr: str, amount: float) -> dict:
|
||||||
"""Step 3: Redeem fiat from VZ vault."""
|
log.info(f"REDEEM {amount} UETR:{uetr}")
|
||||||
log.info(f"REDEEM {amount} — UETR: {uetr}")
|
|
||||||
return run_tool('mirror', 'redeem',
|
return run_tool('mirror', 'redeem',
|
||||||
'--uetr', uetr,
|
'--uetr', uetr,
|
||||||
'--amount', str(amount))
|
'--amount', str(amount))
|
||||||
|
|
||||||
|
|
||||||
def mirror_burn_sell(uetr: str, amount: float) -> dict:
|
def mirror_burn_sell(uetr: str, amount: float) -> dict:
|
||||||
"""
|
|
||||||
Step 4: Burn-sell via venue routing.
|
|
||||||
Tries CryptoHost → KoreExchange → Uniswap per runbook.
|
|
||||||
"""
|
|
||||||
for venue in VENUES:
|
for venue in VENUES:
|
||||||
log.info(f"BURN-SELL via {venue} — UETR: {uetr}")
|
log.info(f"BURN-SELL via {venue} UETR:{uetr}")
|
||||||
result = run_tool('mirror', 'burn-sell',
|
result = run_tool('mirror', 'burn-sell',
|
||||||
'--uetr', uetr,
|
'--uetr', uetr,
|
||||||
'--amount', str(amount),
|
'--amount', str(amount),
|
||||||
'--venue', venue)
|
'--venue', venue)
|
||||||
if 'error' not in result:
|
if 'error' not in result:
|
||||||
log.info(f"Burn-sell success via {venue}")
|
|
||||||
return result
|
return result
|
||||||
log.warning(f"Venue {venue} failed, trying next")
|
log.warning(f"Venue {venue} failed")
|
||||||
return {'error': 'All venues failed'}
|
return {'error': 'All venues failed'}
|
||||||
|
|
||||||
|
|
||||||
def mirror_close(uetr: str) -> dict:
|
def mirror_close(uetr: str) -> dict:
|
||||||
"""Step 5: Close the tranche."""
|
log.info(f"CLOSE UETR:{uetr}")
|
||||||
log.info(f"CLOSE — UETR: {uetr}")
|
|
||||||
return run_tool('mirror', 'close', '--uetr', uetr)
|
return run_tool('mirror', 'close', '--uetr', uetr)
|
||||||
|
|
||||||
|
|
||||||
# ── Ethereum anchoring ────────────────────────────────────────────────────────
|
# ── Ethereum anchoring ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def anchor_to_ethereum(kore_id: str, uetr: str, amount: float,
|
def anchor_to_ethereum(koreid: str, uetr: str, amount: float,
|
||||||
currency: str, event_type: str) -> str:
|
currency: str, step: str, gen: KoreIDGenerator) -> str:
|
||||||
"""
|
"""
|
||||||
Anchor KoreNet event to Ethereum mainnet.
|
Anchor KoreNet event to Ethereum.
|
||||||
Returns Ethereum TX hash.
|
Generates anchor KoreID linked to original.
|
||||||
TODO: Deploy KoreAnchor.sol contract and call it here.
|
TODO: Call KoreAnchor.sol once deployed.
|
||||||
For now, stores in oracle transmit log.
|
|
||||||
"""
|
"""
|
||||||
payload = {
|
import hashlib
|
||||||
'koreId': kore_id,
|
anchor_kid = gen.for_ethereum_anchor(
|
||||||
'uetr': uetr,
|
original_koreid=koreid,
|
||||||
'amount': amount,
|
tx_hash=f"pending_{uetr}_{step}",
|
||||||
'currency': currency,
|
block_number=0,
|
||||||
'eventType': event_type,
|
event_type=step
|
||||||
'timestamp': datetime.now(timezone.utc).isoformat(),
|
)
|
||||||
'hash': hashlib.sha256(
|
log.info(f"Anchor KoreID: {anchor_kid['koreid']} → {koreid}")
|
||||||
json.dumps({'koreId': kore_id, 'uetr': uetr, 'amount': amount},
|
|
||||||
sort_keys=True).encode()
|
|
||||||
).hexdigest()
|
|
||||||
}
|
|
||||||
log.info(f"Anchoring to Ethereum: {kore_id} — {event_type}")
|
|
||||||
# TODO: Call KoreAnchor.sol contract
|
# TODO: Call KoreAnchor.sol contract
|
||||||
# tx_hash = kore_anchor_contract.anchor(payload)
|
# tx = anchor_contract.anchor(koreid, anchor_kid['hash'], amount)
|
||||||
# return tx_hash
|
placeholder_hash = f"0x{hashlib.sha256(f'{koreid}{step}'.encode()).hexdigest()}"
|
||||||
return f"0x{payload['hash'][:64]}" # placeholder until contract deployed
|
return placeholder_hash
|
||||||
|
|
||||||
|
|
||||||
def trigger_oracle_update():
|
def trigger_oracle_update():
|
||||||
"""Immediately trigger oracle transmit when VZ vault balance changes."""
|
"""Immediately trigger oracle transmit when VZ vault balance changes."""
|
||||||
log.info("Triggering immediate oracle update")
|
log.info("Triggering immediate oracle transmit")
|
||||||
try:
|
try:
|
||||||
import subprocess
|
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
['node', '/opt/eth-deploy-v21/manual_transmit.js'],
|
['node', '/opt/eth-deploy-v21/manual_transmit.js'],
|
||||||
stdout=open('/home/nigel/vz-transmit.log', 'a'),
|
stdout=open('/home/nigel/vz-transmit.log', 'a'),
|
||||||
stderr=subprocess.STDOUT
|
stderr=subprocess.STDOUT
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Oracle update failed: {e}")
|
log.error(f"Oracle trigger failed: {e}")
|
||||||
|
|
||||||
|
# ── Post confirmation to KoreChain ────────────────────────────────────────────
|
||||||
|
|
||||||
# ── KoreID confirmation ───────────────────────────────────────────────────────
|
def post_confirmation(original_koreid: str, tx_hash: str, uetr: str,
|
||||||
|
step: str, gen: KoreIDGenerator, r) -> str:
|
||||||
def post_confirmation(original_kore_id: str, tx_hash: str,
|
|
||||||
uetr: str, event_type: str, pg) -> dict:
|
|
||||||
"""
|
"""
|
||||||
Post Ethereum anchor confirmation back to KoreChain.
|
Post Ethereum anchor confirmation back to KoreChain.
|
||||||
Generates a confirmation KoreID linked to the original.
|
Queued in Redis until korechain.korenet.cloud is accessible.
|
||||||
TODO: POST to korechain.korenet.cloud/api/v1/anchor once G2 cert trusted.
|
|
||||||
"""
|
"""
|
||||||
gen = KoreIDGenerator(pg)
|
anchor_kid = gen.for_ethereum_anchor(
|
||||||
payload = gen.generate(
|
original_koreid=original_koreid,
|
||||||
event_type=f'{event_type}_CONFIRM',
|
tx_hash=tx_hash,
|
||||||
currency='USD',
|
block_number=0,
|
||||||
current_network='ETHEREUM',
|
event_type=step
|
||||||
parent_network='KORECHAIN',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
confirmation = {
|
confirmation = {
|
||||||
'koreid': payload['koreid'],
|
'koreid': anchor_kid['koreid'],
|
||||||
'linked_to': original_kore_id,
|
'linked_to': original_koreid,
|
||||||
'uetr': uetr,
|
'uetr': uetr,
|
||||||
'ethereum_tx': tx_hash,
|
'ethereum_tx': tx_hash,
|
||||||
'event_type': f'{event_type}_CONFIRM',
|
'step': step,
|
||||||
'anchored_at': datetime.now(timezone.utc).isoformat(),
|
'anchored_at': datetime.now(timezone.utc).isoformat(),
|
||||||
'proof': {
|
'genesis_root': GENESIS_ROOT,
|
||||||
'hash': tx_hash,
|
|
||||||
'network': 'ETHEREUM_MAINNET',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(f"Confirmation KoreID: {payload['koreid']} → {original_kore_id}")
|
|
||||||
|
|
||||||
# TODO: POST to korechain.korenet.cloud once G2 cert trusted
|
|
||||||
# session().post('https://korechain.korenet.cloud/api/v1/anchor', json=confirmation)
|
|
||||||
|
|
||||||
# Queue for when endpoint is available
|
|
||||||
r = get_redis()
|
|
||||||
r.lpush('korechain:outbound_queue', json.dumps(confirmation))
|
r.lpush('korechain:outbound_queue', json.dumps(confirmation))
|
||||||
|
log.info(f"Queued confirmation: {anchor_kid['koreid']}")
|
||||||
|
return anchor_kid['koreid']
|
||||||
|
|
||||||
return confirmation
|
# ── Main event processor ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def process_event(event: dict, r, pg,
|
||||||
|
gen: KoreIDGenerator,
|
||||||
|
validator: KoreIDValidator,
|
||||||
|
eb: EventBusClient):
|
||||||
|
"""Process a single payment/deal/settlement event."""
|
||||||
|
|
||||||
# ── Main processing ───────────────────────────────────────────────────────────
|
event_id = event.get('id') or event.get('dealId') or event.get('paymentId', '')
|
||||||
|
koreid = event.get('koreId') or event.get('kore_id', '')
|
||||||
def process_payment_event(event: dict, r, pg):
|
|
||||||
"""
|
|
||||||
Process a single payment/deal event from the KoreNet feed.
|
|
||||||
Executes full mirror lifecycle.
|
|
||||||
"""
|
|
||||||
event_id = event.get('id') or event.get('dealId') or event.get('uetr', '')
|
|
||||||
kore_id = event.get('koreId') or event.get('kore_id', '')
|
|
||||||
amount = float(event.get('amount', 0))
|
amount = float(event.get('amount', 0))
|
||||||
currency = event.get('currency', 'USD')
|
currency = event.get('currency', 'USD')
|
||||||
entity = event.get('counterparty') or event.get('customer', 'UNKNOWN')
|
entity = event.get('counterparty') or event.get('customer', 'UNKNOWN')
|
||||||
uetr = event.get('uetr') or event.get('railRef') or event_id
|
|
||||||
rail_ref = event.get('railRef') or event.get('reference', f'REF-{event_id}')
|
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'
|
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.get('uetr') or event.get('railRef') or event.get('swiftRef', '')
|
||||||
|
)
|
||||||
|
|
||||||
if not event_id or is_processed(r, event_id):
|
if not event_id or is_processed(r, event_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info(f"Processing event: {event_id} | {currency} {amount:,.2f} | KoreID: {kore_id}")
|
log.info(f"Processing: {event_id} | {currency} {amount:,.2f} | UETR: {uetr}")
|
||||||
|
|
||||||
|
correlation_id = str(uuid.uuid4())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Step 0: Compliance
|
# Step 0a: Validate inbound KoreID
|
||||||
if not sanctions_check(entity):
|
if koreid and not validate_inbound_koreid(koreid, validator, eb, uetr):
|
||||||
log.error(f"SANCTIONS HIT — {entity} — rejecting {event_id}")
|
log.error(f"Rejecting {event_id} — invalid KoreID")
|
||||||
mark_processed(r, event_id)
|
mark_processed(r, event_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step 1: Mint
|
# Step 0b: Sanctions check
|
||||||
mint_result = mirror_mint(uetr, 'vector-zulu', currency, amount, rail_ref, vault)
|
sanctions = run_tool('oracle', 'sanctions',
|
||||||
if 'error' in mint_result:
|
'--entity', entity,
|
||||||
log.error(f"Mint failed: {mint_result}")
|
'--jurisdiction', 'ZA')
|
||||||
|
if sanctions.get('status') != 'CLEAR':
|
||||||
|
log.error(f"SANCTIONS HIT — {entity}")
|
||||||
|
eb.emit_sanctions_hit(entity, 'ZA', uetr)
|
||||||
|
mark_processed(r, event_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Anchor MINT to Ethereum
|
# Step 0c: Entity enrichment
|
||||||
tx_hash = anchor_to_ethereum(kore_id, uetr, amount, currency, 'MINT')
|
run_tool('oracle', 'enrich', '--entity', entity,
|
||||||
post_confirmation(kore_id, tx_hash, uetr, 'MINT', pg)
|
'--type', 'BENEFICIARY', '--jurisdiction', 'ZA')
|
||||||
|
|
||||||
|
# Step 1: Mint
|
||||||
|
mint_result = mirror_mint(uetr, currency, amount, rail_ref, vault)
|
||||||
|
if 'error' in mint_result:
|
||||||
|
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)
|
||||||
|
eb.emit_mirror_success('mint', uetr, mint_kid['koreid'], amount, currency)
|
||||||
|
|
||||||
# Step 2: Settle
|
# Step 2: Settle
|
||||||
settle_result = mirror_settle(uetr)
|
settle_result = mirror_settle(uetr)
|
||||||
tx_hash = anchor_to_ethereum(kore_id, uetr, amount, currency, 'SETTLE')
|
settle_kid = gen.for_mirror_settle(uetr, parent_koreid=mint_kid['koreid'])
|
||||||
post_confirmation(kore_id, tx_hash, uetr, 'SETTLE', pg)
|
tx = anchor_to_ethereum(settle_kid['koreid'], uetr, amount, currency, 'SETTLE', gen)
|
||||||
|
post_confirmation(settle_kid['koreid'], tx, uetr, 'SETTLE', gen, r)
|
||||||
|
eb.emit_mirror_success('settle', uetr, settle_kid['koreid'], amount, currency)
|
||||||
|
|
||||||
# Step 3: Redeem
|
# Step 3: Redeem
|
||||||
redeem_result = mirror_redeem(uetr, amount)
|
redeem_result = mirror_redeem(uetr, amount)
|
||||||
tx_hash = anchor_to_ethereum(kore_id, uetr, amount, currency, 'REDEEM')
|
redeem_kid = gen.for_mirror_redeem(uetr, amount, parent_koreid=settle_kid['koreid'])
|
||||||
post_confirmation(kore_id, tx_hash, uetr, 'REDEEM', pg)
|
tx = anchor_to_ethereum(redeem_kid['koreid'], uetr, amount, currency, 'REDEEM', gen)
|
||||||
|
post_confirmation(redeem_kid['koreid'], tx, uetr, 'REDEEM', gen, r)
|
||||||
|
eb.emit_mirror_success('redeem', uetr, redeem_kid['koreid'], amount, currency)
|
||||||
|
|
||||||
# Trigger oracle update if VZ vault affected
|
# Oracle update if VZ vault affected
|
||||||
if vault in VZ_VAULTS:
|
if vault in VZ_VAULTS:
|
||||||
trigger_oracle_update()
|
trigger_oracle_update()
|
||||||
|
|
||||||
# Step 4: Burn-sell
|
# Step 4: Burn-sell
|
||||||
burn_result = mirror_burn_sell(uetr, amount)
|
burn_result = mirror_burn_sell(uetr, amount)
|
||||||
tx_hash = anchor_to_ethereum(kore_id, uetr, amount, currency, 'BURN_SELL')
|
venue = burn_result.get('venue', 'unknown')
|
||||||
post_confirmation(kore_id, tx_hash, uetr, 'BURN_SELL', pg)
|
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)
|
||||||
|
eb.emit_mirror_success('burn', uetr, burn_kid['koreid'], amount, currency)
|
||||||
|
|
||||||
# Step 5: Close
|
# Step 5: Close
|
||||||
close_result = mirror_close(uetr)
|
close_result = mirror_close(uetr)
|
||||||
tx_hash = anchor_to_ethereum(kore_id, uetr, amount, currency, 'CLOSE')
|
close_kid = gen.for_mirror_close(uetr, parent_koreid=burn_kid['koreid'])
|
||||||
post_confirmation(kore_id, tx_hash, uetr, 'CLOSE', pg)
|
tx = anchor_to_ethereum(close_kid['koreid'], uetr, amount, currency, 'CLOSE', gen)
|
||||||
|
post_confirmation(close_kid['koreid'], tx, uetr, 'CLOSE', gen, r)
|
||||||
|
eb.emit_mirror_success('close', uetr, close_kid['koreid'], amount, currency)
|
||||||
|
|
||||||
mark_processed(r, event_id)
|
mark_processed(r, event_id)
|
||||||
log.info(f"✓ Complete: {event_id} | {currency} {amount:,.2f}")
|
log.info(f"✓ Complete: {event_id} | {currency} {amount:,.2f} | UETR: {uetr}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Processing error {event_id}: {e}")
|
log.error(f"Processing error {event_id}: {e}")
|
||||||
|
eb.emit(EventType.ENDPOINT_DOWN,
|
||||||
|
{'event_id': event_id, 'error': str(e)},
|
||||||
|
Severity.HIGH)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
log.info("Vector Zulu Payment Mirror Adapter starting...")
|
log.info("VZ Portal Feed Poller v2.0 starting...")
|
||||||
log.info(f"Rails: {RAILS_URL} | API: {API_URL} | Poll: {POLL_INTERVAL}s")
|
log.info(f"Rails: {RAILS_URL} | API: {API_URL} | Poll: {POLL_INTERVAL}s")
|
||||||
|
|
||||||
r = get_redis()
|
r = get_redis()
|
||||||
pg = get_pg()
|
pg = get_pg()
|
||||||
|
gen = KoreIDGenerator()
|
||||||
|
validator = KoreIDValidator(CERT, KEY)
|
||||||
|
eb = EventBusClient()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Poll all three feeds
|
|
||||||
payments = poll_feed('api/v1/tenants/portal/payments/feed')
|
payments = poll_feed('api/v1/tenants/portal/payments/feed')
|
||||||
settlements = poll_feed('api/v1/tenants/portal/settlements/feed')
|
settlements = poll_feed('api/v1/tenants/portal/settlements/feed')
|
||||||
deals = poll_feed('api/v1/tenants/deals/feed')
|
deals = poll_feed('api/v1/tenants/deals/feed')
|
||||||
@@ -384,9 +379,9 @@ def run():
|
|||||||
all_events = payments + settlements + deals
|
all_events = payments + settlements + deals
|
||||||
|
|
||||||
if all_events:
|
if all_events:
|
||||||
log.info(f"Found {len(all_events)} events to process")
|
log.info(f"Found {len(all_events)} events")
|
||||||
for event in all_events:
|
for event in all_events:
|
||||||
process_payment_event(event, r, pg)
|
process_event(event, r, pg, gen, validator, eb)
|
||||||
else:
|
else:
|
||||||
log.debug("No new events")
|
log.debug("No new events")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user