173 lines
5.7 KiB
Python
173 lines
5.7 KiB
Python
#!/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}")
|