1. 批量生成钱包和私钥
"""
批量生成 EVM 钱包脚本(以太坊 / BSC 等兼容链通用)。
用法示例:
python wallet_batch_generate.py --count 100 --out wallets.csv
功能说明:
- 按照指定数量生成新钱包
- 将 (地址, 私钥) 保存到 CSV 文件中
- 私钥非常敏感,请妥善保存,不要泄露给任何人
"""
import argparse
import csv
from pathlib import Path
from eth_account import Account # eth_account 提供创建 EVM 钱包的能力
def generate_wallets(count: int):
"""
根据传入的数量生成指定个数的钱包。
:param count: 要生成的钱包个数
:return: 字典列表,每一项包含 address 和 private_key
"""
wallets = [] # 用列表保存所有生成的钱包
for _ in range(count):
# Account.create() 会随机生成一个新的私钥和对应地址
acct = Account.create()
wallets.append(
{
"address": acct.address,
# acct.key 为 bytes,需要转成 0x 前缀的 16 进制字符串
"private_key": acct.key.hex(),
}
)
return wallets
def save_wallets_csv(wallets, out_path: Path):
"""
将钱包列表保存为 CSV 文件。
:param wallets: generate_wallets 返回的钱包列表
:param out_path: 输出文件路径
"""
# 确保输出目录存在
out_path.parent.mkdir(parents=True, exist_ok=True)
# newline="" 避免在 Windows 上出现多余空行
with out_path.open("w", newline="") as f:
# CSV 中包含两列:address, private_key
writer = csv.DictWriter(f, fieldnames=["address", "private_key"])
writer.writeheader() # 写入表头
for w in wallets:
writer.writerow(w) # 写入每一行钱包数据
def main():
# 命令行参数解析
parser = argparse.ArgumentParser(description="Bulk EVM wallet generator")
parser.add_argument(
"--count",
type=int,
default=10,
help="要生成的钱包数量 (默认: 10)",
)
parser.add_argument(
"--out",
type=str,
default="wallets.csv",
help="输出 CSV 文件名 (默认: wallets.csv)",
)
args = parser.parse_args()
# 实际执行:生成钱包并保存到文件
wallets = generate_wallets(args.count)
save_wallets_csv(wallets, Path(args.out))
print(f"Generated {len(wallets)} wallets -> {args.out}") # 打印结果提示
if __name__ == "__main__":
main()
生成结果
address,private_key
0x3edF6bAa9c05B49Ec1Ab6466eDF874F933b5Fdc6,0x4717366318e6b5a291e9336b7a928dc49e288ce097c2b358c86225c018cfd826
0xaaA4B56d178Bd6d7C43AF41aC6f76e68E26773ee,0x08db4706ddf406b003a9d2e5cd5b9218976472ebe32bb7c96f36d55df19d4049
0x9143478fcA59738b4E5231a36B5ddb4b0117E6EE,0xdb11a2b49b3e821f97c46f2deca265a202cff8fe3e3a6829d38c373bc358775b
0x07065a76093639238De4ddC2dc87661145634012,0x5e2e17d5ea713b69b5c73ab0bc588cf36278a80c3ba195fe5673a8613546fbff
0x95E53d0b44E6e7A7E3DdAeC7e7AA1861CA449e60,0x6fca47db904bf464eca3b466f89f7c02f1ab500bf00a4f4070100992d94eda6b
0x04B655F4148da3a1A91bB64f17CFf5844F481037,0x6c2c4376d071f92bd040ea98fe589c785506b20c5d95d34367da2eca73410df8
0xC5998fCB55FbD9DA586Db8254a56cc92Fa10957c,0x8aba7635ccc76350d6517645321a9e88aa385c53ab34212813424b023a7dadfe
0x08B2aC23755B79F07cB8cf65b1641CE88aB2320B,0x728b3d97c2dbfbce7aa96304e897e566bc11dbe0d2488d453ce673496b02ac7d
0xDBA53305618Fbb26d23b876288e4e98750E91d2F,0x98813cce44d89879047d88dce4e621b83e1ba8457dda47ed4974f87e87b8c2f6
0xA346bAc7cD6e2ef0871Eda4e2d10d6a81e703785,0x5915e05dc8aa9155251e8242e5d8e4381c16ea6468b1b5328063b3572b098120
0x33f7e149C6103c9ac71cc2C1E27E50245976975b,0xcb2ffaa275bbbca24eaf7d314b85713045cc60012c1c8d47a5ca316b4bf7e8a3
0x9D8ee198B380c7F7E877d56035d93DBd5f9223b0,0x92d857c012c26a4225c54a8fa489e3a668109a5b0c2e7137546f1bdb671d2090
0x15FE88aD587d13304f5cbc42f3100120f178B232,0x1287ee9634bd64439039e095309daad49551327e3ce7653651d5e8b7023e13a5
0x1CB48A714F34f7cA30a87b3A31c476d729887aE0,0x5eda61ced65b53ea1c7b2bad5e32222cb5bf6cbdf647da1c2a8f338a8d201855
0xEdA3Da34e8fCE33da93c5399F7Bb1df9B87CAB1B,0xf6d5260ec404c33aa409d328c31ad41a0bd59f31403896f3ab29a3e8b481d0ea
0xAD47198630bf577Df2BAcB4313A08f1b92885240,0x005d91a11e5a5ef28d2eda3403ed61af487c11ebf577f18012f0dc40caa3bd42
0x15a835994b286c15F4a95ccc3e0C4409B2916f34,0xc05169f04db1061d10951c5f470c9271fe5e81e212faaa3e6479ab4bef5e34a0
0x835AAA9A48Bd9E9811D8CAceA168B43e3B3581a4,0x9089006a8109ee3ad7b0786de0fb263f62d5e6adfc9ecbec683ddaba9c594c5f
0x2CF3fD65741107C3e8ed79d3D3cAa6aF44eB4742,0x411bd4d3595c55c3e372c56409c18bcf3ba28872a66035a19dda83b3debbe280
0x7bd97328229D567D0739622A6D997A5A6880Ca56,0x7fb168a7783921f73b94096ffb5460618faf70ca0f6419f02bd15833cb99a1ee
0xe148a7b312995B0aB06F85a05f4EE44e50A0Aef1,0xcf4269efbc333e31173073ed941f0471cb97721b43b17baa2b644379da27eef8
0x07D328EfB7163F1A19140511dFab3943A9EcfcB6,0x5a12c6806a88dd8e3703d83330be6d59467f768695a35655f974eb2dd06b607d
0x1484fE55738144914d487f81E59175455e79AFac,0xd694f6d5930f6ebf250292bd501ba8769d6da2737bdf6fbbf0ff7092d3fa4196
0x30447414b4EAD257f57004e2C878465B71199d3f,0x88a0dd0fb4110c64551ad26d74a7319d31d142c2aecae6eef5419dc574c270b3
0x4Be8d84dF668b0828f265a9d0161553197B2ab39,0x569001e17ae8cf045914f073a93e32b261ef6c1d0082898b14f11842f3683fc7
0x1b622Fab6ffFACE9047D58f1c341f5D4E66B219B,0x1d7ef3ce24d94b56f05e41e838a4a595323e585575e6a0d758a77daafd37b6a0
0xAA0081F45cB889EA73DD261a201E51E5Db3d7b6e,0x3fa75310703491635e6718b65c82b66a2a363f980fabd674b5866f2e66605466
0xA6D6C859A1D1D1A62358Aa61b3328a88f76C1210,0xef8109f98e25d36f749eb26ae2ba633c5bc951766e97267d08f5746d4767ef7c
0xd7d1a089249D558910B1D8bf6b310a06115a65dB,0x657a00b5c6ca494757e0c2f77b5e05d3c75b0c0964ca4abebfb5cc0165a03382
0x9cdf151E2a0c8A19967Ac7d61bA0790484fd4557,0xf4476852cf7786ff926987759da21a1ada5fea4cddec45508f31f214fc016aa4
0x00754d1e968327700d8B4E01FCf145EC937aEe5C,0xd2cd10b47d5a3c8a89cf5ba7aafc2359a7ad34fb46710490a36aba07ab411ad3
0x580CcbeAcb9d6e3f80F64CDD86D24cBD7ADAcc4e,0x550ce1c3f06ab40e3a0ff9844141423fb4d388b75927c80ccded2e0dd6537614
0x8888AAf9A899bD119acF72784cC220550C2cEdd3,0xc499917ec485a0d8df72f72aa7fcab113d1c06c6716be52ae19cbf04e8ce5b12
0x3a0bAf91f6D5B8be976Fb2c7aAbF3bB370d73bA1,0xb2fb94e75432bcc3ffdb53b8412b9422e9e62358f493f556bea7f43c33afb92f
0xDAcaf0477A79091Fa9F210796Dd1cA39B10fbaa7,0x76a974cd576f3619b37027f8d2f889bce6ccfd3f79e06432cd9d06a246124c87
0x5a6146957f486A8D762f6f4936d688E7a1739c79,0x141fe8e450939ec7b97ee2a3ea75ee74c34fae0714d5fad0fe0d52cc790c52e0
0x1c61372b2C3a62Ea3D809C5dAEd64604749B66FD,0xb30cb8034c4f5a670ff1bbc1cc3f00c3dc642acc7732c44795e6e3449bee9704
0x584b58eC28aBA2f4567de3F46556E93A2c102657,0x242b9d4c724342516627fe56a4f8100894a0a65cebf5fd1b8b1f2959f802951d
0x862271F54a254566909de1B14Fb748F35A0f7Fa4,0xfce14c522af1fb651a2703e399fbba3022c550937819198c9623741e9ef27c27
0x8e74c862079d5Ac5D780A03cAde5b05d05938577,0xfbfc015af2fe50853b0cf27b4684ea46addd2af95fd07d150cbe353c19837f9b
0x454B7118014777A3878DFF4044Abf7Eec309AbDc,0xbd913060d8a1b83c902423dde089c3eb8f6219843f9b8b1131dcdff7dcfb5d00
0xFe0232c3Bd7e422e3f0B7aD3655A145fD5641E44,0x34840ea609ea77e6d94dbb46dc354a6f7e2a9eb09c5302231333b1aae70d3826
0x8594e20d3f880E001c7621db2b5Ee8Ff2Bd5Df5B,0x36d7eb1665481b40c80a8ddcd4a627990b8d51dda6c7393af17528b2f65bfd22
0x00B0390891C09097b3b510656f57efE21afC3f27,0x9c6ac4b6cc17e15b15be8d1525666d060aa403f72f6c9c3db2c465fe9afaf0ce
0xBb7a46AECfB70DcBCd4c0472Adb44230dabcA20A,0x774cff93db2817074d05eb16bd2a253f617ea56ad6a6d5deb1fa3c472598ac79
0x197563446733aB62413Da05644A4e7624f345B12,0xd657c0ec0b2cdee5c8d244ab5a259bb444e2fc12dcb3bd84dab2a6c51efc3602
0x77F387bbf20929fE3C4b9ED2912AC014D2D818c0,0x290b93d84a850cdba2936df0045c7c12c9382609e85f8b7e07bf68ae4751a257
0xcd8d5B74617364Ee5bc98b6664D773636CBca69B,0xea919f59f059a62747b394d04c54619c4c2e128ca4ec51424ae2a27eafd02d59
0xaf3aad9d2d4F0d975496c12C99f2B6C227ff8789,0x8c61ade1b503c37aecf4ed8200368abba3ca22b107e3a11f3c6c0c6b68eec165
0x1437C17783da3Bb6b4Bcee656DF3cAb7c48708a7,0xe4885f71eef53b5e2873ed1e1d6c969e364fb5960d8abc7c7aaba4ecf611459e
0x98c2b2a5e3562eC4096be2155607164f1FF18FbE,0xe9cec0bba41432bbe6942a0f910e0303601b8519484ef22073a07c477130bcdf
0x83ce2dD31d77CF46A6fFA37962a92447858e6BB1,0x150f3b6224819b91f00bac1a13c96fd22967a5d9bf48f4f5144e5a0147156ebf
0x7E4B87254bfa83C0729B7736b1aC900452E3D66E,0xa707a1d284ace3abfff9c499c60a6b5818b72b1a9f61359a6707eeceff45ef51
0x266DB4c1f7CBc6b490Ce252D05e6EAb05f0941b6,0x29fb6b5dd3c67e43c96a4490b27aaf99d8f3a378a909c2c812bb66c3e8765d58
0x16fe7513d013d1656569f349B5058302d8033d15,0xc01837fdda629d4b09c32474a391d47c0755132a5cb6e0cd7b79d911b443f8ad
0x2f9841B52fec381e7cD625b4197a5346050BA792,0x8897550f3ab2a905dce04bfb2ac8a65ae31cb084d26be9bd668d5176ee5e734d
0xDFbC97ecc83da03660Bf3C49DE73fe6726a9AbC3,0x4cf961697e6d409d5247862bbb633eaf2c94074de6a81ce00f8a1710db73c95e
0xD6d2C266f32f728E9Fa448b9B9cB0B88ee39A975,0x3daed7cb06a4e977c2c5f8cfe58e4b8396d105d28021c0c15fb4afb748d95325
0xE00A16cBCa1a86b0E3d3f75C70f55e0D8E20cBf0,0x320df731c8cbbaa39ea55fbc4eb711d5a57d0b45e4b91c716ce4c80998cb074e
0xD72b742B24914dCAdd9A44E4c36C129c88E499FC,0x09e1027035f2a2556684c40e2b4519b6c39ce1d2a6190ad5f2bb4276ebf6788e
0x519b32Fe735352EFDd455F8f953661D9bF4743eb,0xcf88c99220eea59f59933c5d0a4c8be777917340983ea2c78fcd9a432773f565
0xC748224Eab82a076c38eb7ebF2699d88e74C7E8A,0xcee9f76cb74eeb49523896c1d9554a9591fafcaef7df0aa0d8a0f745355af206
0x357abd9A1F46D1a3295735F850f4EeC584B74708,0xd3186b55690389a16ffd8d029e3d08bc1a2f5afa422b86de57ab9a75072d1968
0x642BfBd1fcAd284E1F6bc423B838c1df978A4bE7,0xade5c496eb83fcc86016b590641d411fe1d45ec37944e4ba6fe100445e2dff1a
0x7F7eF159fc1893621802cebb605DafdA27d7f715,0x39ded5f9ee58a83035c6ef14aac615ab28a0f8a0601e195f10c417a6fe4aea7e
0xD4bd275C7C3bAE6Cbb58275469Ad0Bd67dDFf8c0,0x705f050db66146b34a3dd32170db2738318c22e59fa72774c66f9aa695df6a44
0x789DBe3cd8E8CBD551D7b5667BE75Beb541A3ef2,0xa1d7d7e9a82c90a9b5d8a50f51aeed244f45ed6a9766d0c08d8d51dbd72dd6ae
0x1917F8FF1Cb21eb376b76f87e0F9f78771C22805,0x8084dc5d8b29d94f2d5c859ae23dfb1ceff80019a3de0a1c6507e8368df66e9a
0x6cb033267F60385Cfc882F395094e1Cc09d18A70,0xc391674bb15974e42f93cc18523e6c4b233e61756e9c2ec4316a82c0965f93e1
0x623182Ee69b66E7e0600Fc9Eb3bc79bb15Fb8371,0xab042fa2cd18a7597b41f0bebc150391e79ac4de3072e6704ce75f992ca81c82
0xa37280908c5F69FBB36cf2d1768cF2AA3a56ecCd,0xd1e7b2eb8eb6c7fec54943c03cf96b869d5f66b03c80f2d39d94274c045f9636
0x2c2Bae2b027d73FeD07eBB395507ECa82C579299,0x2037a6de0b7be1a05112e2f11930c91d2082de1775d2113d4b0332e53b448a73
0x80602659C14bB59a847402FA745E6c39c1E70A19,0xbb973ca834fd4c45161ffe704d7628a7664e6147e812d809ce9dfc76c9e843b4
0x707613d171599eC4ac7921000Ad2A1E3a5d39652,0x2607739d2d54312adc2c179a1f5f3aa65b8d5630b05ea1a4b260c2d4a765552c
0x4d1D25266925ff374Bb9B77898db57De300E0062,0xd037a83814b78446d4e9b469d0e3d944eb82490575de78543b23706c0f7062f5
0x5128f69D97Cf9C34eC31dEB205419689E35f9E35,0x703582402d3303ed414642a58e1a03070f91db41593d30ddd3eb876ebdeebcfe
0x37E44022C806284818165fA9fd4A232aA49aF8d7,0x8dc0b89ba74c3a22ac01a8b246b5025dda8e2a33ea6903659161d7f1b310c18f
0x32dCe9E5E0eDbd17280D96a8687d78461c73f71D,0x4ea118ae47c047b901165a3abfe57e483595d7f25e7f8ec06284c0c63ea30007
0xA9fe2c6F067c86C96239442820cf6E4Db9134E0d,0x051dd5562d8811ec2bb434d674b84ea3b86190377fe248419a7fa0ba39077236
0xd3698229895f660f002878fe2FfbfF81016b3129,0xf9a858c319e799252d8a7afb4c6a8a61ba22a43719e003dcd0536a7c4bfb753b
0x73261CdF6b2CF32394e081eCCe5F0b5a2d593115,0xfca9eaccba547c066c5ce0cd32025fcb04f871817897b4c02e2cda37d8561e1c
0xce3485285Ccfb5dBFfB4f5584ed9466c2C0cB68E,0xfe8bf62c5a224fe83d14b52d4c15b8f80ab64992777a8c06a28669ce919a6647
0xF19CD6dd4626c87864cE84CEE2561B14b3BA0d8d,0xc160e7a7ff26cd850209d2d99cd3d703db02912bbdd621843caefa210c059d1d
0xe3892997A6752B6AcD0037bf98cAbA21CA5bd9Bf,0xa2ba8295b74f2ddeed01cb2d151f204cad26c70df0e2b5d01b2340552f383c56
0x09f6A44B927e5b0C73CF0A878cE6482AbA3387E5,0xa754bf89f10f3b03b7345a94899596b39a5bebf51b2bfb8ae83c81499b8dbbf2
0x3b02Dd161C1Fa5dC2400900F955E14df722A05F8,0xc74ac7aa6706901db48d2ce87bcbfe610c3d1982289f70cffd5075610d20fb32
0x8d4DbD31aA794b3cB51438955E5d0Aa75D88F10E,0x8495b013ed03602d324ef1531b3c112397c6dfe499a06bf9fc3ede1f624cced4
0x963bd4B16C196eDBf3222FbA1dbB1EeD4BD41763,0x5c60cfbf51ecd0c30c7498b2d8d79862ca20de71500b34879176851567676cce
0x83581D7831969EC859e969341D9Ef55F1dddC2f4,0xcfad65fadc733db9b30a252c1bf1a6bea42fb8cf8f6c306b0caac90afaac16e9
0x032555711991b4495e46A0987F956f538B471df6,0x0f9209a03a2fde32634653446db3385cf1ba43b842e97dd1b12aec7b2a62b6ae
0x983c8fa89b60b52C3f29A83b7D2AC564302FC6b8,0x68c095c299513f183ca1742a7cdfbfaf691de625576681bebe2cea13f3dcaed0
0x4ed2ea2671A9f6210BD546BA9b851c7F867A3385,0xdd7e81d2ac901b686fbf94b52b462c8eed00c12cda73e5df813a66e961b7ebff
0x89853B5788a344f734c9c9603d81373E2984A2b8,0xb95a2bdf49c6e00cd95d04118b79aa701cd49aa3a64cf597b89693eef59bc243
0x7CbCE9732ec177fD6EAc0C72dfac3b9052b80BfD,0x37f4f839f301ac1a22cd1c45c8542a55ac73316a7b6f8053b53bc1e99726b092
0x1e8De18B04630c8e8F1CF53A8B61c635982A2C7E,0x5f9a7de4a45fa23cbceed9b9783b90f664ce4488647a7603975c295e92f83e5e
0xa51eC9E1F72f8d11f78D089C096ceAa2aC1964A5,0xa894e5f220e850e074717c376d76fcd1f1855719c1b0ddd144cf0cfc252071d5
0x45421E27ffa6b21FdaA5C995c15F47b33d522fD5,0x459d11f7932cda03f9ff2676e5933c7b71ce40b122ef40bbd86b6047b89ae4cb
0x1C8fA00Cdd287C26671eDEECc8Ad1bc27b66267A,0x62b70f6b09b2c4789ad5ba041728454c9f052be9973a9ef280eb14551724f99a
0xB37CcA95e441393486d0219C13f4e158F55d9A95,0xbd113c6503f17041b3193fc99e0ade660a9a110929851cb2cb3158ea6ad0ec13
0x702869D0aE6749C0883838D8e6c65354c2d60210,0x2e9308c77035f7582c25de648a399254bbece31aaba969cf2e54290a18202e1d
2. 自动做市脚本
"""
Simple AMM market-making bot example for an EVM DEX (e.g. Uniswap V2-style).
注意:
- 该脚本仅为学习示例,不保证收益,也不适合直接用于实盘。
- 做市存在无常损失、滑点、抢跑等多种风险,请谨慎评估。
核心思路:
- 监控某个交易对价格(从 DEX 或预言机读取)。
- 当价格偏离你设定的目标区间时,自动挂单/换币,把价格拉回区间附近。
在真实环境中,你还需要:
- 更强的风控(持仓限制、止损条件等)
- 更完善的日志和监控
- 更安全的私钥管理(硬件钱包、托管服务等)
本版本额外支持:
- 从已生成的钱包列表 CSV(wallets.csv)中按序号选择一个钱包做市,无需手动复制私钥。
"""
import argparse
import csv
import os
import time
from dataclasses import dataclass
from decimal import Decimal
from pathlib import Path
from eth_account import Account # 用于从私钥恢复账户
from eth_account.signers.local import LocalAccount
from web3 import Web3
UNISWAP_V2_ROUTER_ABI = [
{
"name": "getAmountsOut",
"outputs": [{"type": "uint256[]", "name": "amounts"}],
"inputs": [
{"type": "uint256", "name": "amountIn"},
{"type": "address[]", "name": "path"},
],
"stateMutability": "view",
"type": "function",
},
{
"name": "swapExactTokensForTokens",
"outputs": [{"type": "uint256[]", "name": "amounts"}],
"inputs": [
{"type": "uint256", "name": "amountIn"},
{"type": "uint256", "name": "amountOutMin"},
{"type": "address[]", "name": "path"},
{"type": "address", "name": "to"},
{"type": "uint256", "name": "deadline"},
],
"stateMutability": "nonpayable",
"type": "function",
},
]
@dataclass
class BotConfig:
rpc: str
router: str
token0: str
token1: str
base_token: str
target_price: Decimal
spread: Decimal
trade_size: Decimal
poll_interval: int
def load_private_key_from_wallets(csv_path: Path, index: int) -> str:
"""
从 wallets.csv 中按序号读取私钥。
:param csv_path: 钱包 CSV 路径(例如 wallets.csv)
:param index: 选择第几个钱包(1 表示第一行,不含表头)
"""
if index < 1:
raise ValueError("wallet-index 必须 >= 1")
if not csv_path.exists():
raise FileNotFoundError(f"钱包文件不存在: {csv_path}")
with csv_path.open() as f:
reader = csv.DictReader(f)
rows = list(reader)
if not rows:
raise ValueError("钱包 CSV 为空,没有可用的钱包记录")
if index > len(rows):
raise IndexError(f"wallet-index 超出范围,总共有 {len(rows)} 个钱包")
row = rows[index - 1]
# 兼容列名:private_key 或 privkey 等,优先使用 private_key
pk = row.get("private_key") or row.get("privkey") or row.get("pk")
if not pk:
raise KeyError("在 CSV 行中找不到 private_key/privkey/pk 字段")
return pk.strip()
def get_router(w3: Web3, router_address: str):
return w3.eth.contract(address=w3.to_checksum_address(router_address), abi=UNISWAP_V2_ROUTER_ABI)
def fetch_price(w3: Web3, router, token0: str, token1: str, amount_in: int) -> Decimal:
path = [w3.to_checksum_address(token0), w3.to_checksum_address(token1)]
amounts = router.functions.getAmountsOut(amount_in, path).call()
return Decimal(amounts[-1]) / Decimal(amount_in)
def build_swap_tx(
w3: Web3,
router,
account: LocalAccount,
amount_in: int,
amount_out_min: int,
path,
gas_price_wei: int,
):
deadline = int(time.time()) + 60 * 5
nonce = w3.eth.get_transaction_count(account.address)
tx = router.functions.swapExactTokensForTokens(
amount_in, amount_out_min, path, account.address, deadline
).build_transaction(
{
"from": account.address,
"nonce": nonce,
"gasPrice": gas_price_wei,
}
)
gas_est = w3.eth.estimate_gas(tx)
tx["gas"] = int(gas_est * 1.1)
return tx
def main():
parser = argparse.ArgumentParser(description="Simple AMM MM bot example")
parser.add_argument("--rpc", required=True, help="RPC endpoint")
parser.add_argument("--router", required=True, help="UniswapV2 router address")
parser.add_argument("--token0", required=True, help="Base token address (token0)")
parser.add_argument("--token1", required=True, help="Quote token address (token1)")
parser.add_argument(
"--target-price",
type=Decimal,
required=True,
help="Target price token1/token0, e.g. 2000 means 1 token0 = 2000 token1",
)
parser.add_argument(
"--spread",
type=Decimal,
default=Decimal("0.02"),
help="Allowed deviation from target price (e.g. 0.02 = ±2%)",
)
parser.add_argument(
"--trade-size",
type=Decimal,
default=Decimal("0.01"),
help="Trade size in token0 units (for demo only)",
)
parser.add_argument(
"--poll-interval",
type=int,
default=15,
help="Seconds between price checks",
)
parser.add_argument(
"--wallets-csv",
type=str,
default=None,
help="钱包列表 CSV 路径(例如 wallets.csv,包含 address,private_key 列)",
)
parser.add_argument(
"--wallet-index",
type=int,
default=1,
help="使用 CSV 中第几个钱包(从 1 开始,默认 1)",
)
parser.add_argument(
"--gas-price-gwei",
type=Decimal,
default=Decimal("5"),
help="Gas price in Gwei",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Simulation mode: log actions but do not send transactions",
)
args = parser.parse_args()
# 优先从 wallets.csv 中按序号读取私钥,其次回退到环境变量 PRIVATE_KEY
if args.wallets_csv:
try:
pk = load_private_key_from_wallets(
Path(args.wallets_csv), args.wallet_index
)
print(
f"Using wallet #{args.wallet_index} from {args.wallets_csv} as bot account."
)
except Exception as e:
raise SystemExit(f"从钱包 CSV 读取私钥失败: {e}")
else:
pk = os.getenv("PRIVATE_KEY")
if not pk:
raise SystemExit(
"请通过 --wallets-csv + --wallet-index 指定钱包,"
"或者设置 PRIVATE_KEY 环境变量。"
)
w3 = Web3(Web3.HTTPProvider(args.rpc))
if not w3.is_connected():
raise SystemExit("Failed to connect to RPC.")
account: LocalAccount = Account.from_key(pk)
print(f"Bot address: {account.address}")
router = get_router(w3, args.router)
# For demo we assume token0 has 18 decimals and use 1 token0 as base amount
amount_in_demo = 10**18
lower = args.target_price * (Decimal(1) - args.spread)
upper = args.target_price * (Decimal(1) + args.spread)
gas_price_wei = w3.to_wei(args.gas_price_gwei, "gwei")
print(
f"Target price: {args.target_price}, band: [{lower}, {upper}], "
f"poll every {args.poll_interval}s"
)
while True:
try:
price = fetch_price(w3, router, args.token0, args.token1, amount_in_demo)
print(f"Current price token1/token0: {price}")
if price < lower:
# price too low -> buy token0 with token1 (expect price goes up)
print("Price below lower band -> buy token0")
if not args.dry_run:
# NOTE: For real bot you must handle approvals, decimals, slippage, etc.
path = [
w3.to_checksum_address(args.token1),
w3.to_checksum_address(args.token0),
]
amount_in = int(args.trade_size * Decimal(10**18))
min_out = int((Decimal(1) / (upper)) * Decimal(amount_in) * Decimal(0.98))
tx = build_swap_tx(
w3, router, account, amount_in, min_out, path, gas_price_wei
)
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
print(f" Sent buy tx: {tx_hash.hex()}")
elif price > upper:
# price too high -> sell token0 for token1
print("Price above upper band -> sell token0")
if not args.dry_run:
path = [
w3.to_checksum_address(args.token0),
w3.to_checksum_address(args.token1),
]
amount_in = int(args.trade_size * Decimal(10**18))
min_out = int(args.target_price * Decimal(amount_in) * Decimal(0.98))
tx = build_swap_tx(
w3, router, account, amount_in, min_out, path, gas_price_wei
)
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
print(f" Sent sell tx: {tx_hash.hex()}")
else:
print("Price within band, no action.")
time.sleep(args.poll_interval)
except Exception as e:
print(f"Error in loop: {e}")
time.sleep(args.poll_interval)
if __name__ == "__main__":
main()
做市结果
warnings.warn(
Using wallet #1 from wallets.csv as bot account.
Bot address: 0x3edF6bAa9c05B49Ec1Ab6466eDF874F933b5Fdc6
Target price: 3000, band: [2940.00, 3060.00], poll every 30s
Current price token1/token0: 1.870626537E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870945274E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
Current price token1/token0: 1.870843304E-9
Price below lower band -> buy token0
3. 批量空投
"""
批量空投(批量转账 ERC-20 Token)脚本,适用于 EVM 兼容链。
脚本原理:
- 使用一个发送钱包地址,依次向 CSV 列表中的多个地址调用 token.transfer()
安全提示:
- 请先在测试网验证脚本,再在主网小额测试。
- 私钥只应通过环境变量或安全方式传入,避免硬编码在代码中。
使用示例:
export PRIVATE_KEY=0x你的私钥
python airdrop_batch_send.py \\
--rpc https://mainnet.infura.io/v3/YOUR_KEY \\
--token 0xYourTokenAddress \\
--csv recipients.csv
recipients.csv 文件格式:
address,amount
0xabc...,100
0xdef...,250
"""
import argparse
import csv
import os
from dataclasses import dataclass
from decimal import Decimal
from pathlib import Path
from typing import List
from eth_account import Account # 用于从私钥恢复账户
from eth_account.signers.local import LocalAccount # 带签名能力的本地账户类型
from web3 import Web3 # web3.py 主入口
from web3.contract import Contract # 智能合约类型
ERC20_ABI = [
# 标准 ERC20 的 transfer 方法
{
"constant": False,
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"},
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function",
},
# 标准 ERC20 的 decimals 方法,用来读取代币精度
{
"constant": True,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"type": "function",
},
]
@dataclass
class Recipient:
address: str # 接收地址
amount: Decimal # 要发送的代币数量(未乘以 10**decimals 之前的“人类可读”数量)
def load_recipients(csv_path: Path) -> List[Recipient]:
"""
从 CSV 文件中读取收款人列表。
CSV 格式: address,amount
"""
recipients: List[Recipient] = []
with csv_path.open() as f:
reader = csv.DictReader(f)
for row in reader:
recipients.append(
Recipient(
address=row["address"].strip(),
amount=Decimal(row["amount"]),
)
)
return recipients
def get_token_contract(w3: Web3, token_address: str) -> Contract:
"""
根据合约地址获取 ERC-20 合约实例。
"""
return w3.eth.contract(
address=w3.to_checksum_address(token_address), abi=ERC20_ABI
)
def main():
# 解析命令行参数
parser = argparse.ArgumentParser(description="Bulk ERC-20 airdrop sender")
parser.add_argument("--rpc", required=True, help="RPC endpoint URL(节点地址)")
parser.add_argument(
"--token", required=True, help="ERC-20 token contract address(代币合约地址)"
)
parser.add_argument(
"--csv", required=True, help="Recipients CSV file (address,amount)"
)
parser.add_argument(
"--gas-price-gwei",
type=Decimal,
default=Decimal("5"),
help="Gas price in Gwei (default: 5, Gas 单价)",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="仅打印将要发送的内容,不真正广播交易(模拟模式)",
)
args = parser.parse_args()
# 从环境变量读取私钥,避免写在代码里
pk = os.getenv("PRIVATE_KEY")
if not pk:
raise SystemExit("Please set PRIVATE_KEY env var for the sender wallet.")
# 初始化 web3,连接 RPC 节点
w3 = Web3(Web3.HTTPProvider(args.rpc))
if not w3.is_connected():
raise SystemExit("Failed to connect to RPC.")
account: LocalAccount = Account.from_key(pk)
print(f"Sender address: {account.address}")
# 初始化 ERC20 合约,并查询代币精度
token = get_token_contract(w3, args.token)
decimals = token.functions.decimals().call()
recipients = load_recipients(Path(args.csv))
print(f"Loaded {len(recipients)} recipients from {args.csv}")
gas_price = w3.to_wei(args.gas_price_gwei, "gwei") # 将 Gwei 转成 Wei
nonce = w3.eth.get_transaction_count(account.address) # 当前账号的 nonce(交易计数)
for idx, r in enumerate(recipients, start=1):
# 将“人类可读”的数量(如 100 USDT)转成链上单位(乘以 10**decimals)
amount_wei = int(r.amount * (10**decimals))
# 构造 ERC20 transfer 交易
tx = token.functions.transfer(
w3.to_checksum_address(r.address), amount_wei
).build_transaction(
{
"from": account.address,
"nonce": nonce,
"gasPrice": gas_price,
}
)
# 预估 Gas,上浮 10% 作为安全冗余
estimated_gas = w3.eth.estimate_gas(tx)
tx["gas"] = int(estimated_gas * 1.1)
print(
f"[{idx}/{len(recipients)}] Send {r.amount} tokens to {r.address}, "
f"gas={tx['gas']}, gas_price={args.gas_price_gwei} gwei"
)
if args.dry_run:
# 仅打印,不真正发送交易;nonce 仍然递增以模拟真实流程
nonce += 1
continue
# 使用本地私钥签名交易并广播
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
print(f" tx hash: {tx_hash.hex()}")
nonce += 1
if __name__ == "__main__":
main()
批量空投执行结果
(venv) apple@jasperMacBook-Pro jd % python airdrop_batch_send.py \
--rpc https://ethereum-sepolia.publicnode.com \
--token 0xbc678aa55423d838954E1e816d43518D3a45F069 \
--csv recipients.csv \
--wallets-csv wallets.csv \
--wallet-index 1 \
--gas-price-gwei 5
/Users/apple/code/jd/venv/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
Using wallet #1 from wallets.csv as airdrop sender.
Sender address: 0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6
Loaded 99 recipients from recipients.csv
[1/99] Send 10 tokens to 0xaaA4B56d178Bd6d7C43AF41aC6f76e68E26773ee, gas=57214, gas_price=5 gwei
tx hash: 0xb994e54064fff373277ca6f21b9ded238da85ba4c3819489a7b94f395ce6b845
[2/99] Send 10 tokens to 0x9143478fcA59738b4E5231a36B5ddb4b0117E6EE, gas=57214, gas_price=5 gwei
tx hash: 0xf93e4d432556d553e6e2a5ca2e062a2fe4d88466085a453b8152593f7148b1c4
[3/99] Send 10 tokens to 0x07065a76093639238De4ddC2dc87661145634012, gas=57214, gas_price=5 gwei
tx hash: 0xd5706659cd4fb1d801e88c6964b418e7e86372745ce7ea3d382756fb0a3c1f08
[4/99] Send 10 tokens to 0x95E53d0b44E6e7A7E3DdAeC7e7AA1861CA449e60, gas=57214, gas_price=5 gwei
tx hash: 0xc9a21f3425846373972399713889a92711d10aa3d267e26b5a83360842ec90e0
[5/99] Send 10 tokens to 0x04B655F4148da3a1A91bB64f17CFf5844F481037, gas=57214, gas_price=5 gwei
tx hash: 0xd8f817fb35ba8a40fc8b978513a04d420284003741cbaadd248e36fce958f200
[6/99] Send 10 tokens to 0xC5998fCB55FbD9DA586Db8254a56cc92Fa10957c, gas=57214, gas_price=5 gwei
tx hash: 0xdb13e29bbb4cb8aa423f1234964ad6e590ffdcbe909e024a9acf931474531397
[7/99] Send 10 tokens to 0x08B2aC23755B79F07cB8cf65b1641CE88aB2320B, gas=57214, gas_price=5 gwei
tx hash: 0xecdaad0b10e6368885d253a69db1a99f41fe3f8b148064f2f49b365a8e140474
[8/99] Send 10 tokens to 0xDBA53305618Fbb26d23b876288e4e98750E91d2F, gas=57214, gas_price=5 gwei
tx hash: 0x0686f960abc8b991270ea2f4824974c390d4753f7aac4b3e49f87b83f8bc9b68
[9/99] Send 10 tokens to 0xA346bAc7cD6e2ef0871Eda4e2d10d6a81e703785, gas=57214, gas_price=5 gwei
tx hash: 0xa1d7ebaec55d4938f38f76486e05de7199baaf59c0f858c70b1118cc9124c608
[10/99] Send 10 tokens to 0x33f7e149C6103c9ac71cc2C1E27E50245976975b, gas=57214, gas_price=5 gwei
tx hash: 0x67b4d7462d59a9f8b5ca6d7203068bca980772a85c8323581f9e3a55f4c57cc1

评论区