#!/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}")