cryptoshield 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cryptoshield/__init__.py +4 -0
- cryptoshield/approvals.py +185 -0
- cryptoshield/cli.py +166 -0
- cryptoshield/db.py +50 -0
- cryptoshield/honeypot.py +219 -0
- cryptoshield/phishing.py +454 -0
- cryptoshield/rugpull.py +141 -0
- cryptoshield/solana.py +264 -0
- cryptoshield/utils.py +194 -0
- cryptoshield-0.2.0.dist-info/METADATA +157 -0
- cryptoshield-0.2.0.dist-info/RECORD +15 -0
- cryptoshield-0.2.0.dist-info/WHEEL +5 -0
- cryptoshield-0.2.0.dist-info/entry_points.txt +2 -0
- cryptoshield-0.2.0.dist-info/licenses/LICENSE +21 -0
- cryptoshield-0.2.0.dist-info/top_level.txt +1 -0
cryptoshield/solana.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Solana token security checker.
|
|
2
|
+
|
|
3
|
+
Uses Solana RPC + Jupiter API for token data.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from .db import cache_get, cache_set
|
|
8
|
+
from .utils import print_header, print_ok, print_warn, print_fail, print_info
|
|
9
|
+
|
|
10
|
+
SOLANA_RPC = "https://api.mainnet-beta.solana.com"
|
|
11
|
+
JUPITER_TOKEN_API = "https://tokens.jup.ag/token/{mint}"
|
|
12
|
+
JUPITER_LIST = "https://tokens.jup.ag/strict"
|
|
13
|
+
BIRDEYE_TOKEN = "https://public-api.birdeye.so/defi/token_overview?address={mint}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_solana_token(mint: str) -> dict:
|
|
17
|
+
"""Check a Solana SPL token for security issues."""
|
|
18
|
+
cache_key = f"solana_token:{mint}"
|
|
19
|
+
cached = cache_get(cache_key)
|
|
20
|
+
if cached:
|
|
21
|
+
return cached
|
|
22
|
+
|
|
23
|
+
report = {
|
|
24
|
+
"mint": mint,
|
|
25
|
+
"chain": "solana",
|
|
26
|
+
"token_name": "Unknown",
|
|
27
|
+
"token_symbol": "?",
|
|
28
|
+
"risks": [],
|
|
29
|
+
"risk_score": 0,
|
|
30
|
+
"holder_count": 0,
|
|
31
|
+
"total_supply": 0,
|
|
32
|
+
"decimals": 0,
|
|
33
|
+
"is_on_jupiter": False,
|
|
34
|
+
"freeze_authority": None,
|
|
35
|
+
"mint_authority": None,
|
|
36
|
+
"is_mutable": True,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# 1. Get token metadata from Jupiter
|
|
40
|
+
try:
|
|
41
|
+
r = requests.get(JUPITER_TOKEN_API.format(mint=mint), timeout=10)
|
|
42
|
+
if r.status_code == 200:
|
|
43
|
+
data = r.json()
|
|
44
|
+
report["token_name"] = data.get("name", "Unknown")
|
|
45
|
+
report["token_symbol"] = data.get("symbol", "?")
|
|
46
|
+
report["decimals"] = data.get("decimals", 0)
|
|
47
|
+
report["is_on_jupiter"] = True
|
|
48
|
+
report["daily_volume"] = data.get("daily_volume", 0)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# 2. Check if on Jupiter strict list (vetted tokens)
|
|
53
|
+
try:
|
|
54
|
+
r = requests.get(JUPITER_LIST, timeout=10)
|
|
55
|
+
if r.status_code == 200:
|
|
56
|
+
strict_tokens = {t["address"] for t in r.json()}
|
|
57
|
+
report["is_on_jupiter_strict"] = mint in strict_tokens
|
|
58
|
+
except Exception:
|
|
59
|
+
report["is_on_jupiter_strict"] = False
|
|
60
|
+
|
|
61
|
+
# 3. Get on-chain token info via RPC
|
|
62
|
+
try:
|
|
63
|
+
payload = {
|
|
64
|
+
"jsonrpc": "2.0",
|
|
65
|
+
"id": 1,
|
|
66
|
+
"method": "getAccountInfo",
|
|
67
|
+
"params": [mint, {"encoding": "jsonParsed"}],
|
|
68
|
+
}
|
|
69
|
+
r = requests.post(SOLANA_RPC, json=payload, timeout=10)
|
|
70
|
+
data = r.json()
|
|
71
|
+
account = data.get("result", {}).get("value", {})
|
|
72
|
+
|
|
73
|
+
if account:
|
|
74
|
+
parsed = account.get("data", {}).get("parsed", {})
|
|
75
|
+
info = parsed.get("info", {})
|
|
76
|
+
|
|
77
|
+
report["mint_authority"] = info.get("mintAuthority")
|
|
78
|
+
report["freeze_authority"] = info.get("freezeAuthority")
|
|
79
|
+
report["is_mutable"] = info.get("isInitialized", True)
|
|
80
|
+
report["total_supply"] = int(info.get("supply", "0"))
|
|
81
|
+
report["decimals"] = info.get("decimals", report["decimals"])
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
# 4. Get holder count from Birdeye (optional, may fail without API key)
|
|
86
|
+
try:
|
|
87
|
+
headers = {"X-API-KEY": "public"}
|
|
88
|
+
r = requests.get(
|
|
89
|
+
BIRDEYE_TOKEN.format(mint=mint),
|
|
90
|
+
headers=headers,
|
|
91
|
+
timeout=10,
|
|
92
|
+
)
|
|
93
|
+
if r.status_code == 200:
|
|
94
|
+
birddata = r.json().get("data", {})
|
|
95
|
+
report["holder_count"] = birddata.get("holder", 0)
|
|
96
|
+
report["daily_volume"] = birddata.get("v24hUSD", report.get("daily_volume", 0))
|
|
97
|
+
report["market_cap"] = birddata.get("mc", 0)
|
|
98
|
+
report["price"] = birddata.get("price", 0)
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
# Risk analysis
|
|
103
|
+
risks = report["risks"]
|
|
104
|
+
score = 0
|
|
105
|
+
|
|
106
|
+
# Freeze authority
|
|
107
|
+
if report["freeze_authority"]:
|
|
108
|
+
risks.append("FREEZE AUTHORITY â issuer can freeze your tokens")
|
|
109
|
+
score += 20
|
|
110
|
+
|
|
111
|
+
# Mint authority
|
|
112
|
+
if report["mint_authority"]:
|
|
113
|
+
risks.append("MINT AUTHORITY â unlimited supply, issuer can mint more")
|
|
114
|
+
score += 15
|
|
115
|
+
|
|
116
|
+
# Not on Jupiter
|
|
117
|
+
if not report["is_on_jupiter"]:
|
|
118
|
+
risks.append("NOT ON JUPITER â unvetted token, high risk")
|
|
119
|
+
score += 15
|
|
120
|
+
|
|
121
|
+
# Not on Jupiter strict list
|
|
122
|
+
if not report.get("is_on_jupiter_strict"):
|
|
123
|
+
risks.append("NOT ON JUPITER STRICT LIST â not officially vetted")
|
|
124
|
+
score += 5
|
|
125
|
+
|
|
126
|
+
# Low holder count
|
|
127
|
+
if report["holder_count"] > 0 and report["holder_count"] < 100:
|
|
128
|
+
risks.append(f"LOW HOLDER COUNT â only {report['holder_count']} holders")
|
|
129
|
+
score += 15
|
|
130
|
+
elif report["holder_count"] > 0 and report["holder_count"] < 1000:
|
|
131
|
+
risks.append(f"SMALL HOLDER COUNT â {report['holder_count']} holders")
|
|
132
|
+
score += 5
|
|
133
|
+
|
|
134
|
+
# Low volume
|
|
135
|
+
vol = report.get("daily_volume", 0)
|
|
136
|
+
if vol > 0 and vol < 1000:
|
|
137
|
+
risks.append(f"LOW VOLUME â ${vol:,.0f} in 24h")
|
|
138
|
+
score += 10
|
|
139
|
+
|
|
140
|
+
report["risk_score"] = min(score, 100)
|
|
141
|
+
if score <= 20:
|
|
142
|
+
report["risk_level"] = "LOW"
|
|
143
|
+
elif score <= 50:
|
|
144
|
+
report["risk_level"] = "MEDIUM"
|
|
145
|
+
else:
|
|
146
|
+
report["risk_level"] = "HIGH"
|
|
147
|
+
|
|
148
|
+
cache_set(cache_key, report, ttl=1800)
|
|
149
|
+
return report
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def check_solana_wallet(wallet: str) -> dict:
|
|
153
|
+
"""Check SOL balance and token holdings for a wallet."""
|
|
154
|
+
report = {
|
|
155
|
+
"wallet": wallet,
|
|
156
|
+
"sol_balance": 0,
|
|
157
|
+
"tokens": [],
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Get SOL balance
|
|
161
|
+
try:
|
|
162
|
+
payload = {
|
|
163
|
+
"jsonrpc": "2.0",
|
|
164
|
+
"id": 1,
|
|
165
|
+
"method": "getBalance",
|
|
166
|
+
"params": [wallet],
|
|
167
|
+
}
|
|
168
|
+
r = requests.post(SOLANA_RPC, json=payload, timeout=10)
|
|
169
|
+
data = r.json()
|
|
170
|
+
report["sol_balance"] = data.get("result", {}).get("value", 0) / 1e9
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# Get token accounts
|
|
175
|
+
try:
|
|
176
|
+
payload = {
|
|
177
|
+
"jsonrpc": "2.0",
|
|
178
|
+
"id": 1,
|
|
179
|
+
"method": "getTokenAccountsByOwner",
|
|
180
|
+
"params": [
|
|
181
|
+
wallet,
|
|
182
|
+
{"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"},
|
|
183
|
+
{"encoding": "jsonParsed"},
|
|
184
|
+
],
|
|
185
|
+
}
|
|
186
|
+
r = requests.post(SOLANA_RPC, json=payload, timeout=10)
|
|
187
|
+
data = r.json()
|
|
188
|
+
accounts = data.get("result", {}).get("value", [])
|
|
189
|
+
|
|
190
|
+
for acc in accounts:
|
|
191
|
+
info = acc["account"]["data"]["parsed"]["info"]
|
|
192
|
+
token_amount = info.get("tokenAmount", {})
|
|
193
|
+
report["tokens"].append({
|
|
194
|
+
"mint": info.get("mint", ""),
|
|
195
|
+
"balance": token_amount.get("uiAmount", 0),
|
|
196
|
+
"decimals": token_amount.get("decimals", 0),
|
|
197
|
+
})
|
|
198
|
+
except Exception:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
return report
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def print_solana_token_report(report: dict):
|
|
205
|
+
"""Pretty-print Solana token check."""
|
|
206
|
+
name = report["token_name"]
|
|
207
|
+
symbol = report["token_symbol"]
|
|
208
|
+
print_header(f"SOLANA TOKEN CHECK â {name} ({symbol})")
|
|
209
|
+
|
|
210
|
+
# Jupiter status
|
|
211
|
+
if report["is_on_jupiter"]:
|
|
212
|
+
print_ok("Listed on Jupiter")
|
|
213
|
+
if report.get("is_on_jupiter_strict"):
|
|
214
|
+
print_ok("On Jupiter Strict List (vetted)")
|
|
215
|
+
else:
|
|
216
|
+
print_warn("NOT on Jupiter Strict List")
|
|
217
|
+
else:
|
|
218
|
+
print_fail("Not listed on Jupiter â unvetted token")
|
|
219
|
+
|
|
220
|
+
# Freeze authority
|
|
221
|
+
if report["freeze_authority"]:
|
|
222
|
+
print_fail(f"Freeze Authority: {report['freeze_authority'][:10]}...")
|
|
223
|
+
else:
|
|
224
|
+
print_ok("Freeze Authority: None")
|
|
225
|
+
|
|
226
|
+
# Mint authority
|
|
227
|
+
if report["mint_authority"]:
|
|
228
|
+
print_fail(f"Mint Authority: {report['mint_authority'][:10]}...")
|
|
229
|
+
else:
|
|
230
|
+
print_ok("Mint Authority: None (fixed supply)")
|
|
231
|
+
|
|
232
|
+
# Holders
|
|
233
|
+
if report["holder_count"]:
|
|
234
|
+
if report["holder_count"] < 100:
|
|
235
|
+
print_fail(f"Holders: {report['holder_count']:,}")
|
|
236
|
+
elif report["holder_count"] < 1000:
|
|
237
|
+
print_warn(f"Holders: {report['holder_count']:,}")
|
|
238
|
+
else:
|
|
239
|
+
print_ok(f"Holders: {report['holder_count']:,}")
|
|
240
|
+
|
|
241
|
+
# Volume
|
|
242
|
+
vol = report.get("daily_volume", 0)
|
|
243
|
+
if vol:
|
|
244
|
+
print_info(f"24h Volume: ${vol:,.0f}")
|
|
245
|
+
|
|
246
|
+
# Market cap
|
|
247
|
+
mc = report.get("market_cap", 0)
|
|
248
|
+
if mc:
|
|
249
|
+
print_info(f"Market Cap: ${mc:,.0f}")
|
|
250
|
+
|
|
251
|
+
# Risk score
|
|
252
|
+
score = report["risk_score"]
|
|
253
|
+
level = report.get("risk_level", "UNKNOWN")
|
|
254
|
+
if level == "LOW":
|
|
255
|
+
print_ok(f"Risk Score: {score}/100 â {level} RISK")
|
|
256
|
+
elif level == "MEDIUM":
|
|
257
|
+
print_warn(f"Risk Score: {score}/100 â {level} RISK")
|
|
258
|
+
else:
|
|
259
|
+
print_fail(f"Risk Score: {score}/100 â {level} RISK")
|
|
260
|
+
|
|
261
|
+
if report["risks"]:
|
|
262
|
+
print()
|
|
263
|
+
for risk in report["risks"]:
|
|
264
|
+
print(f" ⥠{risk}")
|
cryptoshield/utils.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Shared utilities."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from web3 import Web3
|
|
8
|
+
|
|
9
|
+
# Default RPC endpoints (public, no key needed)
|
|
10
|
+
RPC_ENDPOINTS = {
|
|
11
|
+
"eth": "https://eth.llamarpc.com",
|
|
12
|
+
"bsc": "https://bsc-dataseed1.binance.org",
|
|
13
|
+
"polygon": "https://polygon-rpc.com",
|
|
14
|
+
"arbitrum": "https://arb1.arbitrum.io/rpc",
|
|
15
|
+
"optimism": "https://mainnet.optimism.io",
|
|
16
|
+
"base": "https://mainnet.base.org",
|
|
17
|
+
"avalanche": "https://api.avax.network/ext/bc/C/rpc",
|
|
18
|
+
"fantom": "https://rpc.ftm.tools",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
CHAIN_IDS = {
|
|
22
|
+
"eth": 1,
|
|
23
|
+
"bsc": 56,
|
|
24
|
+
"polygon": 137,
|
|
25
|
+
"arbitrum": 42161,
|
|
26
|
+
"optimism": 10,
|
|
27
|
+
"base": 8453,
|
|
28
|
+
"avalanche": 43114,
|
|
29
|
+
"fantom": 250,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Minimal ERC-20 ABI
|
|
33
|
+
ERC20_ABI = [
|
|
34
|
+
{
|
|
35
|
+
"constant": True,
|
|
36
|
+
"inputs": [{"name": "_owner", "type": "address"}],
|
|
37
|
+
"name": "balanceOf",
|
|
38
|
+
"outputs": [{"name": "balance", "type": "uint256"}],
|
|
39
|
+
"type": "function",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"constant": True,
|
|
43
|
+
"inputs": [
|
|
44
|
+
{"name": "_owner", "type": "address"},
|
|
45
|
+
{"name": "_spender", "type": "address"},
|
|
46
|
+
],
|
|
47
|
+
"name": "allowance",
|
|
48
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
49
|
+
"type": "function",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"constant": True,
|
|
53
|
+
"inputs": [],
|
|
54
|
+
"name": "name",
|
|
55
|
+
"outputs": [{"name": "", "type": "string"}],
|
|
56
|
+
"type": "function",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"constant": True,
|
|
60
|
+
"inputs": [],
|
|
61
|
+
"name": "symbol",
|
|
62
|
+
"outputs": [{"name": "", "type": "string"}],
|
|
63
|
+
"type": "function",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"constant": True,
|
|
67
|
+
"inputs": [],
|
|
68
|
+
"name": "decimals",
|
|
69
|
+
"outputs": [{"name": "", "type": "uint8"}],
|
|
70
|
+
"type": "function",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"constant": True,
|
|
74
|
+
"inputs": [],
|
|
75
|
+
"name": "totalSupply",
|
|
76
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
77
|
+
"type": "function",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"constant": False,
|
|
81
|
+
"inputs": [
|
|
82
|
+
{"name": "_spender", "type": "address"},
|
|
83
|
+
{"name": "_value", "type": "uint256"},
|
|
84
|
+
],
|
|
85
|
+
"name": "approve",
|
|
86
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
87
|
+
"type": "function",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"constant": True,
|
|
91
|
+
"inputs": [],
|
|
92
|
+
"name": "owner",
|
|
93
|
+
"outputs": [{"name": "", "type": "address"}],
|
|
94
|
+
"type": "function",
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
# Transfer event ABI
|
|
99
|
+
TRANSFER_EVENT = {
|
|
100
|
+
"anonymous": False,
|
|
101
|
+
"inputs": [
|
|
102
|
+
{"indexed": True, "name": "from", "type": "address"},
|
|
103
|
+
{"indexed": True, "name": "to", "type": "address"},
|
|
104
|
+
{"indexed": False, "name": "value", "type": "uint256"},
|
|
105
|
+
],
|
|
106
|
+
"name": "Transfer",
|
|
107
|
+
"type": "event",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Approval event ABI
|
|
111
|
+
APPROVAL_EVENT = {
|
|
112
|
+
"anonymous": False,
|
|
113
|
+
"inputs": [
|
|
114
|
+
{"indexed": True, "name": "owner", "type": "address"},
|
|
115
|
+
{"indexed": True, "name": "spender", "type": "address"},
|
|
116
|
+
{"indexed": False, "name": "value", "type": "uint256"},
|
|
117
|
+
],
|
|
118
|
+
"name": "Approval",
|
|
119
|
+
"type": "event",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Well-known addresses to label
|
|
123
|
+
KNOWN_ADDRESSES = {
|
|
124
|
+
"0x7a250d5630b4cf539739df2c5dacb4c659f2488d": "Uniswap V2 Router",
|
|
125
|
+
"0xe592427a0aece92de3edee1f18e0157c05861564": "Uniswap V3 Router",
|
|
126
|
+
"0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45": "Uniswap SwapRouter02",
|
|
127
|
+
"0x1111111254fb6c44bac0bed2854e76f90643097d": "1inch Router V4",
|
|
128
|
+
"0xdef1c0ded9bec7f1a1670819833240f027b25eff": "0x Exchange Proxy",
|
|
129
|
+
"0x1111111254eeb25477b68fb85ed929f73a960582": "1inch Router V5",
|
|
130
|
+
"0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f": "SushiSwap Router",
|
|
131
|
+
"0x000000000022d473030f116ddee9f6b43ac78ba3": "Seaport (OpenSea)",
|
|
132
|
+
"0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad": "Universal Router",
|
|
133
|
+
"0x00000000000000adc04c56bf30ac9d3c0aaf14dc": "Seaport 1.5",
|
|
134
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "USDC",
|
|
135
|
+
"0xdac17f958d2ee523a2206206994597c13d831ec7": "USDT",
|
|
136
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": "DAI",
|
|
137
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "WETH",
|
|
138
|
+
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "WBTC",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_web3(chain: str = "eth") -> Web3:
|
|
143
|
+
rpc = RPC_ENDPOINTS.get(chain)
|
|
144
|
+
if not rpc:
|
|
145
|
+
print(f"Unsupported chain: {chain}")
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
return Web3(Web3.HTTPProvider(rpc))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def is_valid_address(addr: str) -> bool:
|
|
151
|
+
return Web3.is_address(addr)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def checksum(addr: str) -> str:
|
|
155
|
+
return Web3.to_checksum_address(addr)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def label_address(addr: str) -> str:
|
|
159
|
+
addr_lower = addr.lower()
|
|
160
|
+
label = KNOWN_ADDRESSES.get(addr_lower, "")
|
|
161
|
+
if label:
|
|
162
|
+
return f"{addr[:6]}...{addr[-4:]} ({label})"
|
|
163
|
+
return f"{addr[:6]}...{addr[-4:]}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def fetch_json(url: str, params: Optional[dict] = None, timeout: int = 15) -> dict:
|
|
167
|
+
try:
|
|
168
|
+
r = requests.get(url, params=params, timeout=timeout)
|
|
169
|
+
r.raise_for_status()
|
|
170
|
+
return r.json()
|
|
171
|
+
except Exception as e:
|
|
172
|
+
return {"error": str(e)}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def print_header(title: str):
|
|
176
|
+
print(f"\n{'â' * 40}")
|
|
177
|
+
print(f" {title}")
|
|
178
|
+
print(f"{'â' * 40}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def print_ok(msg: str):
|
|
182
|
+
print(f" â
{msg}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def print_warn(msg: str):
|
|
186
|
+
print(f" â ī¸ {msg}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def print_fail(msg: str):
|
|
190
|
+
print(f" â {msg}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def print_info(msg: str):
|
|
194
|
+
print(f" âšī¸ {msg}")
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cryptoshield
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: All-in-one crypto security toolkit â honeypot, approvals, rugpull, phishing
|
|
5
|
+
Author-email: yossweh <cilokcilok15@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yossweh/cryptoshield
|
|
8
|
+
Project-URL: Repository, https://github.com/yossweh/cryptoshield
|
|
9
|
+
Project-URL: Issues, https://github.com/yossweh/cryptoshield/issues
|
|
10
|
+
Keywords: crypto,security,honeypot,rugpull,web3,defi,approval
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: typer>=0.9.0
|
|
21
|
+
Requires-Dist: web3>=6.0.0
|
|
22
|
+
Requires-Dist: requests>=2.28.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# đĄ CryptoShield
|
|
26
|
+
|
|
27
|
+
**All-in-one crypto security toolkit.** Check tokens before you buy. Scan your wallet for dangerous approvals. Detect rugpulls. Block phishing sites.
|
|
28
|
+
|
|
29
|
+
[](https://python.org)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://pypi.org/project/cryptoshield/)
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **đ¯ Honeypot Detection** â Can you sell? Hidden taxes? Mint function? Check before you buy.
|
|
36
|
+
- **đ Approval Scanner** â Find all token approvals on your wallet. Flag dangerous unlimited approvals.
|
|
37
|
+
- **đ´ Rugpull Scorer** â Analyze contracts for common rug patterns. Score 0-100.
|
|
38
|
+
- **đŖ Phishing Checker** â 60+ known scam domains. Typosquatting, fake airdrops, wallet drainers.
|
|
39
|
+
- **âī¸ Solana Support** â Check SPL tokens, freeze/mint authority, Jupiter listing status.
|
|
40
|
+
- **đĻ Batch Mode** â Check 100+ tokens/wallets from a file.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install cryptoshield
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or from source:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/yossweh/cryptoshield
|
|
52
|
+
cd cryptoshield
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Full security report (EVM)
|
|
59
|
+
```bash
|
|
60
|
+
cryptoshield check 0xdAC17F958D2ee523a2206206994597C13D831ec7
|
|
61
|
+
cryptoshield check 0xToken --chain bsc
|
|
62
|
+
cryptoshield check 0xToken --quick # honeypot only
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Solana token check
|
|
66
|
+
```bash
|
|
67
|
+
cryptoshield check EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v --chain solana
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Scan wallet approvals
|
|
71
|
+
```bash
|
|
72
|
+
cryptoshield approvals 0xYourWallet
|
|
73
|
+
cryptoshield approvals 0xYourWallet --chain polygon
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Check phishing URL
|
|
77
|
+
```bash
|
|
78
|
+
cryptoshield check-url uniswap-airdrop.com
|
|
79
|
+
cryptoshield check-url https://app.uniswap.org
|
|
80
|
+
cryptoshield check-url metamask-sync.xyz
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Solana wallet scan
|
|
84
|
+
```bash
|
|
85
|
+
cryptoshield solana YourSolanaWalletAddress
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Batch check
|
|
89
|
+
```bash
|
|
90
|
+
cryptoshield batch tokens.txt --mode honeypot
|
|
91
|
+
cryptoshield batch tokens.txt --mode honeypot --chain solana
|
|
92
|
+
cryptoshield batch wallets.txt --mode approvals --chain bsc
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Example Output
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
đĄ CRYPTO SHIELD REPORT
|
|
99
|
+
âââââââââââââââââââââââ
|
|
100
|
+
|
|
101
|
+
đ¯ HONEYPOT CHECK â Tether USD (USDT)
|
|
102
|
+
â
Can sell: YES
|
|
103
|
+
â
Tax: 0% buy / 0% sell
|
|
104
|
+
â Owner can mint: YES â infinite supply risk
|
|
105
|
+
â Owner can change balances: YES
|
|
106
|
+
â
Contract: Verified
|
|
107
|
+
âšī¸ Holders: 14,585,422
|
|
108
|
+
â ī¸ Risk Score: 30/100 â MEDIUM RISK
|
|
109
|
+
|
|
110
|
+
đŖ PHISHING CHECK â uniswap-airdrop.com
|
|
111
|
+
đ¨ KNOWN SCAM DOMAIN â DO NOT VISIT
|
|
112
|
+
â KNOWN SCAM DOMAIN â uniswap-airdrop.com is in scam database
|
|
113
|
+
|
|
114
|
+
âī¸ SOLANA TOKEN CHECK â SafeToken (SAFE)
|
|
115
|
+
â
Listed on Jupiter
|
|
116
|
+
â
On Jupiter Strict List (vetted)
|
|
117
|
+
â
Freeze Authority: None
|
|
118
|
+
â
Mint Authority: None (fixed supply)
|
|
119
|
+
â
Holders: 5,432
|
|
120
|
+
â
Risk Score: 0/100 â LOW RISK
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Supported Chains
|
|
124
|
+
|
|
125
|
+
| Chain | Honeypot | Approvals | Rugpull |
|
|
126
|
+
|-------|----------|-----------|---------|
|
|
127
|
+
| Ethereum | â
| â
| â
|
|
|
128
|
+
| BSC | â
| â
| â
|
|
|
129
|
+
| Polygon | â
| â
| â
|
|
|
130
|
+
| Arbitrum | â
| â
| â
|
|
|
131
|
+
| Optimism | â
| â
| â
|
|
|
132
|
+
| Base | â
| â
| â
|
|
|
133
|
+
| Avalanche | â
| â
| â
|
|
|
134
|
+
| Fantom | â
| â
| â
|
|
|
135
|
+
| Solana | â
| â | â
|
|
|
136
|
+
|
|
137
|
+
## Data Sources
|
|
138
|
+
|
|
139
|
+
- **GoPlus Security API** â Honeypot detection, token security analysis (free, no key)
|
|
140
|
+
- **Jupiter API** â Solana token data, strict list
|
|
141
|
+
- **Birdeye API** â Solana holder count, volume
|
|
142
|
+
- **On-chain data** â Approval events, contract code, ownership (direct RPC)
|
|
143
|
+
- **Heuristics** â URL pattern matching, domain analysis, typosquatting detection
|
|
144
|
+
- **Community scam database** â 60+ known phishing domains
|
|
145
|
+
|
|
146
|
+
## Contributing
|
|
147
|
+
|
|
148
|
+
PRs welcome! Especially:
|
|
149
|
+
|
|
150
|
+
- More scam domains / phishing patterns
|
|
151
|
+
- More chain support (TON, Sui, Aptos)
|
|
152
|
+
- Better rugpull heuristics
|
|
153
|
+
- UI improvements
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
cryptoshield/__init__.py,sha256=aHkk2WXVV51D4zlLYdV6GNaao5V_Z146v4kXfmBQYgc,105
|
|
2
|
+
cryptoshield/approvals.py,sha256=La44yNJqzTVkJt1GLJqnk4Hg099-ytNkxTO3foNc2OA,5517
|
|
3
|
+
cryptoshield/cli.py,sha256=c7gFg8P7ulj-oBGTfjkfwj-dhX0F59kSigmcqPiXtew,5619
|
|
4
|
+
cryptoshield/db.py,sha256=371XFYFgEoEhe2gbzIv2DFcNfHB8bX1bG38c1uvjXuE,1169
|
|
5
|
+
cryptoshield/honeypot.py,sha256=jZMOUFU5RIIXRZ-_i7zAsAjDoat-PtACd2hCm6tze8c,7486
|
|
6
|
+
cryptoshield/phishing.py,sha256=JgGZHo9PX00GnVN02swj3Y7XeOq0SDbrv0iu0zkodaw,12287
|
|
7
|
+
cryptoshield/rugpull.py,sha256=CI_FqMld6rzsfz1YIVvm5PxiOEN2a1L2MP-wyktkx0Q,5525
|
|
8
|
+
cryptoshield/solana.py,sha256=GuKCD9mRM4wjC4VE0nCUazyK4Wh7VP8P18keZaRB0W8,8399
|
|
9
|
+
cryptoshield/utils.py,sha256=CywjELqlKJmSl6KRHbLf956Ns2zpZ45AuLqe75ljKIE,5240
|
|
10
|
+
cryptoshield-0.2.0.dist-info/licenses/LICENSE,sha256=2P1HcJoaA_1bos2PzIXkSFjMv7JgGQDnB9Yz1MaKEiY,1064
|
|
11
|
+
cryptoshield-0.2.0.dist-info/METADATA,sha256=Yy6vcK-GI_gqmTLGuLQqdOyAEe6qQixW93TdUJE1E4Y,4783
|
|
12
|
+
cryptoshield-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
cryptoshield-0.2.0.dist-info/entry_points.txt,sha256=7DOJMy4KvAZpSFkqq1Akp9GizoMwf59lj-eKethhSuI,55
|
|
14
|
+
cryptoshield-0.2.0.dist-info/top_level.txt,sha256=bJXUAxolMDDB1OfNQQPuxqXSU42OgIALB9H4BMB3EDc,13
|
|
15
|
+
cryptoshield-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yossweh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cryptoshield
|