Appearance
Safety suite
Three in-chat tools that sit on top of the same Etherscan v2 + model infrastructure as the contract auditor: a token rug checker, a wallet reputation card, and an approval revoker.
All three are free, need no auth, and produce a public shareable artifact.
Token safety (RugCheck)
Intent: TOKEN_SAFETY · Action: token_safety
Scans an ERC-20 for the patterns that precede a rug: concentrated holdings, mint authority, blacklists, punitive taxes, honeypot mechanics.
Pipeline
Locker detection recognises PinkLock, Unicrypt and Team.Finance addresses (hardcoded), so LP tokens sitting in a legitimate locker are not misread as whale concentration.
Output shape
| Field | Meaning |
|---|---|
score | 0–100 |
verdict | safe | caution | risky | dangerous |
findings[] | Human-readable issues |
checks[] | ownership_renounced, mintable, blacklist, tax, proxy, honeypot |
recommendations | What a user should do |
Caching
A scan less than 6 hours old for the same (address, chainId) is reused, skipping the model call entirely. This is the single largest cost control in the safety suite, and it is why Autopilot's safety gate can afford to check every token it buys.
Routes
All public, no auth:
| Endpoint | Returns |
|---|---|
GET /v1/safety/token/:address?chainId=… | Scan JSON |
GET /v1/safety/scan/:id | Scan JSON by scanId |
GET /v1/safety/token/:address/badge.svg?chainId=… | Embeddable SVG badge |
Surfaces
| Surface | File |
|---|---|
| Chat card | defi-dex/src/component/defi/swapAI/actions/TokenSafetyResult.tsx |
| Public page | /token/:address — src/pages/audit/TokenSafety.tsx |
| Embed view | /token/:address/embed — src/pages/audit/TokenSafetyEmbed.tsx |
The public page shows score, verdict, the checks grid, a top-holders bar, full findings, and — the growth mechanic — an embed snippet a project can copy onto its own token site. The embed view has a transparent background and is fully self-contained.
The embed badge cannot be iframed from defi.blazpay.com
netlify.toml sets X-Frame-Options: SAMEORIGIN for /* with no override, so third-party embedding fails today. netlify.ai.toml fixes it for /token/*/embed via CSP frame-ancestors * — which means the badge only works once the artifact routes live on ai.blazpay.com. See domains.
Used by Autopilot
The same scanner is the safety gate in autopilotCron. safety.minTokenScore in an Autopilot policy is compared against this score. Trusted majors are skipped; unscored tokens block rather than pass.
Wallet reputation card
Intent: WALLET_CARD · Action: wallet_card
A shareable on-chain profile. Accepts a connected wallet, a 0x address, a Blazpay username, or a .eth name.
Data sources
| Source | Provides |
|---|---|
portfolioService.getSummary / getPortfolio (Zerion) | Balances, chain distribution, top tokens |
Etherscan v2 txlist (ascending + descending with offset=1) | First and last transaction timestamp per chain |
Etherscan v2 eth_getTransactionCount proxy | Per-chain transaction counts |
Chains covered: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Linea, Avalanche.
Labels
Computed heuristically — no LLM involved:
| Category | Labels |
|---|---|
| Size | Whale, Shark, Fish |
| Tenure | OG (5y+), Veteran (3y+), Newcomer |
| Breadth | Multi-chain, Power user, Active |
| Behaviour | Diamond hands, Degen, Stable stacker |
Routes
| Endpoint | Purpose |
|---|---|
GET /v1/wallet-card/:wallet | Cached card JSON |
POST /v1/wallet-card/generate | Force a fresh generate |
Surfaces
| Surface | File |
|---|---|
| Chat card | swapAI/actions/WalletCardResult.tsx |
| Public page | /wallet/:wallet — src/pages/audit/WalletCard.tsx |
The public page uses the same "diploma" treatment as the audit certificate, with Refresh, Share, and Download → PNG/PDF. Export lazy-loads html-to-image and jspdf. The exportable region is wrapped in <div ref={cardRef}>, and elements marked data-export-hide="true" are excluded from the render — so the buttons do not appear in the downloaded image.
Approval revoker
Intent: REVOKE_APPROVALS · Action: revoke_approvals
Finds the ERC-20 allowances a wallet has granted, scores their risk, and revokes them one click at a time.
Scan
Server-side, in bz-backend/v1/services/approvals.service.js:
- Etherscan v2
module=logsgetLogswith theApprovaltopic00x8c5be1…andtopic1 = pad32(wallet). Paginates up to 5 × 1000 logs. - Reduce to the latest log per
(token, spender)pair. - Keep only pairs whose value is
> 0. - Cap at the top 25 by allowance size, to bound the metadata fan-out.
- Per token:
tokeninfofor symbol, name, decimals. - Per spender:
getsourcecodefor the verified flag and contract name.
Risk score
| Condition | Points |
|---|---|
| Unlimited allowance | +40 |
| Unverified spender contract | +25 |
| Very large (but finite) allowance | +15 |
Revocation
There is no backend route — the chat intent returns the scan inline, and revokes are client-side transactions.
swapAI/actions/ApprovalRevoker.tsx:
- Reads
useWallet()for a provider andchainId. - If the wallet is on the wrong chain, shows a "Switch to chain" button using
wallet.switchChain. - Each row's Revoke button builds
Contract(token, erc20Abi, signer).approve(spender, 0). - Per-row state machine: idle → pending → success/error. The row is removed on success.
The scan infers "active" from the most recent Approval log
It does not query the current on-chain allowance(owner, spender). A freshly spent allowance can therefore still appear in the list. The clean hardening is a multicall over allowance before display — a known, unimplemented improvement.
Public pages
All routed outside MainLayout so they work unauthenticated, historically at audit.blazpay.ai and migrating to ai.blazpay.com:
| Route | Page |
|---|---|
/token/:address | Token scan |
/token/:address/embed | Iframe-embeddable mini card |
/wallet/:wallet | Wallet reputation card |
/report/:id | Audit report — see contract auditor |
/certificate/:id | Audit certificate |
SEOHead is applied to all of them, which helps Googlebot. Social bots still see the global OG fallback because there is no server-side OG rendering — a known future improvement.
bz-agent wiring
All three intents follow the audit feature's pattern exactly:
| Piece | Files |
|---|---|
| Templates | src/template/{tokenSafety,walletCard,revokeApprovals}.ts |
| Nodes | tokenSafetyNode, walletCardNode, revokeApprovalsNode in src/agent/node.ts |
| Graph | addNode + addEdge(…, 'EXIT') + a case in the entry conditional edges, in src/agent/graph.ts |
| Intents | AVAILABLE_INTENTS in src/utils/constant.ts |
bz-backend dispatch
Three cases in v1/services/agent.service.js:
js
case 'TOKEN_SAFETY': return await scanToken(...)
case 'WALLET_CARD': return await generateWalletCard(...)
case 'REVOKE_APPROVALS': return await scanApprovals(...)Plus three fields on BlazMessage.data (tokenSafety, walletCard, revokeApprovals) and three BLAZ_ACTIONS entries.
Environment variables
| Variable | Used by |
|---|---|
ETHERSCAN_API_KEY | All three — Etherscan v2 unified multi-chain key |
OPENAI_API_KEY | Token safety only. Wallet card and approvals use no model |
AUDIT_PUBLIC_BASE_URL | Share-link prefix, defaults to https://audit.blazpay.ai |
One Etherscan key covers every chain
The v2 endpoint is https://api.etherscan.io/v2/api?chainid=…. Supported here: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Linea, Avalanche, Fantom.
Known limits
- Wallet card "oldest hold" defaults to wallet age, not per-token entry time. Correct per-token entry needs an ERC-20
Transferlog index per chain. - Wallet card "protocols used" is reserved but unfilled — it needs a curated counterparty address book.
- ENS resolution is only against the internal username database. Real on-chain resolution is one ENS Universal Resolver
eth_callaway. - Approval scan does not read current allowances. See the warning above.
- The approval scan caps at 25 entries. A wallet with hundreds of approvals sees only the largest.
- Token safety depends on verified source. An unverified contract yields a much weaker scan, and the score reflects uncertainty rather than confirmed safety.
- The embed badge is blocked by
X-Frame-Optionson defi. See the warning above.