Is this TRC-20 contract safe? Paste the address, find out in seconds.
No install, no code to read. This checks any TRON contract — a presale, a token, anything — against 9 structural red flags in plain language, live against the real on-chain ABI.
This isn't a generic "best practices" list. It's the exact set of checks that come from actually designing and shipping a presale contract honestly — each one maps to a specific, real way a presale contract can be built to work against the buyer instead of for them. Kinetix's own AUDX presale contract is used below as a live, checkable example of what "passes" looks like.
Why the ABI matters more than the marketing
Every TRC-20/ERC-20-style contract exposes an ABI — a JSON list of every function it has. Unlike a website's claims, the ABI can't lie: if a function exists to move funds somewhere, it's in there. The fastest due-diligence step for any presale is reading the ABI before reading anything the team wrote about themselves.
The 8 checks
Look for a function like withdrawUsdt(uint256 amount) vs withdrawUsdt(uint256 amount, address to). If the destination is a function parameter, the owner can send collected funds anywhere, any time — including a fresh wallet you've never seen. If it's fixed at deployment, the owner is structurally limited to one address; check what that address actually is.
Whatever function returns unsold tokens after the sale closes — a parameterized destination here is just as much of a red flag as on the withdraw function.
Search the ABI for setPrice(), updateRate(), or configure(). A presale's entire value proposition is a fixed price — if the owner can change it after you've committed to buy, the "fixed price" claim is misleading.
setCap(), increaseSupply(), extendPool() — a pool that can grow after launch dilutes everyone who already bought in, with no way to know it happened until they check.
transferOwnership() / renounceOwnership(). Ownership isn't automatically bad — a presale needs one to close the sale and withdraw proceeds. What matters is whether it's transferable to a new, unknown address at any time.
pause(), blacklist(), setTradingEnabled() — mechanisms for selectively freezing specific buyers or the whole contract mid-sale.
Check for a close() function and who can call it — should be owner-only. Then check honestly whether there's ANY refund mechanism if the sale closes early with money already collected. Most don't; that's normal, but it means reading the docs before sending money matters more, not less.
If you can't get a real ABI — source never published/verified on a block explorer — that's not a technical check, that's the whole due-diligence process failing before it starts. Treat an unverified sale contract as maximum risk regardless of what anyone tells you.
The script
Pulls a verified ABI straight from TronScan's public API (no key required) and flags any of the patterns above automatically. If the contract isn't verified, that failure IS the finding — the script says so instead of guessing.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) and reclaimUnsold() functions take no address parameter at all; destination wallets are fixed at deployment and can't be changed by anyone, including the owner. That's not a claim, it's checkable directly against the contract's own ABI, the same way this script checks any other contract.More free security tools
Rug Pull & Honeypot Contract Scanner
Scores any token contract against 12 known red flags: ownership, mint function, blacklist/pause, buy/sell tax delta, liquidity lock status, holder concentration.
Token Approval Risk Auditor
Scans your wallet's Approval events across EVM chains and flags unlimited allowances — the most common wallet-draining vector, more common than private key theft.
Stablecoin Depeg Monitor
Watches USDT/USDC/DAI/FRAX against their $1.00 peg across CEX feeds and on-chain pools, and pushes a Telegram alert on early drift, before headline-level crashes.
Multi-Sig Wallet Security Playbook
Gnosis Safe setup, auditing, and recovery planning for anyone holding meaningful funds in a shared wallet.
这不是一份泛泛而谈的"最佳实践"清单,而是我们自己动手设计并诚实地上线一个预售合约之后,真实总结出来的检查项——每一条都对应一种预售合约可能被设计成"坑买家而不是护买家"的具体真实手法。下文用 Kinetix 自己的 AUDX 预售合约作为一个可以直接核实的"通过"范例。
为什么 ABI 比宣传文案更重要
每一个 TRC-20/ERC-20 风格的合约都会暴露一份 ABI——一份列出合约所有函数的 JSON 清单。跟网站上的宣传不同,ABI 不会撒谎:如果有一个函数能把资金转到某处,它就一定会出现在这里。对任何预售来说,最快的尽调步骤就是先读 ABI,再读团队自己写的介绍。
8 项检查
看函数是 withdrawUsdt(uint256 amount) 这种,还是 withdrawUsdt(uint256 amount, address to) 这种。如果目标地址是函数的参数,那所有者随时可以把募集到的资金转去任何地方,包括一个你从没见过的新钱包。如果目标地址是部署时就写死的,所有者在结构上就被限制到一个地址——去查一下这个地址到底是不是公开披露过的。
预售结束后把未售出代币收回的函数也是一样的逻辑:如果目标地址是参数,风险和不受限的提现函数一样大。
在 ABI 里搜 setPrice()、updateRate()、configure() 之类的函数。预售的核心卖点就是固定价格——如果你已经决定要买了,所有者却还能改价格,那"固定价格"这个说法本身就是误导。
setCap()、increaseSupply()、extendPool() 这类函数——池子上线后还能变大,会稀释所有已经买入的人,而且他们在自己去查之前根本不会知道发生了这件事。
transferOwnership() / renounceOwnership()。所有权本身不是坏事——预售合约本来就需要一个所有者来关闭销售、提取资金。关键在于:这个所有权能不能随时转给一个全新的、未知的地址。
pause()、blacklist()、setTradingEnabled()——这些都是可以在销售中途选择性冻结特定买家、或冻结整个合约的机制。
找 close() 函数,看谁能调用它——应该只能是所有者。然后诚实地检查:如果销售提前关闭、钱已经收了,合约有没有任何退款机制?大部分都没有,这本身不算异常,但这意味着"打钱前先读文档"这件事更重要,而不是更不重要。
如果你根本拿不到真实的 ABI——源码从来没有在区块浏览器上公开验证过——这已经不是某一项技术检查了,而是整个尽调流程在起点就失败了。不管任何人怎么跟你说,一个未验证的销售合约都应该被当作最高风险对待。
脚本
直接从 TronScan 的公开 API 拉取已验证的 ABI(不需要 API key),自动标出上面提到的可疑模式。如果合约没有经过验证,这个"拿不到 ABI"的结果本身就是一个发现——脚本会如实说明,而不是瞎猜。
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) 和 reclaimUnsold() 函数都不接受地址参数,目标钱包在部署时就写死了,包括所有者在内的任何人都无法更改。这不是一句宣传语,而是可以直接对照合约 ABI 核实的事实,和这个脚本检查其他合约的方式完全一样。更多免费安全工具
拉盘/蜜罐合约扫描器
对任意代币合约按 12 项已知红旗打分:所有权、增发函数、拉黑/暂停、买卖税差异、流动性锁仓状态、持有人集中度。
代币授权风险审计
扫描你钱包在各条 EVM 链上的 Approval 事件,标出无限额度授权——这是比私钥被盗更常见的钱包被清空路径。
稳定币脱锚监控
持续对比 USDT/USDC/DAI/FRAX 与 1 美元锚定价,覆盖中心化交易所和链上池子,在出现早期偏离时就推送 Telegram 提醒,而不是等到头条级别的崩盘才反应。
多签钱包安全手册
Gnosis Safe 的搭建、审计与恢复方案规划,适合任何用共享钱包保管较大资金的人。
這不是一份泛泛而談的"最佳實踐"清單,而是我們自己動手設計並誠實地上線一個預售合約之後,真實總結出來的檢查項——每一條都對應一種預售合約可能被設計成"坑買家而不是護買家"的具體真實手法。下文用 Kinetix 自己的 AUDX 預售合約作爲一個可以直接覈實的"通過"範例。
爲什麼 ABI 比宣傳文案更重要
每一個 TRC-20/ERC-20 風格的合約都會暴露一份 ABI——一份列出合約所有函數的 JSON 清單。跟網站上的宣傳不同,ABI 不會撒謊:如果有一個函數能把資金轉到某處,它就一定會出現在這裏。對任何預售來說,最快的盡調步驟就是先讀 ABI,再讀團隊自己寫的介紹。
8 項檢查
看函數是 withdrawUsdt(uint256 amount) 這種,還是 withdrawUsdt(uint256 amount, address to) 這種。如果目標地址是函數的參數,那所有者隨時可以把募集到的資金轉去任何地方,包括一個你從沒見過的新錢包。如果目標地址是部署時就寫死的,所有者在結構上就被限制到一個地址——去查一下這個地址到底是不是公開披露過的。
預售結束後把未售出代幣收回的函數也是一樣的邏輯:如果目標地址是參數,風險和不受限的提現函數一樣大。
在 ABI 裏搜 setPrice()、updateRate()、configure() 之類的函數。預售的核心賣點就是固定價格——如果你已經決定要買了,所有者卻還能改價格,那"固定價格"這個說法本身就是誤導。
setCap()、increaseSupply()、extendPool() 這類函數——池子上線後還能變大,會稀釋所有已經買入的人,而且他們在自己去查之前根本不會知道發生了這件事。
transferOwnership() / renounceOwnership()。所有權本身不是壞事——預售合約本來就需要一個所有者來關閉銷售、提取資金。關鍵在於:這個所有權能不能隨時轉給一個全新的、未知的地址。
pause()、blacklist()、setTradingEnabled()——這些都是可以在銷售中途選擇性凍結特定買家、或凍結整個合約的機制。
找 close() 函數,看誰能調用它——應該只能是所有者。然後誠實地檢查:如果銷售提前關閉、錢已經收了,合約有沒有任何退款機制?大部分都沒有,這本身不算異常,但這意味着"打錢前先讀文檔"這件事更重要,而不是更不重要。
如果你根本拿不到真實的 ABI——源碼從來沒有在區塊瀏覽器上公開驗證過——這已經不是某一項技術檢查了,而是整個盡調流程在起點就失敗了。不管任何人怎麼跟你說,一個未驗證的銷售合約都應該被當作最高風險對待。
腳本
直接從 TronScan 的公開 API 拉取已驗證的 ABI(不需要 API key),自動標出上面提到的可疑模式。如果合約沒有經過驗證,這個"拿不到 ABI"的結果本身就是一個發現——腳本會如實說明,而不是瞎猜。
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) 和 reclaimUnsold() 函數都不接受地址參數,目標錢包在部署時就寫死了,包括所有者在內的任何人都無法更改。這不是一句宣傳語,而是可以直接對照合約 ABI 覈實的事實,和這個腳本檢查其他合約的方式完全一樣。更多免費安全工具
拉盤/蜜罐合約掃描器
對任意代幣合約按 12 項已知紅旗打分:所有權、增發函數、拉黑/暫停、買賣稅差異、流動性鎖倉狀態、持有人集中度。
代幣授權風險審計
掃描你錢包在各條 EVM 鏈上的 Approval 事件,標出無限額度授權——這是比私鑰被盜更常見的錢包被清空路徑。
穩定幣脫錨監控
持續對比 USDT/USDC/DAI/FRAX 與 1 美元錨定價,覆蓋中心化交易所和鏈上池子,在出現早期偏離時就推送 Telegram 提醒,而不是等到頭條級別的崩盤才反應。
多籤錢包安全手冊
Gnosis Safe 的搭建、審計與恢復方案規劃,適合任何用共享錢包保管較大資金的人。
これは一般的な「ベストプラクティス」一覧ではなく、プレセール契約を正直に設計・公開した経験から得た検査項目です。それぞれが「買い手を守るのではなく不利にする」具体的な手口に対応します。下記では Kinetix の AUDX プレセール契約を「合格」の実例として使います。
なぜ ABI が宣伝より重要か
TRC-20/ERC-20 系の契約は ABI(全関数の JSON 一覧)を公開します。サイトの主張と違い、ABI は嘘をつけません。資金を移せる関数があればそこに載ります。プレセールの最速のデューデリは、チームの文章より先に ABI を読むことです。
8 つのチェック
withdrawUsdt(uint256 amount) と withdrawUsdt(uint256 amount, address to) を比較。宛先が関数パラメータなら所有者はいつでも任意のウォレットへ送金できます。デプロイ時固定なら構造的に1アドレスに制限されます。
セール終了後に未売却を戻す関数でも、宛先がパラメータなら出金と同様のレッドフラグです。
ABI で setPrice() / updateRate() / configure() を検索。固定価格が売りなら、購入後に変更できるのは誤導です。
setCap() / increaseSupply() / extendPool() — ローンチ後に拡大できるプールは既存購入者を希薄化します。
transferOwnership() / renounceOwnership()。所有権自体は必要ですが、未知アドレスへいつでも移せるかが重要です。
pause() / blacklist() / setTradingEnabled() — 特定買い手や契約全体を凍結できる仕組み。
close() の呼び出し権限と、早期終了時の返金有無を確認。多くは返金なしですが、送金前に文書を読む重要性は上がります。
本物の ABI が取れない(未公開/未検証)なら、デューデリは開始前に失敗です。未検証のセール契約は最大リスクとして扱ってください。
スクリプト
TronScan の公開 API から検証済み ABI を取得し、上記パターンを自動フラグします。未検証なら、推測せずその失敗自体を結果として出します。
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) と reclaimUnsold() にアドレス引数はなく、宛先はデプロイ時固定で所有者も変更できません。主張ではなく ABI で直接確認できます。その他の無料セキュリティツール
Rug Pull & Honeypot スキャナー
所有権・ミント・ブラックリスト/一時停止・売買税差・流動性ロック・保有集中など既知フラグを評価。
トークン承認リスク監査
EVM の Approval を走査し、無制限 allowance を検出。
일반적인 "모범 사례" 목록이 아니라, 프리세일 계약을 정직하게 설계·출시한 경험에서 나온 검사 항목입니다. 각각이 구매자에게 불리하게 설계될 수 있는 실제 수법에 대응합니다. 아래에서는 Kinetix AUDX 프리세일 계약을 "통과" 예시로 사용합니다.
ABI가 마케팅보다 중요한 이유
TRC-20/ERC-20 스타일 계약은 ABI(모든 함수 JSON 목록)를 노출합니다. 웹사이트 주장과 달리 ABI는 거짓말하지 않습니다. 자금을 옮길 함수가 있으면 반드시 나옵니다. 프리세일의 가장 빠른 실사는 팀 소개보다 ABI를 먼저 읽는 것입니다.
8가지 검사
withdrawUsdt(uint256 amount) vs withdrawUsdt(uint256 amount, address to). 목적지가 함수 매개변수면 소유자가 언제든 임의 지갑으로 보낼 수 있습니다. 배포 시 고정이면 구조적으로 한 주소로 제한됩니다.
판매 종료 후 미판매 토큰을 회수하는 함수에서도 목적지가 매개변수면 출금과 같은 레드플래그입니다.
ABI에서 setPrice(), updateRate(), configure()를 검색하세요. 고정가가 핵심인데 구매 후 변경 가능하면 오해의 소지가 있습니다.
setCap(), increaseSupply(), extendPool() — 출시 후 커질 수 있는 풀은 기존 구매자를 희석합니다.
transferOwnership() / renounceOwnership(). 소유권 자체는 필요하지만, 언제든 알 수 없는 주소로 이전 가능한지가 관건입니다.
pause(), blacklist(), setTradingEnabled() — 특정 구매자나 계약 전체를 동결할 수 있는 메커니즘.
close() 호출 권한과 조기 종료 시 환불 유무를 확인하세요. 대부분 환불이 없지만, 송금 전 문서 확인이 더 중요합니다.
실제 ABI를 얻을 수 없으면(미공개/미검증) 실사는 시작 전에 실패한 것입니다. 미검증 판매 계약은 최대 위험으로 취급하세요.
스크립트
TronScan 공개 API에서 검증된 ABI를 가져와 위 패턴을 자동으로 표시합니다. 미검증이면 추측하지 않고 그 실패 자체를 결과로 냅니다.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount)와 reclaimUnsold()는 주소 매개변수가 없고 목적지는 배포 시 고정되어 소유자도 바꿀 수 없습니다. 주장이 아니라 ABI로 직접 확인됩니다.더 많은 무료 보안 도구
Rug Pull & Honeypot 스캐너
소유권, 민트, 블랙리스트/일시정지, 매수·매도세 차이, 유동성 잠금, 보유 집중 등 알려진 플래그를 평가.
토큰 승인 위험 감사
EVM Approval을 스캔해 무제한 allowance를 표시.
No es una lista genérica de «buenas prácticas». Son comprobaciones nacidas de diseñar y lanzar un contrato de preventa con honestidad — cada una mapea a una forma real de perjudicar al comprador. Abajo usamos el contrato de preventa AUDX de Kinetix como ejemplo verificable de «aprobado».
Por qué el ABI importa más que el marketing
Todo contrato estilo TRC-20/ERC-20 expone un ABI — lista JSON de funciones. A diferencia del sitio, el ABI no miente: si hay una función para mover fondos, está ahí. El paso más rápido de due diligence es leer el ABI antes que el texto del equipo.
Las 8 comprobaciones
Compare withdrawUsdt(uint256 amount) vs withdrawUsdt(uint256 amount, address to). Si el destino es un parámetro, el owner puede enviar fondos a cualquier billetera. Si está fijo al desplegar, queda limitado a una dirección.
La función que recupera tokens no vendidos con destino parametrizado es tan grave como un withdraw libre.
Busque setPrice(), updateRate() o configure(). Si el precio «fijo» se puede cambiar tras comprometerse, es engañoso.
setCap(), increaseSupply(), extendPool() — un pool que crece diluye a quien ya compró.
transferOwnership() / renounceOwnership(). Ownership no es malo per se; lo crítico es si puede pasar a una dirección desconocida en cualquier momento.
pause(), blacklist(), setTradingEnabled() — mecanismos para congelar compradores o todo el contrato a mitad de venta.
Revise close() y si hay algún camino de reembolso si se cierra temprano. La mayoría no tiene; por eso leer docs antes de enviar dinero importa más.
Sin ABI real (fuente no publicada/verificada), la due diligence falla antes de empezar. Trate un contrato de venta no verificado como riesgo máximo.
El script
Obtiene el ABI verificado de la API pública de TronScan y marca estos patrones automáticamente. Si no está verificado, ese fallo ES el hallazgo — no inventa resultados.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) y reclaimUnsold() no toman dirección; los destinos quedan fijos al desplegar y nadie, ni el owner, puede cambiarlos. No es un claim: se verifica en el ABI, igual que este script con cualquier otro contrato.Más herramientas de seguridad gratis
Escáner Rug Pull & Honeypot
Puntúa ownership, mint, blacklist/pause, delta de tasas, lock de liquidez, concentración de holders.
Auditor de aprobaciones
Escanea Approvals EVM y marca allowances ilimitados.
Это не общий список «лучших практик», а проверки из опыта честного проектирования и запуска пресейл-контракта — каждая соответствует реальному способу навредить покупателю. Ниже контракт пресейла AUDX от Kinetix — проверяемый пример «прошёл».
Почему ABI важнее маркетинга
Контракты TRC-20/ERC-20-стиля открывают ABI — JSON-список функций. В отличие от сайта, ABI не врёт: если есть функция перевода средств, она там. Самый быстрый due diligence — читать ABI до текстов команды.
8 проверок
Сравните withdrawUsdt(uint256 amount) и withdrawUsdt(uint256 amount, address to). Если адрес — параметр, владелец может отправить средства куда угодно. Если фиксирован при деплое — ограничен одним адресом.
Параметризованный адрес в reclaim — такой же red flag, как свободный withdraw.
Ищите setPrice(), updateRate(), configure(). «Фиксированная цена», которую можно менять после покупки — вводит в заблуждение.
setCap(), increaseSupply(), extendPool() — растущий после запуска пул размывает уже купивших.
transferOwnership() / renounceOwnership(). Ownership нужен, но важно, можно ли передать его неизвестному адресу в любой момент.
pause(), blacklist(), setTradingEnabled() — механизмы заморозки покупателей или всего контракта.
Проверьте close() и наличие refund при раннем закрытии. Обычно refund нет — тем важнее читать docs до отправки денег.
Без реального ABI (исходник не опубликован/не верифицирован) due diligence провален до старта. Неверифицированный sale-контракт — максимальный риск.
Скрипт
Тянет верифицированный ABI из публичного API TronScan и автоматически помечает эти паттерны. Если контракт не верифицирован, этот сбой и есть находка — без догадок.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) и reclaimUnsold() не принимают адрес; назначения фиксированы при деплое и не меняются даже владельцем. Это не заявление — это проверяется по ABI, как и любой другой контракт этим скриптом.Другие бесплатные инструменты безопасности
Сканер Rug Pull & Honeypot
Оценивает ownership, mint, blacklist/pause, дельту налогов, lock ликвидности, концентрацию холдеров.
Аудит Approval
Сканирует Approval на EVM и помечает безлимитные allowances.
Đây không phải danh sách "best practices" chung chung, mà các kiểm tra từ việc thiết kế và ra mắt hợp đồng presale trung thực — mỗi mục tương ứng một cách thực tế có thể hại người mua. Bên dưới dùng hợp đồng presale AUDX của Kinetix làm ví dụ "pass" có thể kiểm chứng.
Vì sao ABI quan trọng hơn marketing
Mọi hợp đồng kiểu TRC-20/ERC-20 đều lộ ABI — danh sách JSON các hàm. Khác website, ABI không nói dối: nếu có hàm chuyển tiền, nó sẽ có. Bước due diligence nhanh nhất là đọc ABI trước nội dung của team.
8 kiểm tra
So withdrawUsdt(uint256 amount) với withdrawUsdt(uint256 amount, address to). Nếu đích là tham số, owner có thể gửi tiền đi bất kỳ đâu. Nếu cố định lúc deploy thì bị giới hạn một địa chỉ.
Hàm thu hồi token chưa bán mà đích là tham số cũng là red flag như withdraw tự do.
Tìm setPrice(), updateRate(), configure(). Giá "cố định" mà vẫn đổi được sau khi bạn cam kết mua là gây hiểu nhầm.
setCap(), increaseSupply(), extendPool() — pool phình sau launch làm loãng người đã mua.
transferOwnership() / renounceOwnership(). Ownership không xấu; quan trọng là có chuyển sang địa chỉ lạ bất cứ lúc nào không.
pause(), blacklist(), setTradingEnabled() — cơ chế đóng băng buyer hoặc cả hợp đồng giữa đợt bán.
Kiểm close() và refund nếu đóng sớm. Đa số không có; vì vậy đọc tài liệu trước khi gửi tiền càng quan trọng.
Không lấy được ABI thật (chưa publish/verify) thì due diligence thất bại từ đầu. Coi hợp đồng sale chưa verify là rủi ro tối đa.
Script
Lấy ABI đã verify từ API công khai TronScan và gắn cờ các mẫu trên. Nếu chưa verify, thất bại đó CHÍNH LÀ kết quả — không đoán mò.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) và reclaimUnsold() không nhận địa chỉ; đích cố định lúc deploy và không ai, kể cả owner, đổi được. Không phải lời tuyên bố — kiểm chứng trực tiếp trên ABI như script này với mọi hợp đồng khác.Thêm công cụ bảo mật miễn phí
Máy quét Rug Pull & Honeypot
Chấm ownership, mint, blacklist/pause, chênh thuế mua/bán, khóa thanh khoản, tập trung holder.
Kiểm toán Approval
Quét Approval trên EVM và gắn cờ allowance không giới hạn.
Isto não é uma lista genérica de «boas práticas». São checks nascidos de desenhar e lançar um contrato de pré-venda com honestidade — cada um mapeia uma forma real de prejudicar o comprador. Abaixo usamos o contrato de pré-venda AUDX da Kinetix como exemplo verificável de «passou».
Porque o ABI importa mais do que o marketing
Todo contrato estilo TRC-20/ERC-20 expõe um ABI — lista JSON de funções. Ao contrário do site, o ABI não mente: se existe função para mover fundos, está lá. O passo mais rápido de due diligence é ler o ABI antes do texto da equipa.
As 8 verificações
Compare withdrawUsdt(uint256 amount) vs withdrawUsdt(uint256 amount, address to). Se o destino for um parâmetro, o owner pode enviar fundos para qualquer carteira. Se for fixo no deploy, fica limitado a um endereço.
Função que recupera tokens não vendidos com destino parametrizado é red flag igual a withdraw livre.
Procure setPrice(), updateRate() ou configure(). Preço «fixo» alterável após compromisso é enganador.
setCap(), increaseSupply(), extendPool() — um pool que cresce dilui quem já comprou.
transferOwnership() / renounceOwnership(). Ownership não é mau per se; o crítico é poder passar a um endereço desconhecido a qualquer momento.
pause(), blacklist(), setTradingEnabled() — mecanismos para congelar compradores ou o contrato no meio da venda.
Verifique close() e se existe algum caminho de reembolso se fechar cedo. A maioria não tem; por isso ler docs antes de enviar dinheiro importa mais.
Sem ABI real (fonte não publicada/verificada), a due diligence falha antes de começar. Trate um contrato de venda não verificado como risco máximo.
O script
Obtém o ABI verificado da API pública do TronScan e marca estes padrões automaticamente. Se não estiver verificado, essa falha É o achado — sem inventar resultados.
#!/usr/bin/env python3
"""
presale_audit.py — TRC-20 presale contract red-flag checker.
Usage:
python3 presale_audit.py <contract_address> [--abi path/to/abi.json]
If --abi is omitted, the script pulls the contract's ABI live from the chain
via TronGrid. On TRON, a contract's ABI is written on-chain at deployment
time — unlike Ethereum, no separate "source verification" step is needed to
expose it. If a contract has no ABI on-chain at all, that failure IS the
finding — the script says so instead of guessing.
No API key required for the endpoints this script uses.
"""
import sys
import json
import argparse
import urllib.request
TRONSCAN_CONTRACT_API = "https://apilist.tronscanapi.com/api/contract"
TRONGRID_GETCONTRACT_API = "https://api.trongrid.io/wallet/getcontract"
RISKY_PATTERNS = [
("withdraw", "to", "Withdraw function takes a destination address as a parameter — "
"funds are NOT limited to one fixed wallet."),
("reclaim", "to", "Unsold-token reclaim takes a destination address as a parameter — "
"same risk as an unlocked withdraw."),
("setprice", None, "Price can be changed after deployment — the 'fixed price' claim may not hold."),
("updaterate", None, "Rate/price appears changeable after deployment."),
("setcap", None, "Sale cap/pool size appears changeable after deployment."),
("increasesupply", None, "Contract can increase its own token supply after deployment."),
("pause", None, "Owner can pause the contract — funds/withdrawals could be frozen selectively."),
("blacklist", None, "Owner can blacklist addresses — could block specific buyers' refunds."),
("mint", None, "Contract (or its underlying token) has a mint function — supply is not fixed."),
]
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def _base58_decode(s):
num = 0
for char in s:
num = num * 58 + _B58_ALPHABET.index(char)
n_bytes = (num.bit_length() + 7) // 8
combined = num.to_bytes(n_bytes, "big") if n_bytes else b""
n_pad = len(s) - len(s.lstrip("1"))
return b"\x00" * n_pad + combined
def tron_address_to_hex(address):
"""T.. base58check address -> 21-byte hex (0x41 prefix + 20-byte address)."""
raw = _base58_decode(address)
return raw[:21].hex()
def fetch_contract_info(address):
"""Cosmetic metadata only (creation date, balance) — best-effort, not load-bearing."""
url = f"{TRONSCAN_CONTRACT_API}?contract={address}"
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
except Exception:
return None
entries = data.get("data", [])
return entries[0] if entries else None
def fetch_contract_abi(address):
"""The real ABI, straight from the chain via TronGrid's wallet/getcontract."""
hex_addr = tron_address_to_hex(address)
body = json.dumps({"value": hex_addr}).encode()
req = urllib.request.Request(
TRONGRID_GETCONTRACT_API, data=body,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
data = json.load(r)
except Exception:
return None
return data.get("abi", {}).get("entrys", [])
def load_abi(path):
with open(path) as f:
return json.load(f)
def scan_abi(abi):
findings = []
fn_names = [f.get("name", "") for f in abi
if f.get("type", "").lower() == "function" and f.get("name")]
fn_lower = {n.lower(): n for n in fn_names}
for keyword, needs_addr_param, message in RISKY_PATTERNS:
for lower_name, real_name in fn_lower.items():
if keyword in lower_name:
if needs_addr_param:
entry = next(f for f in abi if f.get("name") == real_name)
params = [p.get("type", "") for p in entry.get("inputs", [])]
if "address" not in params:
continue
findings.append((real_name, message))
if "transferownership" in fn_lower:
findings.append((fn_lower["transferownership"],
"Ownership is transferable — today's team may not control it tomorrow."))
return findings, fn_names
def main():
ap = argparse.ArgumentParser()
ap.add_argument("address")
ap.add_argument("--abi", help="path to a local ABI JSON file")
args = ap.parse_args()
print(f"=== Presale audit: {args.address} ===\n")
info = fetch_contract_info(args.address)
if info:
print(f"Contract created: {info.get('date_created', 'unknown')}")
print(f"Balance: {info.get('balance', 0)}")
print()
abi = None
if args.abi:
abi = load_abi(args.abi)
print(f"Loaded ABI from {args.abi} ({len(abi)} entries)\n")
else:
abi = fetch_contract_abi(args.address)
if abi:
print(f"Loaded ABI live from chain via TronGrid ({len(abi)} entries)\n")
if not abi:
print("NO ABI FOUND ON-CHAIN. On TRON, a contract's ABI is normally written")
print("on-chain at deploy time — no separate 'verification' step is needed to")
print("expose it. This means you cannot audit what you cannot read. Do not send")
print("funds to a sale contract you can't inspect, and separately check whether")
print("its source is verified on a block explorer like TronScan.")
sys.exit(1)
findings, fn_names = scan_abi(abi)
print(f"Functions found ({len(fn_names)}): {', '.join(sorted(fn_names))}\n")
if not findings:
print("No red flags matched from this checklist. This does NOT mean the contract")
print("is safe — it means it passed these structural checks. Still read the")
print("actual source code before sending money.")
else:
print(f"{len(findings)} finding(s):\n")
for fn, msg in findings:
print(f" ⚠ {fn}(): {msg}")
print("\nThis script checks structure, not intent. A contract can pass every check")
print("here and still be malicious in ways only a full manual code read will catch.")
if __name__ == "__main__":
main()
withdrawUsdt(uint256 amount) e reclaimUnsold() não recebem endereço; os destinos ficam fixos no deploy e ninguém, nem o owner, os pode mudar. Não é um claim: verifica-se no ABI, como este script com qualquer outro contrato.Mais ferramentas de segurança grátis
Scanner Rug Pull & Honeypot
Pontua ownership, mint, blacklist/pause, delta de taxas, lock de liquidez, concentração de holders.
Auditor de aprovações
Analisa Approvals EVM e assinala allowances ilimitados.