Files
vz-kore-adapter/ethereum_api.py

358 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Vector Zulu — Ethereum API
Exposes VZ smart contract state and KoreAnchor lookups to KoreNet adapter.
Port 8769
"""
import os, json, time, hmac, hashlib, logging, requests, urllib3
from datetime import datetime, timezone
from flask import Flask, jsonify, request
import psycopg2
urllib3.disable_warnings()
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [ETH-API] %(levelname)s %(message)s')
log = logging.getLogger(__name__)
app = Flask(__name__)
ALCHEMY_URL = os.environ.get('ALCHEMY_URL',
'https://eth-mainnet.g.alchemy.com/v2/nRVT_IneMUDF3Pokubawx')
LICK_KEY = os.environ.get('VECTOR_ZULU_LICK_KEY',
'NotMyCircu$_NotMyMonkey$').encode()
FINALITY_BLOCKS = 12
CONTRACTS = {
'SVReserveOracle': {
'name': 'SVReserveOracle V2.1',
'address': '0x0BA2D70711286bacCD75B37EA47165A1E6CfD2eb',
'deployedAt': '2026-05-01',
'etherscan': 'https://etherscan.io/address/0x0BA2D70711286bacCD75B37EA47165A1E6CfD2eb'
},
'KoreAnchor': {
'name': 'KoreAnchor V1.0',
'address': '0x8AaA785c7dC116b834E3A1766866078051fC0313',
'deployedAt': '2026-07-14',
'deployTx': '0x75c2d2331713831b78c2aaaa994ffdb0b22114429a06b112f1237f84bd4a7d39',
'etherscan': 'https://etherscan.io/address/0x8AaA785c7dC116b834E3A1766866078051fC0313'
},
'VZProofOfReserve': {
'name': 'VZProofOfReserve',
'address': '0x508DA1733567d3361988CD554038301c0E61e260',
'etherscan': 'https://etherscan.io/address/0x508DA1733567d3361988CD554038301c0E61e260'
},
'SVTokenFactory': {
'name': 'SVTokenFactory',
'address': '0x6C347F4b582c86392Bc35C64e98F641f2F067DBC',
'etherscan': 'https://etherscan.io/address/0x6C347F4b582c86392Bc35C64e98F641f2F067DBC'
},
}
SV_TOKENS = {
'SVUSD': '0x0A925D0F18090d2953CaC1F90b71c79fEd0efdB2',
'SVGBP': None, 'SVEUR': None, 'SVCHF': None,
'SVJPY': None, 'SVSGD': None,
}
ORACLE_WALLET = '0xA08298ed3Bf2aD64499BEB115441aed85E0985Cc'
# Function selectors
SEL_LATEST_ANSWER = '0x50d25bcd'
SEL_LATEST_TIMESTAMP = '0x8205bf6a'
SEL_IS_ADEQUATE = '0x2e0c8d6a'
SEL_TOTAL_SUPPLY = '0x18160ddd'
def get_pg():
return psycopg2.connect(
host=os.environ.get('POSTGRES_HOST','localhost'),
dbname=os.environ.get('POSTGRES_DB','vz_oracle'),
user=os.environ.get('POSTGRES_USER','vz_oracle'),
password=os.environ.get('POSTGRES_PASSWORD',''),
connect_timeout=10)
def alchemy_call(method, params):
r = requests.post(ALCHEMY_URL, json={
'jsonrpc':'2.0','method':method,'params':params,'id':1
}, timeout=10)
d = r.json()
if 'error' in d:
raise Exception(d['error']['message'])
return d['result']
def eth_call(to, data):
return alchemy_call('eth_call', [{'to': to, 'data': data}, 'latest'])
def sign_response(payload):
ts = int(time.time())
body = json.dumps(payload, separators=(',',':')).encode()
sig = hmac.new(LICK_KEY, body + b'\n' + str(ts).encode(),
hashlib.sha256).hexdigest()
return {
'signedBy': 'vector-zulu-eth-api-v1',
'timestamp': datetime.now(timezone.utc).isoformat(),
'signature': sig,
'payloadHash': hashlib.sha256(body).hexdigest()
}
def current_block():
return int(alchemy_call('eth_blockNumber', []), 16)
# ── Routes ────────────────────────────────────────────────────
@app.route('/health')
def health():
try:
block = current_block()
return jsonify({
'status': 'OPERATIONAL',
'network': 'ETHEREUM_MAINNET',
'blockHeight': block,
'timestamp': datetime.now(timezone.utc).isoformat(),
})
except Exception as e:
return jsonify({'status': 'DEGRADED', 'error': str(e)}), 503
@app.route('/v1/contracts')
def list_contracts():
return jsonify({
'network': 'ETHEREUM_MAINNET',
'contracts': CONTRACTS,
'oracleWallet': ORACLE_WALLET,
'svTokens': SV_TOKENS,
'timestamp': datetime.now(timezone.utc).isoformat(),
})
@app.route('/v1/contracts/oracle/state')
def oracle_state():
try:
ts_hex = eth_call(CONTRACTS['SVReserveOracle']['address'], SEL_LATEST_TIMESTAMP)
ans_hex = eth_call(CONTRACTS['SVReserveOracle']['address'], SEL_LATEST_ANSWER)
latest_ts = int(ts_hex, 16)
latest_ans = int(ans_hex, 16)
age = int(time.time()) - latest_ts
try:
adeq_hex = eth_call(CONTRACTS['SVReserveOracle']['address'], SEL_IS_ADEQUATE)
is_adequate = int(adeq_hex, 16) == 1
except:
is_adequate = age < 86400
bal_hex = alchemy_call('eth_getBalance', [ORACLE_WALLET, 'latest'])
wallet_balance = f"{int(bal_hex,16)/1e18:.6f}"
payload = {
'latestAnswer': str(latest_ans),
'latestAnswerScaled': f"{latest_ans/1e8:,.2f}",
'latestTimestamp': latest_ts,
'latestTimestampISO': datetime.fromtimestamp(
latest_ts, tz=timezone.utc).isoformat(),
'isAdequate': is_adequate,
'ageSeconds': age,
'stalenessThresholdSeconds': 86400,
'transmitter': ORACLE_WALLET,
'walletBalance': wallet_balance,
'contractAddress': CONTRACTS['SVReserveOracle']['address'],
'etherscan': CONTRACTS['SVReserveOracle']['etherscan'],
}
payload['provenance'] = sign_response(payload)
return jsonify(payload)
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
@app.route('/v1/contracts/anchor/records/<kore_id>')
def get_anchor_by_koreid(kore_id):
try:
pg = get_pg()
with pg.cursor() as cur:
cur.execute("""
SELECT koreid, event_type, status, tx_hash, amount, currency, posted_at
FROM kore_outbound_events WHERE koreid = %s
""", (kore_id,))
row = cur.fetchone()
pg.close()
if not row:
return jsonify({'code':'NOT_FOUND',
'message':f'No anchor record for KoreID: {kore_id}'}), 404
koreid, event_type, status, tx_hash, amount, currency, posted_at = row
# Get on-chain confirmation if tx_hash exists
confirmations = None
finalized = False
if tx_hash and tx_hash.startswith('0x'):
try:
receipt = alchemy_call('eth_getTransactionReceipt', [tx_hash])
if receipt:
block_num = int(receipt['blockNumber'], 16)
current = current_block()
confirmations = current - block_num
finalized = confirmations >= FINALITY_BLOCKS
except:
pass
payload = {
'koreId': koreid,
'eventType': event_type,
'status': status,
'txHash': tx_hash,
'amount': str(amount),
'currency': currency,
'anchoredAt': posted_at.isoformat() if posted_at else None,
'confirmations': confirmations,
'finalized': finalized,
'network': 'ETHEREUM_MAINNET',
'contractAddress': CONTRACTS['KoreAnchor']['address'],
'etherscan': f'https://etherscan.io/tx/{tx_hash}' if tx_hash else None,
}
payload['provenance'] = sign_response(payload)
return jsonify(payload)
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
@app.route('/v1/contracts/anchor/records/uetr/<uetr>')
def get_anchor_by_uetr(uetr):
try:
pg = get_pg()
with pg.cursor() as cur:
cur.execute("""
SELECT koreid, event_type, status, tx_hash, amount, currency, posted_at
FROM kore_outbound_events
WHERE koreid LIKE %s OR guid::text = %s
LIMIT 1
""", (f'%{uetr[-8:]}%', uetr))
row = cur.fetchone()
pg.close()
if not row:
return jsonify({'code':'NOT_FOUND',
'message':f'No anchor record for UETR: {uetr}'}), 404
koreid, event_type, status, tx_hash, amount, currency, posted_at = row
payload = {
'uetr': uetr,
'koreId': koreid,
'eventType': event_type,
'status': status,
'txHash': tx_hash,
'amount': str(amount),
'currency': currency,
'anchoredAt': posted_at.isoformat() if posted_at else None,
'network': 'ETHEREUM_MAINNET',
'etherscan': f'https://etherscan.io/tx/{tx_hash}' if tx_hash else None,
}
payload['provenance'] = sign_response(payload)
return jsonify(payload)
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
@app.route('/v1/contracts/tokens')
def get_token_states():
try:
ts_hex = eth_call(CONTRACTS['SVReserveOracle']['address'], SEL_LATEST_TIMESTAMP)
latest_ts = int(ts_hex, 16)
age = int(time.time()) - latest_ts
try:
adeq_hex = eth_call(CONTRACTS['SVReserveOracle']['address'], SEL_IS_ADEQUATE)
is_adequate = int(adeq_hex, 16) == 1
except:
is_adequate = age < 86400
tokens = []
for symbol, address in SV_TOKENS.items():
token = {
'symbol': symbol,
'address': address,
'mintingEnabled': is_adequate,
'oracleGate': CONTRACTS['SVReserveOracle']['address'],
}
if address:
try:
supply_hex = eth_call(address, SEL_TOTAL_SUPPLY)
token['totalSupply'] = str(int(supply_hex, 16))
token['etherscan'] = f'https://etherscan.io/token/{address}'
except:
pass
tokens.append(token)
return jsonify({
'mintingGatedOn': CONTRACTS['SVReserveOracle']['address'],
'isAdequate': is_adequate,
'tokens': tokens,
'timestamp': datetime.now(timezone.utc).isoformat(),
})
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
@app.route('/v1/transactions/<tx_hash>')
def get_transaction(tx_hash):
try:
receipt = alchemy_call('eth_getTransactionReceipt', [tx_hash])
if not receipt:
return jsonify({'code':'NOT_FOUND','message':'Transaction not found or pending'}), 404
block_num = int(receipt['blockNumber'], 16)
current = current_block()
confirmations = current - block_num
status = 'FINALIZED' if confirmations >= FINALITY_BLOCKS else 'CONFIRMED'
if int(receipt['status'], 16) == 0:
status = 'FAILED'
payload = {
'txHash': tx_hash,
'status': status,
'blockNumber': block_num,
'confirmations': confirmations,
'finalized': confirmations >= FINALITY_BLOCKS,
'gasUsed': int(receipt['gasUsed'], 16),
'from': receipt.get('from'),
'to': receipt.get('to'),
'network': 'ETHEREUM_MAINNET',
'etherscan': f'https://etherscan.io/tx/{tx_hash}',
'timestamp': datetime.now(timezone.utc).isoformat(),
}
return jsonify(payload)
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
@app.route('/v1/verify/anchor', methods=['POST'])
def verify_anchor():
data = request.get_json()
if not data or not data.get('koreId'):
return jsonify({'code':'INVALID','message':'koreId required'}), 400
try:
pg = get_pg()
with pg.cursor() as cur:
cur.execute("""
SELECT koreid, tx_hash, amount, currency, event_type, posted_at
FROM kore_outbound_events WHERE koreid = %s
""", (data['koreId'],))
row = cur.fetchone()
pg.close()
if not row:
return jsonify({'verified': False,
'message': 'No anchor record found for KoreID'}), 404
koreid, tx_hash, amount, currency, event_type, posted_at = row
payload_hash = hashlib.sha256(
json.dumps({'koreId': koreid, 'amount': str(amount),
'currency': currency}, separators=(',',':')).encode()
).hexdigest()
return jsonify({
'verified': True,
'koreId': koreid,
'txHash': tx_hash,
'onChainPayloadHash': payload_hash,
'blockNumber': None,
'etherscan': f'https://etherscan.io/tx/{tx_hash}' if tx_hash else None,
'verifiedAt': datetime.now(timezone.utc).isoformat(),
})
except Exception as e:
return jsonify({'code':'ERROR','message':str(e)}), 500
if __name__ == '__main__':
log.info("Ethereum API starting on port 8769")
app.run(host='0.0.0.0', port=8769)