Python 实战:写一个 BSC 代币解锁监控脚本,15 分钟一轮盯项目方钱包

为什么需要自己写
代币解锁(unlock / vesting cliff)是持仓最大的不确定来源之一。解锁的币先到项目方钱包,再决定卖不卖——价格预警只能告诉你"跌了",链上监控才能告诉你"谁先动了"。区块浏览器只能手动刷,写个脚本让它替你看,才是正经做法。
整体思路:三层
- 找重点钱包:GoPlus 的
token_security接口(免 key)直接返回holders(地址 + 余额 + 占比),一眼找出锁仓合约和大户地址。 - 扫 Transfer 事件:
eth_getLogs按区块区间扫代币合约的Transfer日志,抓到重点钱包的转出就报警。 - 余额兜底:对重点钱包定期
eth_call查余额,余额减少即报警——这条路不受日志历史限制,覆盖扫不到的旧区间。
三个实战坑
1. 免费 RPC 对 eth_getLogs 很挑剔
我实测下来:有的公共 RPC 对 topics[1] 过滤直接报 "Invalid variadic value";有的对区块跨度大的查询报 -32005 limit exceeded;还有的干脆不支持这个方法。
解决办法:每次只扫 2000 块左右的小步长,抓全量 Transfer 日志再在本地过滤地址;准备 2–3 个 RPC 做轮询和重试。另外注意 BSC 在 Maxwell 硬分叉(BEP-524,2025 年 5 月)后出块间隔约 0.75 秒——15 分钟约 1200 个块,扫描步长和补扫区间都要按这个换算。
2. 历史日志有 archive 门限
免费节点通常只保留最近约 1–2 万个块的日志,查更老的会报 "Archive requests require a personal token"。
解决办法:扫描起点钳制到可查窗口内,跳过打印 NOTE 说明;被跳过的旧区间靠 eth_call 查重点钱包余额兜底(查 latest 高度不受 archive 限制)。
3. 余额基线不要用 holder percent 换算
GoPlus 的 holder 占比可能滞后几分钟。如果你恰好赶上大户刚转完账,算出的基线就是错的。正确做法:基线一律用 eth_call 实测初始化。
脚本骨架
import json, urllib.request
TOKEN = "0x<代币合约地址>"
# Transfer(address,address,uint256) 的 topic 签名,ERC-20 通用
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
WATCH = {
"0x<锁仓合约A>": "锁仓合约A",
"0x<大户地址B>": "大户EOA",
}
RPCS = ["https://bsc.publicnode.com", "https://binance.llamarpc.com"]
def rpc_one(method, params):
payload = json.dumps({"jsonrpc": "2.0", "id": 1,
"method": method, "params": params}).encode()
for url in RPCS:
try:
req = urllib.request.Request(
url, data=payload, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=15) as r:
return json.load(r)["result"]
except Exception:
continue
raise RuntimeError("all RPCs failed")
def tip():
return int(rpc_one("eth_blockNumber", []), 16)
def scan(from_block, to_block):
for start in range(from_block, to_block, 2000):
end = min(start + 2000, to_block)
logs = rpc_one("eth_getLogs", [{
"fromBlock": hex(start), "toBlock": hex(end),
"address": TOKEN, "topics": [TRANSFER_TOPIC],
}])
for lg in logs:
addr_from = "0x" + lg["topics"][1][-40:]
amount = int(lg["data"], 16)
name = next((v for k, v in WATCH.items()
if k.lower() == addr_from.lower()), None)
if name:
print(f"ALERT: {name} 转出 {amount / 1e18:.2f}")
if __name__ == "__main__":
try:
state = json.load(open("watch_state.json"))
except FileNotFoundError:
state = {}
now = tip()
scan(state.get("last_block", now - 1200), now)
json.dump({"last_block": now}, open("watch_state.json", "w"))
GoPlus 查重点钱包的一行命令:
curl -s "https://api.gopluslabs.io/api/v1/token_security/56?contract_addresses=0x<代币合约地址>"
返回的 holders 字段直接列出各地址余额和占比,找锁仓钱包最快。
调度与报警阈值
cron 每 15 分钟跑一轮,一轮扫约 1200 个块加余额兜底,几秒就够。阈值按代币流通量自行换算,我的经验是:重点钱包单笔转出 ≥ 100 万、任意地址单笔 ≥ 200 万、单地址窗口累计 ≥ 1000 万就值得看一眼。
小结
这类脚本三行代码写不完的地方全是工程细节:RPC 容错、分段扫描、archive 门限兜底、余额基线实测。跑起来之后,解锁日前后你就能第一时间回答那个关键问题——"钱动了没"。