cryptoshield 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (20) hide show
  1. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/PKG-INFO +1 -1
  2. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/__init__.py +1 -1
  3. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/rugpull.py +53 -46
  4. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/solana.py +111 -101
  5. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/utils.py +56 -37
  6. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/PKG-INFO +1 -1
  7. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/pyproject.toml +1 -1
  8. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/LICENSE +0 -0
  9. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/README.md +0 -0
  10. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/approvals.py +0 -0
  11. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/cli.py +0 -0
  12. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/db.py +0 -0
  13. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/honeypot.py +0 -0
  14. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield/phishing.py +0 -0
  15. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/SOURCES.txt +0 -0
  16. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/dependency_links.txt +0 -0
  17. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/entry_points.txt +0 -0
  18. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/requires.txt +0 -0
  19. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/cryptoshield.egg-info/top_level.txt +0 -0
  20. {cryptoshield-0.2.1 → cryptoshield-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cryptoshield
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: All-in-one crypto security toolkit — honeypot, approvals, rugpull, phishing
5
5
  Author-email: yossweh <cilokcilok15@gmail.com>
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
1
  """CryptoShield — All-in-one crypto security toolkit."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
4
4
  __author__ = "yossweh"
@@ -24,18 +24,20 @@ def analyze_rugpull(address: str, chain: str = "eth") -> dict:
24
24
  "risk_level": "UNKNOWN",
25
25
  }
26
26
 
27
- contract = w3.eth.contract(address=addr, abi=ERC20_ABI)
28
-
29
- # 1. Check if contract is verified (has code)
30
- code = w3.eth.get_code(addr)
31
- if code == b"" or code == b"0x":
32
- report["checks"].append({"name": "Contract Code", "status": "FAIL", "detail": "No contract code — not a contract"})
33
- report["score"] += 50
34
- else:
35
- report["checks"].append({"name": "Contract Code", "status": "OK", "detail": f"{len(code)} bytes deployed"})
27
+ # 1. Check if contract has code
28
+ try:
29
+ code = w3.eth.get_code(addr)
30
+ if code == b"" or code == b"0x":
31
+ report["checks"].append({"name": "Contract Code", "status": "FAIL", "detail": "No contract code not a contract"})
32
+ report["score"] += 50
33
+ else:
34
+ report["checks"].append({"name": "Contract Code", "status": "OK", "detail": f"{len(code)} bytes deployed"})
35
+ except Exception as e:
36
+ report["checks"].append({"name": "Contract Code", "status": "INFO", "detail": f"Could not check: {str(e)[:50]}"})
36
37
 
37
38
  # 2. Check if owner is renounced
38
39
  try:
40
+ contract = w3.eth.contract(address=addr, abi=ERC20_ABI)
39
41
  owner = contract.functions.owner().call()
40
42
  zero = "0x0000000000000000000000000000000000000000"
41
43
  dead = "0x000000000000000000000000000000000000dEaD"
@@ -47,8 +49,9 @@ def analyze_rugpull(address: str, chain: str = "eth") -> dict:
47
49
  except Exception:
48
50
  report["checks"].append({"name": "Ownership", "status": "INFO", "detail": "No owner function (could be good or bad)"})
49
51
 
50
- # 3. Check total supply concentration
52
+ # 3. Check total supply
51
53
  try:
54
+ contract = w3.eth.contract(address=addr, abi=ERC20_ABI)
52
55
  total_supply = contract.functions.totalSupply().call()
53
56
  if total_supply > 0:
54
57
  report["checks"].append({"name": "Total Supply", "status": "INFO", "detail": f"{total_supply:,}"})
@@ -56,46 +59,50 @@ def analyze_rugpull(address: str, chain: str = "eth") -> dict:
56
59
  report["checks"].append({"name": "Total Supply", "status": "FAIL", "detail": "Zero supply"})
57
60
  report["score"] += 20
58
61
  except Exception:
59
- report["checks"].append({"name": "Total Supply", "status": "FAIL", "detail": "Cannot read supply"})
62
+ report["checks"].append({"name": "Total Supply", "status": "INFO", "detail": "Cannot read supply"})
60
63
 
61
- # 4. Check liquidity (look for LP tokens)
62
- # This is a simplified check — real check would scan DEX pairs
64
+ # 4. Liquidity hint
63
65
  report["checks"].append({"name": "Liquidity", "status": "INFO", "detail": "Check manually on DEX Screener"})
64
66
 
65
- # 5. Get GoPlus data for additional checks
66
- goplus = check_honeypot(addr, chain)
67
- if "error" not in goplus:
68
- if goplus.get("is_honeypot"):
69
- report["score"] += 40
70
- report["checks"].append({"name": "Honeypot", "status": "FAIL", "detail": "Token is a honeypot"})
71
- else:
72
- report["checks"].append({"name": "Honeypot", "status": "OK", "detail": "Not a honeypot"})
73
-
74
- if not goplus.get("is_open_source"):
75
- report["score"] += 15
76
- report["checks"].append({"name": "Source Code", "status": "FAIL", "detail": "Not verified"})
67
+ # 5. GoPlus data (honeypot, source code, proxy, etc.)
68
+ try:
69
+ goplus = check_honeypot(addr, chain)
70
+ if "error" not in goplus:
71
+ if goplus.get("is_honeypot"):
72
+ report["score"] += 40
73
+ report["checks"].append({"name": "Honeypot", "status": "FAIL", "detail": "Token is a honeypot"})
74
+ else:
75
+ report["checks"].append({"name": "Honeypot", "status": "OK", "detail": "Not a honeypot"})
76
+
77
+ if not goplus.get("is_open_source"):
78
+ report["score"] += 15
79
+ report["checks"].append({"name": "Source Code", "status": "FAIL", "detail": "Not verified"})
80
+ else:
81
+ report["checks"].append({"name": "Source Code", "status": "OK", "detail": "Verified on explorer"})
82
+
83
+ if goplus.get("is_proxy"):
84
+ report["score"] += 5
85
+ report["checks"].append({"name": "Proxy", "status": "WARN", "detail": "Proxy contract — logic can change"})
86
+
87
+ if goplus.get("selfdestruct"):
88
+ report["score"] += 15
89
+ report["checks"].append({"name": "Self-Destruct", "status": "FAIL", "detail": "Has selfdestruct function"})
90
+
91
+ if goplus.get("hidden_owner"):
92
+ report["score"] += 10
93
+ report["checks"].append({"name": "Hidden Owner", "status": "FAIL", "detail": "Hidden owner detected"})
94
+
95
+ if goplus.get("owner_can_mint"):
96
+ report["score"] += 10
97
+ report["checks"].append({"name": "Mintable", "status": "WARN", "detail": "Owner can mint new tokens"})
98
+
99
+ holder_count = goplus.get("holder_count", 0)
100
+ if holder_count:
101
+ report["checks"].append({"name": "Holders", "status": "INFO", "detail": f"{holder_count:,} holders"})
77
102
  else:
78
- report["checks"].append({"name": "Source Code", "status": "OK", "detail": "Verified on explorer"})
79
-
80
- if goplus.get("is_proxy"):
81
- report["score"] += 5
82
- report["checks"].append({"name": "Proxy", "status": "WARN", "detail": "Proxy contract — logic can change"})
83
-
84
- if goplus.get("selfdestruct"):
85
- report["score"] += 15
86
- report["checks"].append({"name": "Self-Destruct", "status": "FAIL", "detail": "Has selfdestruct function"})
87
-
88
- if goplus.get("hidden_owner"):
89
- report["score"] += 10
90
- report["checks"].append({"name": "Hidden Owner", "status": "FAIL", "detail": "Hidden owner detected"})
91
-
92
- if goplus.get("owner_can_mint"):
93
- report["score"] += 10
94
- report["checks"].append({"name": "Mintable", "status": "WARN", "detail": "Owner can mint new tokens"})
95
-
96
- holder_count = goplus.get("holder_count", 0)
97
- if holder_count:
98
- report["checks"].append({"name": "Holders", "status": "INFO", "detail": f"{holder_count:,} holders"})
103
+ report["checks"].append({"name": "GoPlus", "status": "INFO", "detail": f"API error: {goplus['error'][:50]}"})
104
+ except Exception as e:
105
+ report["checks"].append({"name": "GoPlus", "status": "INFO", "detail": f"Could not check: {str(e)[:50]}"})
99
106
 
100
107
  # Cap score at 100
101
108
  report["score"] = min(report["score"], 100)
@@ -1,6 +1,6 @@
1
1
  """Solana token security checker.
2
2
 
3
- Uses Solana RPC + Jupiter API for token data.
3
+ Uses Solana RPC + Solana Token Registry for token data.
4
4
  """
5
5
 
6
6
  import requests
@@ -8,9 +8,36 @@ from .db import cache_get, cache_set
8
8
  from .utils import print_header, print_ok, print_warn, print_fail, print_info
9
9
 
10
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}"
11
+ TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json"
12
+
13
+ # Cache the token registry
14
+ _registry_cache = None
15
+
16
+
17
+ def _get_token_registry() -> dict:
18
+ """Get the Solana token registry (cached)."""
19
+ global _registry_cache
20
+ if _registry_cache is not None:
21
+ return _registry_cache
22
+
23
+ cached = cache_get("solana_token_registry")
24
+ if cached:
25
+ _registry_cache = cached
26
+ return cached
27
+
28
+ try:
29
+ r = requests.get(TOKEN_REGISTRY_URL, timeout=30)
30
+ r.raise_for_status()
31
+ data = r.json()
32
+ tokens = {}
33
+ for t in data.get("tokens", []):
34
+ tokens[t["address"]] = t
35
+ _registry_cache = tokens
36
+ cache_set("solana_token_registry", tokens, ttl=86400) # Cache 24h
37
+ return tokens
38
+ except Exception:
39
+ _registry_cache = {}
40
+ return {}
14
41
 
15
42
 
16
43
  def check_solana_token(mint: str) -> dict:
@@ -30,35 +57,23 @@ def check_solana_token(mint: str) -> dict:
30
57
  "holder_count": 0,
31
58
  "total_supply": 0,
32
59
  "decimals": 0,
33
- "is_on_jupiter": False,
60
+ "is_known": False,
34
61
  "freeze_authority": None,
35
62
  "mint_authority": None,
36
- "is_mutable": True,
63
+ "tags": [],
37
64
  }
38
65
 
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
66
+ # 1. Get token info from registry
67
+ registry = _get_token_registry()
68
+ if mint in registry:
69
+ token_info = registry[mint]
70
+ report["token_name"] = token_info.get("name", "Unknown")
71
+ report["token_symbol"] = token_info.get("symbol", "?")
72
+ report["decimals"] = token_info.get("decimals", 0)
73
+ report["is_known"] = True
74
+ report["tags"] = token_info.get("tags", [])
75
+
76
+ # 2. Get on-chain token info via RPC
62
77
  try:
63
78
  payload = {
64
79
  "jsonrpc": "2.0",
@@ -66,7 +81,7 @@ def check_solana_token(mint: str) -> dict:
66
81
  "method": "getAccountInfo",
67
82
  "params": [mint, {"encoding": "jsonParsed"}],
68
83
  }
69
- r = requests.post(SOLANA_RPC, json=payload, timeout=10)
84
+ r = requests.post(SOLANA_RPC, json=payload, timeout=15)
70
85
  data = r.json()
71
86
  account = data.get("result", {}).get("value", {})
72
87
 
@@ -76,26 +91,30 @@ def check_solana_token(mint: str) -> dict:
76
91
 
77
92
  report["mint_authority"] = info.get("mintAuthority")
78
93
  report["freeze_authority"] = info.get("freezeAuthority")
79
- report["is_mutable"] = info.get("isInitialized", True)
80
- report["total_supply"] = int(info.get("supply", "0"))
94
+ supply_raw = int(info.get("supply", "0"))
95
+ report["total_supply"] = supply_raw
81
96
  report["decimals"] = info.get("decimals", report["decimals"])
82
- except Exception:
83
- pass
84
97
 
85
- # 4. Get holder count from Birdeye (optional, may fail without API key)
98
+ # Calculate human-readable supply
99
+ if report["decimals"] > 0:
100
+ report["total_supply_ui"] = supply_raw / (10 ** report["decimals"])
101
+ else:
102
+ report["total_supply_ui"] = supply_raw
103
+ except Exception as e:
104
+ report["rpc_error"] = str(e)[:50]
105
+
106
+ # 3. Get holder count from Solana RPC (approximate)
86
107
  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)
108
+ payload = {
109
+ "jsonrpc": "2.0",
110
+ "id": 1,
111
+ "method": "getTokenLargestAccounts",
112
+ "params": [mint],
113
+ }
114
+ r = requests.post(SOLANA_RPC, json=payload, timeout=15)
115
+ data = r.json()
116
+ largest = data.get("result", {}).get("value", [])
117
+ report["top_holders"] = len(largest)
99
118
  except Exception:
100
119
  pass
101
120
 
@@ -113,34 +132,23 @@ def check_solana_token(mint: str) -> dict:
113
132
  risks.append("MINT AUTHORITY — unlimited supply, issuer can mint more")
114
133
  score += 15
115
134
 
116
- # Not on Jupiter
117
- if not report["is_on_jupiter"]:
118
- risks.append("NOT ON JUPITER — unvetted token, high risk")
135
+ # Not in registry
136
+ if not report["is_known"]:
137
+ risks.append("NOT IN TOKEN REGISTRY — unvetted token, high risk")
119
138
  score += 15
120
139
 
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
140
+ # Stablecoin tag (positive)
141
+ if "stablecoin" in report.get("tags", []):
142
+ score -= 10 # Lower risk for known stablecoins
125
143
 
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:
144
+ # Native SOL wrapped (positive)
145
+ if "native" in report.get("tags", []):
146
+ score -= 5
147
+
148
+ report["risk_score"] = max(min(score, 100), 0)
149
+ if report["risk_score"] <= 20:
142
150
  report["risk_level"] = "LOW"
143
- elif score <= 50:
151
+ elif report["risk_score"] <= 50:
144
152
  report["risk_level"] = "MEDIUM"
145
153
  else:
146
154
  report["risk_level"] = "HIGH"
@@ -165,7 +173,7 @@ def check_solana_wallet(wallet: str) -> dict:
165
173
  "method": "getBalance",
166
174
  "params": [wallet],
167
175
  }
168
- r = requests.post(SOLANA_RPC, json=payload, timeout=10)
176
+ r = requests.post(SOLANA_RPC, json=payload, timeout=15)
169
177
  data = r.json()
170
178
  report["sol_balance"] = data.get("result", {}).get("value", 0) / 1e9
171
179
  except Exception:
@@ -183,16 +191,30 @@ def check_solana_wallet(wallet: str) -> dict:
183
191
  {"encoding": "jsonParsed"},
184
192
  ],
185
193
  }
186
- r = requests.post(SOLANA_RPC, json=payload, timeout=10)
194
+ r = requests.post(SOLANA_RPC, json=payload, timeout=15)
187
195
  data = r.json()
188
196
  accounts = data.get("result", {}).get("value", [])
189
197
 
198
+ registry = _get_token_registry()
199
+
190
200
  for acc in accounts:
191
201
  info = acc["account"]["data"]["parsed"]["info"]
192
202
  token_amount = info.get("tokenAmount", {})
203
+ mint = info.get("mint", "")
204
+ balance = token_amount.get("uiAmount", 0)
205
+
206
+ # Get token name from registry
207
+ name = "Unknown"
208
+ symbol = mint[:6]
209
+ if mint in registry:
210
+ name = registry[mint].get("name", "Unknown")
211
+ symbol = registry[mint].get("symbol", symbol)
212
+
193
213
  report["tokens"].append({
194
- "mint": info.get("mint", ""),
195
- "balance": token_amount.get("uiAmount", 0),
214
+ "mint": mint,
215
+ "name": name,
216
+ "symbol": symbol,
217
+ "balance": balance,
196
218
  "decimals": token_amount.get("decimals", 0),
197
219
  })
198
220
  except Exception:
@@ -207,15 +229,14 @@ def print_solana_token_report(report: dict):
207
229
  symbol = report["token_symbol"]
208
230
  print_header(f"SOLANA TOKEN CHECK — {name} ({symbol})")
209
231
 
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")
232
+ # Known status
233
+ if report.get("is_known"):
234
+ print_ok("Listed in Solana Token Registry")
235
+ tags = report.get("tags", [])
236
+ if tags:
237
+ print_info(f"Tags: {', '.join(tags)}")
217
238
  else:
218
- print_fail("Not listed on Jupiter — unvetted token")
239
+ print_warn("NOT in Token Registry — unvetted token")
219
240
 
220
241
  # Freeze authority
221
242
  if report["freeze_authority"]:
@@ -229,24 +250,13 @@ def print_solana_token_report(report: dict):
229
250
  else:
230
251
  print_ok("Mint Authority: None (fixed supply)")
231
252
 
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}")
253
+ # Supply
254
+ if report.get("total_supply_ui"):
255
+ print_info(f"Total Supply: {report['total_supply_ui']:,.2f}")
256
+
257
+ # Top holders
258
+ if report.get("top_holders"):
259
+ print_info(f"Top holders: {report['top_holders']} largest accounts")
250
260
 
251
261
  # Risk score
252
262
  score = report["risk_score"]
@@ -6,16 +6,47 @@ from typing import Optional
6
6
  import requests
7
7
  from web3 import Web3
8
8
 
9
- # Default RPC endpoints (public, no key needed)
9
+ # Default RPC endpoints (public, no key needed) — with fallbacks
10
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",
11
+ "eth": [
12
+ "https://rpc.ankr.com/eth",
13
+ "https://eth.llamarpc.com",
14
+ "https://eth.drpc.org",
15
+ "https://cloudflare-eth.com",
16
+ ],
17
+ "bsc": [
18
+ "https://bsc-dataseed1.binance.org",
19
+ "https://bsc-dataseed2.binance.org",
20
+ "https://rpc.ankr.com/bsc",
21
+ ],
22
+ "polygon": [
23
+ "https://polygon-rpc.com",
24
+ "https://rpc.ankr.com/polygon",
25
+ "https://polygon.drpc.org",
26
+ ],
27
+ "arbitrum": [
28
+ "https://arb1.arbitrum.io/rpc",
29
+ "https://rpc.ankr.com/arbitrum",
30
+ "https://arbitrum.drpc.org",
31
+ ],
32
+ "optimism": [
33
+ "https://mainnet.optimism.io",
34
+ "https://rpc.ankr.com/optimism",
35
+ "https://optimism.drpc.org",
36
+ ],
37
+ "base": [
38
+ "https://mainnet.base.org",
39
+ "https://rpc.ankr.com/base",
40
+ "https://base.drpc.org",
41
+ ],
42
+ "avalanche": [
43
+ "https://api.avax.network/ext/bc/C/rpc",
44
+ "https://rpc.ankr.com/avalanche",
45
+ ],
46
+ "fantom": [
47
+ "https://rpc.ftm.tools",
48
+ "https://rpc.ankr.com/fantom",
49
+ ],
19
50
  }
20
51
 
21
52
  CHAIN_IDS = {
@@ -95,31 +126,7 @@ ERC20_ABI = [
95
126
  },
96
127
  ]
97
128
 
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
129
+ # Known addresses to label
123
130
  KNOWN_ADDRESSES = {
124
131
  "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": "Uniswap V2 Router",
125
132
  "0xe592427a0aece92de3edee1f18e0157c05861564": "Uniswap V3 Router",
@@ -140,11 +147,23 @@ KNOWN_ADDRESSES = {
140
147
 
141
148
 
142
149
  def get_web3(chain: str = "eth") -> Web3:
143
- rpc = RPC_ENDPOINTS.get(chain)
144
- if not rpc:
150
+ """Get Web3 instance with automatic RPC fallback."""
151
+ endpoints = RPC_ENDPOINTS.get(chain)
152
+ if not endpoints:
145
153
  print(f"Unsupported chain: {chain}")
146
154
  sys.exit(1)
147
- return Web3(Web3.HTTPProvider(rpc))
155
+
156
+ for rpc in endpoints:
157
+ try:
158
+ w3 = Web3(Web3.HTTPProvider(rpc, request_kwargs={"timeout": 10}))
159
+ # Test connection
160
+ w3.eth.block_number
161
+ return w3
162
+ except Exception:
163
+ continue
164
+
165
+ print(f"All RPC endpoints failed for {chain}")
166
+ sys.exit(1)
148
167
 
149
168
 
150
169
  def is_valid_address(addr: str) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cryptoshield
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: All-in-one crypto security toolkit — honeypot, approvals, rugpull, phishing
5
5
  Author-email: yossweh <cilokcilok15@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cryptoshield"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "All-in-one crypto security toolkit — honeypot, approvals, rugpull, phishing"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes