signin-lumiterra.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # -*- coding: utf-8 -*-
  2. """
  3. Monad 测试网 talentum 批量签到脚本
  4. 网址: https://monad.talentum.id
  5. 合约: 0x9E1DDC2a90bc0bec6325dF48Cbb3125Ce2879F7C 方法: checkIn() → 0x9e4cda43
  6. """
  7. import asyncio
  8. import os
  9. import random
  10. from typing import List
  11. import traceback
  12. from web3 import Web3
  13. from web3.types import TxReceipt
  14. from eth_account import Account
  15. # ------------------------ 用户配置区 ------------------------
  16. RPC_URL = "https://testnet-rpc.monad.xyz"
  17. CHECK_IN_CONTRACT = "0x9E1DDC2a90bc0bec6325dF48Cbb3125Ce2879F7C"
  18. KEY_FILE = "AccountList.txt"
  19. MIN_KEEP_BALANCE = 0.02
  20. MIN_DELAY = 15
  21. MAX_DELAY = 30
  22. CHECK_IN_ABI = [{
  23. "inputs": [],
  24. "name": "checkIn",
  25. "outputs": [],
  26. "stateMutability": "nonpayable",
  27. "type": "function"
  28. }]
  29. # -----------------------------------------------------------
  30. w3 = Web3(Web3.HTTPProvider(RPC_URL))
  31. if not w3.is_connected():
  32. raise RuntimeError("无法连接 RPC")
  33. def load_private_keys(path: str) -> List[str]:
  34. if not os.path.isfile(path):
  35. raise FileNotFoundError(f"找不到私钥文件: {path}")
  36. with open(path, "r", encoding="utf-8") as f:
  37. return [line.strip() for line in f if line.strip()]
  38. def random_gas_limit(base: int = 100_000) -> int:
  39. limit = base + random.randint(-5_000, 10_000)
  40. return max(21_000, limit) # 保证不会小于最低gas
  41. async def wait_tx(tx_hash: str, tx_params: dict):
  42. print(f"⏳ 等待交易确认: {tx_hash}")
  43. try:
  44. receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
  45. if receipt.status == 1:
  46. print("✅ 成功")
  47. else:
  48. print("❌ 失败(链上执行返回 status=0),尝试获取失败原因...")
  49. # 用 eth_call 复现错误,获取revert reason
  50. try:
  51. w3.eth.call(tx_params, block_identifier="pending")
  52. print("但 eth_call 没报错,说明没有 revert reason(或已被修复)")
  53. except Exception as call_err:
  54. # eth_call 抛出的异常中通常包含 revert reason
  55. err_msg = str(call_err)
  56. print(f"失败原因(revert reason): {err_msg}")
  57. print("-" * 40)
  58. return receipt
  59. except Exception as e:
  60. print(f"❌ 等待交易时出错: {e}")
  61. import traceback
  62. traceback.print_exc()
  63. return None
  64. async def send_contract_call(private_key: str,
  65. contract_addr: str,
  66. contract_abi: list,
  67. desc: str):
  68. acct = Account.from_key(private_key)
  69. addr = acct.address
  70. balance = w3.eth.get_balance(addr)
  71. if w3.from_wei(balance, "ether") < MIN_KEEP_BALANCE:
  72. print(f"⚠️ {addr} 余额不足({w3.from_wei(balance, 'ether')} ETH),跳过")
  73. return
  74. nonce = w3.eth.get_transaction_count(addr)
  75. contract = w3.eth.contract(address=Web3.to_checksum_address(contract_addr), abi=contract_abi)
  76. try:
  77. data = contract.encodeABI(fn_name='checkIn')
  78. tx = {
  79. "to": Web3.to_checksum_address(contract_addr),
  80. "value": 0,
  81. "data": data,
  82. "gas": random_gas_limit(),
  83. "gasPrice": w3.eth.gas_price,
  84. "nonce": nonce,
  85. "chainId": w3.eth.chain_id,
  86. }
  87. signed = Account.sign_transaction(tx, private_key)
  88. try:
  89. tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction).hex()
  90. print(f"{desc} {addr} 提交签到交易: {tx_hash}")
  91. # 传入 tx_params 方便 wait_tx 用 eth_call 复现
  92. await wait_tx(tx_hash, {
  93. "from": addr,
  94. "to": tx["to"],
  95. "data": tx["data"],
  96. "value": tx["value"],
  97. "gas": tx["gas"]
  98. })
  99. except Exception as send_e:
  100. print(f"❌ {addr} 广播交易时遇到异常:{send_e}")
  101. import traceback
  102. traceback.print_exc()
  103. except Exception as e:
  104. print(f"❌ {addr} 构造或签名交易时出错: {e}")
  105. import traceback
  106. traceback.print_exc()
  107. async def process_account(private_key: str):
  108. acct = Account.from_key(private_key)
  109. print(f"\n========== 开始处理 {acct.address} ==========")
  110. await send_contract_call(
  111. private_key,
  112. CHECK_IN_CONTRACT,
  113. CHECK_IN_ABI,
  114. "SignIn"
  115. )
  116. async def main():
  117. keys = load_private_keys(KEY_FILE)
  118. if not keys:
  119. print("私钥文件为空")
  120. return
  121. print(f"共读取到 {len(keys)} 个钱包")
  122. for idx, pk in enumerate(keys):
  123. await process_account(pk)
  124. if idx != len(keys) - 1:
  125. wait_sec = random.uniform(MIN_DELAY, MAX_DELAY)
  126. print(f"⏳ 随机等待 {wait_sec:.2f} 秒,防批量风控")
  127. await asyncio.sleep(wait_sec)
  128. if __name__ == "__main__":
  129. asyncio.run(main())