|
|
@@ -0,0 +1,118 @@
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+"""
|
|
|
+Monad 测试网 talentum 批量签到脚本
|
|
|
+网址: monad.talentum.id
|
|
|
+2. checkIn() → 0x9E1DDC2a90bc0bec6325dF48Cbb3125Ce2879F7C 方法 0x9e4cda43
|
|
|
+"""
|
|
|
+
|
|
|
+import asyncio
|
|
|
+import os
|
|
|
+import random
|
|
|
+from typing import List
|
|
|
+
|
|
|
+from web3 import Web3
|
|
|
+from web3.types import TxReceipt
|
|
|
+from eth_account import Account
|
|
|
+
|
|
|
+# ------------------------ 用户配置区 ------------------------
|
|
|
+RPC_URL = "https://testnet-rpc.monad.xyz"
|
|
|
+
|
|
|
+CHECK_IN_CONTRACT = "0x9E1DDC2a90bc0bec6325dF48Cbb3125Ce2879F7C"
|
|
|
+
|
|
|
+KEY_FILE = "AccountList.txt"
|
|
|
+MIN_KEEP_BALANCE = 0.02
|
|
|
+MIN_DELAY = 15
|
|
|
+MAX_DELAY = 30
|
|
|
+
|
|
|
+CHECK_IN_ABI = [{
|
|
|
+ "inputs": [],
|
|
|
+ "name": "checkIn",
|
|
|
+ "outputs": [],
|
|
|
+ "stateMutability": "nonpayable",
|
|
|
+ "type": "function"
|
|
|
+}]
|
|
|
+
|
|
|
+# -----------------------------------------------------------
|
|
|
+
|
|
|
+w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
|
|
+if not w3.is_connected():
|
|
|
+ raise RuntimeError("无法连接 RPC")
|
|
|
+
|
|
|
+CHECK_IN_SELECTOR = "0x9e4cda43"
|
|
|
+
|
|
|
+def load_private_keys(path: str) -> List[str]:
|
|
|
+ if not os.path.isfile(path):
|
|
|
+ raise FileNotFoundError(f"找不到私钥文件: {path}")
|
|
|
+ with open(path, "r", encoding="utf-8") as f:
|
|
|
+ return [line.strip() for line in f if line.strip()]
|
|
|
+
|
|
|
+def random_gas_limit(base: int = 100_000) -> int:
|
|
|
+ return base + random.randint(-5_000, 10_000)
|
|
|
+
|
|
|
+async def wait_tx(tx_hash: str) -> TxReceipt:
|
|
|
+ print(f"⏳ 等待交易确认: {tx_hash}")
|
|
|
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
|
|
|
+ if receipt.status == 1:
|
|
|
+ print("✅ 成功")
|
|
|
+ else:
|
|
|
+ print("❌ 失败")
|
|
|
+ return receipt
|
|
|
+
|
|
|
+async def send_contract_call(private_key: str,
|
|
|
+ contract_addr: str,
|
|
|
+ contract_abi: list,
|
|
|
+ desc: str):
|
|
|
+ acct = Account.from_key(private_key)
|
|
|
+ addr = acct.address
|
|
|
+ balance = w3.eth.get_balance(addr)
|
|
|
+ if w3.from_wei(balance, "ether") < MIN_KEEP_BALANCE:
|
|
|
+ print(f"⚠️ {addr} 余额不足,跳过")
|
|
|
+ return
|
|
|
+
|
|
|
+ nonce = w3.eth.get_transaction_count(addr)
|
|
|
+
|
|
|
+ contract = w3.eth.contract(address=Web3.to_checksum_address(contract_addr), abi=contract_abi)
|
|
|
+ data = contract.encodeABI(fn_name='checkIn')
|
|
|
+
|
|
|
+ tx = {
|
|
|
+ "to": Web3.to_checksum_address(contract_addr),
|
|
|
+ "value": 0,
|
|
|
+ "data": data,
|
|
|
+ "gas": random_gas_limit(),
|
|
|
+ "gasPrice": w3.eth.gas_price,
|
|
|
+ "nonce": nonce,
|
|
|
+ "chainId": w3.eth.chain_id,
|
|
|
+ }
|
|
|
+
|
|
|
+ signed = Account.sign_transaction(tx, private_key)
|
|
|
+ tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction).hex()
|
|
|
+ print(f"{desc} tx_hash: {tx_hash}")
|
|
|
+ await wait_tx(tx_hash)
|
|
|
+
|
|
|
+async def process_account(private_key: str):
|
|
|
+ acct = Account.from_key(private_key)
|
|
|
+ print(f"\n========== 开始处理 {acct.address} ==========")
|
|
|
+ try:
|
|
|
+ await send_contract_call(
|
|
|
+ private_key,
|
|
|
+ CHECK_IN_CONTRACT,
|
|
|
+ CHECK_IN_ABI,
|
|
|
+ "SignIn"
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ print(f"执行出错: {e}")
|
|
|
+
|
|
|
+async def main():
|
|
|
+ keys = load_private_keys(KEY_FILE)
|
|
|
+ if not keys:
|
|
|
+ print("私钥文件为空")
|
|
|
+ return
|
|
|
+
|
|
|
+ print(f"共读取到 {len(keys)} 个钱包")
|
|
|
+ for pk in keys:
|
|
|
+ await process_account(pk)
|
|
|
+ if pk != keys[-1]:
|
|
|
+ await asyncio.sleep(random.uniform(MIN_DELAY, MAX_DELAY))
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ asyncio.run(main())
|