| import os |
| import json |
| from web3 import Web3 |
| from dotenv import load_dotenv |
|
|
| load_dotenv() |
|
|
| RPC_URL = os.environ.get("PURECHAIN_RPC_URL", "http://3.34.161.207:8545") |
| PRIVATE_KEY = os.environ.get("PURECHAIN_PRIVATE_KEY") |
| ADDR_OLD = os.environ.get("PURECHAIN_CONTRACT_ADDRESS_PUREBI_JUN11") |
| ADDR_NEW = os.environ.get("PURECHAIN_CONTRACT_ADDRESS") |
|
|
| def main(): |
| if not PRIVATE_KEY or not ADDR_OLD or not ADDR_NEW: |
| print("❌ Missing environment variables. Check .env") |
| return |
|
|
| w3 = Web3(Web3.HTTPProvider(RPC_URL)) |
| if not w3.is_connected(): |
| print("❌ Web3 not connected.") |
| return |
|
|
| admin_account = w3.eth.account.from_key(PRIVATE_KEY) |
| print(f"Admin Account: {admin_account.address}") |
| |
| |
| abi_minimal = json.loads('''[ |
| {"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"string","name":"phrase","type":"string"},{"indexed":false,"internalType":"string","name":"dialect","type":"string"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"EntryProposed","type":"event"}, |
| {"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"EntryVerified","type":"event"}, |
| {"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"userXP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, |
| {"inputs":[{"internalType":"address[]","name":"_users","type":"address[]"},{"internalType":"string[]","name":"_dialects","type":"string[]"},{"internalType":"uint256[]","name":"_xps","type":"uint256[]"}],"name":"batchMigrateXP","outputs":[],"stateMutability":"nonpayable","type":"function"} |
| ]''') |
| |
| contract_old = w3.eth.contract(address=w3.to_checksum_address(ADDR_OLD), abi=abi_minimal) |
| contract_new = w3.eth.contract(address=w3.to_checksum_address(ADDR_NEW), abi=abi_minimal) |
|
|
| |
| print("🔍 Discovering users from old contract events...") |
| try: |
| events = contract_old.events.EntryProposed.get_logs(fromBlock=0, toBlock='latest') |
| except Exception as e: |
| print(f"Failed to fetch logs: {e}") |
| return |
|
|
| user_dialects = set() |
| for ev in events: |
| user = ev.args.contributor |
| dialect = ev.args.dialect |
| user_dialects.add((user, dialect)) |
| |
| if not user_dialects: |
| print("No users found.") |
| return |
| |
| print(f"Found {len(user_dialects)} User/Dialect pairs. Checking XP balances...") |
| |
| users = [] |
| dialects = [] |
| xps = [] |
| |
| for (user, dialect) in user_dialects: |
| old_xp = contract_old.functions.userXP(user, dialect).call() |
| if old_xp > 0: |
| |
| new_xp = contract_new.functions.userXP(user, dialect).call() |
| if new_xp < old_xp: |
| diff = old_xp - new_xp |
| users.append(user) |
| dialects.append(dialect) |
| xps.append(diff) |
| print(f"💡 Found {diff} unmigrated XP for {user} in {dialect}") |
| |
| if not users: |
| print("✅ No new XP needs migration.") |
| return |
| |
| print(f"🚀 Migrating {len(users)} records...") |
| |
| nonce = w3.eth.get_transaction_count(admin_account.address) |
| tx = contract_new.functions.batchMigrateXP(users, dialects, xps).build_transaction({ |
| 'chainId': w3.eth.chain_id, |
| 'gas': 3000000, |
| 'gasPrice': w3.eth.gas_price, |
| 'nonce': nonce, |
| }) |
| |
| signed_tx = w3.eth.account.sign_transaction(tx, PRIVATE_KEY) |
| tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) |
| |
| print(f"⏳ Migration TX sent! Hash: {tx_hash.hex()}") |
| receipt = w3.eth.wait_for_transaction_receipt(tx_hash) |
| print(f"🎉 Migration complete in block {receipt.blockNumber}!") |
|
|
| if __name__ == "__main__": |
| main() |
|
|