From e6f6e2979d790adac00efb848a359171a745ae9a Mon Sep 17 00:00:00 2001 From: VZ Build Date: Tue, 14 Jul 2026 18:18:07 +0000 Subject: [PATCH] Add KoreAnchor Ethereum integration - real on-chain anchoring --- eth_anchor.py | 172 ++++++++++++++++++++++++++++++++++++++++++++ korechain_poster.py | 30 ++++++++ 2 files changed, 202 insertions(+) create mode 100644 eth_anchor.py diff --git a/eth_anchor.py b/eth_anchor.py new file mode 100644 index 0000000..fbc8761 --- /dev/null +++ b/eth_anchor.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +""" +Vector Zulu — Ethereum KoreAnchor Caller +Calls KoreAnchor.sol at 0x8AaA785c7dC116b834E3A1766866078051fC0313 +to anchor KoreIDs immutably on Ethereum mainnet. + +Transmitter: 0xA08298ed3Bf2aD64499BEB115441aed85E0985Cc +Uses Chainlink oracle wallet keystore for signing. +""" + +import os, json, hashlib, logging +from web3 import Web3 + +log = logging.getLogger(__name__) + +KORE_ANCHOR_ADDRESS = '0x8AaA785c7dC116b834E3A1766866078051fC0313' +ETH_RPC = os.environ.get('ETH_RPC', 'https://eth-mainnet.g.alchemy.com/v2/nRVT_IneMUDF3Pokubawx') +KEYSTORE_PATH = os.environ.get('ANCHOR_KEYSTORE', '/opt/eth-deploy-v21/oracle_keystore.json') + +KORE_ANCHOR_ABI = [ + { + "inputs": [ + {"name": "koreId", "type": "string"}, + {"name": "uetr", "type": "string"}, + {"name": "eventType", "type": "string"}, + {"name": "payloadHash", "type": "bytes32"}, + {"name": "amount", "type": "uint256"}, + {"name": "currency", "type": "bytes3"}, + {"name": "linkedTo", "type": "string"} + ], + "name": "anchor", + "outputs": [{"name": "anchoredAt", "type": "uint256"}], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{"name": "koreId", "type": "string"}], + "name": "isAnchored", + "outputs": [{"name": "", "type": "bool"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAnchors", + "outputs": [{"name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": False, + "inputs": [ + {"indexed": True, "name": "koreId", "type": "string"}, + {"indexed": True, "name": "uetr", "type": "string"}, + {"indexed": False, "name": "eventType", "type": "string"}, + {"indexed": False, "name": "amount", "type": "uint256"}, + {"indexed": False, "name": "currency", "type": "bytes3"}, + {"indexed": False, "name": "anchoredAt", "type": "uint256"} + ], + "name": "AnchorCreated", + "type": "event" + } +] + + +class EthereumAnchor: + + def __init__(self): + self.w3 = Web3(Web3.HTTPProvider(ETH_RPC)) + self.contract = self.w3.eth.contract( + address=Web3.to_checksum_address(KORE_ANCHOR_ADDRESS), + abi=KORE_ANCHOR_ABI + ) + self._account = None + log.info(f"EthereumAnchor initialized — contract {KORE_ANCHOR_ADDRESS}") + + def _get_account(self): + if self._account: + return self._account + if not os.path.exists(KEYSTORE_PATH): + log.warning(f"Keystore not found at {KEYSTORE_PATH}") + return None + with open(KEYSTORE_PATH) as f: + ks = json.load(f) + # Empty string password confirmed working + self._account = self.w3.eth.account.from_key( + self.w3.eth.account.decrypt(ks, '') + ) + log.info(f"Wallet loaded: {self._account.address}") + return self._account + + def payload_hash(self, payload: dict) -> bytes: + """SHA256 of canonical JSON payload.""" + canonical = json.dumps(payload, separators=(',', ':'), sort_keys=True) + return hashlib.sha256(canonical.encode()).digest() + + def anchor(self, kore_id: str, uetr: str, event_type: str, + amount: float, currency: str, linked_to: str, + payload: dict = None) -> str: + """ + Anchor a KoreID on Ethereum mainnet. + Returns tx hash or empty string on failure. + """ + account = self._get_account() + if not account: + log.error("No signing account available") + return '' + + try: + # Check if already anchored + if self.contract.functions.isAnchored(kore_id).call(): + log.info(f"Already anchored: {kore_id}") + return 'already_anchored' + + # Build payload hash + ph = self.payload_hash(payload or { + 'koreId': kore_id, 'uetr': uetr, + 'amount': amount, 'currency': currency + }) + + # Convert amount to uint256 (x1e8) + amount_int = int(amount * 1e8) + + # Convert currency to bytes3 + currency_bytes = currency.encode('utf-8').ljust(3, b'\x00')[:3] + + # Build transaction + tx = self.contract.functions.anchor( + kore_id, uetr, event_type, + ph, amount_int, currency_bytes, linked_to + ).build_transaction({ + 'from': account.address, + 'nonce': self.w3.eth.get_transaction_count(account.address), + 'gas': 200000, + }) + + # Sign and send + signed = account.sign_transaction(tx) + tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction) + tx_hex = tx_hash.hex() + log.info(f"Anchored {kore_id} — tx: {tx_hex}") + return tx_hex + + except Exception as e: + log.error(f"Anchor failed for {kore_id}: {e}") + return '' + + def total_anchors(self) -> int: + try: + return self.contract.functions.totalAnchors().call() + except Exception as e: + log.error(f"totalAnchors error: {e}") + return 0 + + +# Singleton +_anchor = None + +def get_anchor() -> EthereumAnchor: + global _anchor + if _anchor is None: + _anchor = EthereumAnchor() + return _anchor + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + a = EthereumAnchor() + print(f"Total anchors on-chain: {a.total_anchors()}") + print(f"Contract: {KORE_ANCHOR_ADDRESS}") + print(f"Connected: {a.w3.is_connected()}") + print(f"Block: {a.w3.eth.block_number}") diff --git a/korechain_poster.py b/korechain_poster.py index 1f331a6..f1a6d4d 100644 --- a/korechain_poster.py +++ b/korechain_poster.py @@ -16,6 +16,15 @@ import os, json, time, logging, requests, redis, psycopg2, urllib3 from datetime import datetime, timezone from lick import build_headers +# Ethereum anchoring — import conditionally (web3 may not be installed) +try: + from eth_anchor import get_anchor + ETH_ANCHOR_ENABLED = True +except ImportError: + ETH_ANCHOR_ENABLED = False + import logging + logging.getLogger(__name__).warning("eth_anchor not available — Ethereum anchoring disabled") + urllib3.disable_warnings() logging.basicConfig(level=logging.INFO, format='%(asctime)s [KORE-POSTER] %(levelname)s %(message)s') log = logging.getLogger(__name__) @@ -145,6 +154,27 @@ class KoreChainPoster: if success: store_posted(self.pg, payload, 'POSTED', response_koreid) log.info(f"✓ {koreid} posted") + + # Anchor to Ethereum mainnet + if ETH_ANCHOR_ENABLED: + try: + anchor = get_anchor() + tx_hash = anchor.anchor( + kore_id=koreid, + uetr=payload.get('uetr', ''), + event_type=payload.get('step', 'ANCHOR'), + amount=float(payload.get('amount', 0)), + currency=payload.get('currency', 'USD'), + linked_to=payload.get('linked_to', 'GENESIS-07'), + payload=payload, + ) + if tx_hash and tx_hash != 'already_anchored': + log.info(f"⛓ Ethereum anchor tx: {tx_hash}") + store_posted(self.pg, {**payload, 'ethereum_tx': tx_hash}, 'ETH_ANCHORED', tx_hash) + elif tx_hash == 'already_anchored': + log.info(f"⛓ Already on Ethereum: {koreid}") + except Exception as e: + log.error(f"Ethereum anchor error for {koreid}: {e}") else: retries += 1 if retries < MAX_RETRIES: