Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions scripts/rugguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,48 @@ def format_markdown(report: RugReport) -> str:
def format_json(report: RugReport) -> str:
"""Format report as pretty JSON."""
return json.dumps(report.to_dict(), indent=2, default=str)


def _svg_badge(report: RugReport, style: str = "flat", label: str = "safety") -> str:
"""Generate a shields.io-compatible SVG badge."""
score = report.safety_score
level = report.risk_level
if score >= 70:
bg = "#4c1"
elif score >= 40:
bg = "#e67e22"
elif score >= 20:
bg = "#e74c3c"
else:
bg = "#c0392b"

label_text = label
value_text = str(score) + "/100 \u2014 " + level
label_w = max(len(label_text) * 7 + 10, 40)
value_w = len(value_text) * 7 + 10
total_w = label_w + value_w
h = 20
rx = 3 if style == "flat" else 0
lx = label_w // 2
vx = label_w + value_w // 2

lines = []
lines.append('<svg xmlns="http://www.w3.org/2000/svg" width="' + str(total_w) + '" height="' + str(h) + '">')
lines.append('<linearGradient id="l" x2="0" y2="1"><stop offset="0%" stop-color="#bbb" stop-opacity=".1"/><stop offset="100%" stop-color="#000" stop-opacity=".1"/></linearGradient>')
lines.append('<rect width="' + str(label_w) + '" height="' + str(h) + '" fill="#555" rx="' + str(rx) + '"/>')
lines.append('<rect x="' + str(label_w) + '" width="' + str(value_w) + '" height="' + str(h) + '" fill="' + bg + '" rx="' + str(rx) + '"/>')
lines.append('<rect width="' + str(total_w) + '" height="' + str(h) + '" fill="url(#l)"/>')
lines.append('<g fill="#fff" font-family="Arial,sans-serif" font-size="11" text-anchor="middle">')
lines.append('<text x="' + str(lx) + '" y="14">' + _escape_svg(label_text) + '</text>')
lines.append('<text x="' + str(vx) + '" y="14">' + _escape_svg(value_text) + '</text>')
lines.append('</g></svg>')
return "\n".join(lines)


def _escape_svg(text: str) -> str:
return (text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))


# ── CLI Entry Point ────────────────────────────────────────────────────────

def cli_token(args: list[str]) -> None:
Expand Down Expand Up @@ -1660,12 +1702,39 @@ def cli_wallet(args: list[str]) -> None:

if result.get("risky_count", 0) > 0:
sys.exit(2)


def cli_badge(args: list[str]) -> None:
"""Generate an SVG safety score badge for a token."""
if not args:
print('Usage: python rugguard.py badge <MINT> [--style flat|flat-square|plastic] [--label TEXT]', file=sys.stderr)
sys.exit(1)

mint = args[0]
style = "flat"
label = "safety"

for a in args:
if a.startswith("--style="):
style = a.split("=", 1)[1]
elif a == "--style" and args.index(a) + 1 < len(args):
style = args[args.index(a) + 1]
if a.startswith("--label="):
label = a.split("=", 1)[1]
elif a == "--label" and args.index(a) + 1 < len(args):
label = args[args.index(a) + 1]

report = rug_check_token(mint.strip())
print(_svg_badge(report, style=style, label=label))


def cli_help() -> None:
print("""Solana Rug Guard — On-chain rug-pull detection engine

USAGE:
python rugguard.py token <MINT_ADDRESS> [--json|--markdown]
python rugguard.py wallet <WALLET_ADDRESS>
python rugguard.py badge <MINT> [--style flat|flat-square|plastic] [--label TEXT]
python rugguard.py watch <MINT_ADDRESS> [--interval 60] [--iterations 0]
[--history PATH] [--webhook URL] [--threshold SCORE]

Expand All @@ -1685,6 +1754,7 @@ def cli_help() -> None:
python rugguard.py token DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
python rugguard.py token DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263 --markdown
python rugguard.py wallet 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
python rugguard.py badge DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
python rugguard.py watch <MINT_ADDRESS> --iterations 1 --threshold 70

ENVIRONMENT:
Expand Down
71 changes: 70 additions & 1 deletion tests/test_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,73 @@ def test_wallet_scan() -> None:
assert "address" in result
assert result["address"] == TEST_WALLET
assert "total_tokens" in result
assert isinstance(result["total_tokens"], int)
assert isinstance(result['total_tokens'], int)


# ── Badge Tests ───────────────────────────────────────────────────────────

class TestBadge:
def test_badge_green_low(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=95, risk_level='LOW',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r)
assert '#4c1' in svg # green
assert '95/100' in svg
assert 'LOW' in svg

def test_badge_yellow_medium(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=55, risk_level='MEDIUM',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r)
assert '#e67e22' in svg # yellow
assert '55/100' in svg

def test_badge_red_high(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=25, risk_level='HIGH',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r)
assert '#e74c3c' in svg # red

def test_badge_darkred_critical(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=10, risk_level='CRITICAL',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r)
assert '#c0392b' in svg # dark red

def test_badge_custom_label(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=80, risk_level='LOW',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r, label='rugcheck')
assert 'rugcheck' in svg

def test_badge_is_valid_svg(self):
from rugguard import _svg_badge, RugScore, RugFlags, TokenMeta
flags = RugFlags()
r = RugReport(
token=TokenMeta(address='A'), safety_score=80, risk_level='LOW',
score=RugScore(), flags=flags, warnings=[], recommendation='',
)
svg = _svg_badge(r)
assert svg.startswith('<svg')
assert svg.endswith('</svg>')
assert 'xmlns=' in svg