import json
import logging
import os
from dataclasses import dataclass, field
from typing import Any
from django.conf import settings
from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import ChecksumAddress, HexAddress, HexStr
from web3 import Web3
from web3.middleware import SignAndSendRawMiddlewareBuilder
logger = logging.getLogger(__name__)
EVENT_MANAGER_ADDRESS = ChecksumAddress(
HexAddress(HexStr(os.environ["PUBLIC_EVENT_MANAGER_ADDRESS"]))
)
EVENT_MANAGER_ABI = json.loads(settings.CONTRACTS_INFO[2].read_text())["abi"]
BACKEND_PRIVATE_KEY = os.environ["BACKEND_EOA_PRIVATE_KEY"]
@dataclass
class ContractService:
w3: Web3 = field(init=False)
account: LocalAccount = field(init=False)
contract: Any = field(init=False)
def __post_init__(self) -> None:
self.w3 = Web3(Web3.HTTPProvider(settings.HTTP_ETH_NODE_HOST))
if not self.w3.is_connected():
raise ConnectionError("Failed to connect to Ethereum node")
self.account = Account.from_key(BACKEND_PRIVATE_KEY)
self.w3.eth.default_account = self.account.address
self.w3.middleware_onion.inject(
SignAndSendRawMiddlewareBuilder.build(self.account), layer=0
)
self.contract = self.w3.eth.contract(
abi=EVENT_MANAGER_ABI, address=EVENT_MANAGER_ADDRESS
)
logger.info("ContractService initialized with EOA %s", self.account.address)
def check_backend_has_role(self) -> bool:
try:
backend_role = self.contract.functions.BACKEND_ROLE().call()
has_role: bool = self.contract.functions.hasRole(
backend_role, self.account.address
).call()
except Exception:
logger.exception("Failed to check BACKEND_ROLE")
raise
else:
return has_role
def grant_verified_shaman_role(self, address: str) -> tuple[bool, str | None]:
try:
verified_shaman_role = self.contract.functions.VERIFIED_SHAMAN_ROLE().call()
tx = self.contract.functions.grantRole(verified_shaman_role, address)
receipt = tx.transact()
tx_hash = receipt.hex() if hasattr(receipt, "hex") else str(receipt)
logger.info(
"Successfully granted VERIFIED_SHAMAN_ROLE to %s. TX: %s",
address,
tx_hash,
)
except Exception as e:
logger.exception("Failed to grant VERIFIED_SHAMAN_ROLE to %s", address)
return False, str(e)
else:
return True, None