Skip to content

Audit & safety API

Public, unauthenticated endpoints for the contract auditor and the safety suite. Mounted under /api/v1.

Product pages: contract auditor, safety suite.

Contract audit — /v1/audit

Route file: v1/routes/audit.route.js. Service: v1/services/contractAudit.service.js. Model: models/contractAuditModel.js.

MethodPathReturns
GET/v1/audit/report/:idAudit JSON
GET/v1/audit/certificate/:idCertificate JSON — only if certificateEligible, otherwise 404
GET/v1/audit/usage?wallet={ used, limit, remaining }

Audit document

FieldMeaning
auditId16-char uuid slice — the public report id
certificateId16-char uuid slice — the public certificate id
walletRequesting wallet, used for quota
scoreOverall security score
findings[]Severity, title, description, location
severityCounts{ critical, high, medium, low, informational }
certificateEligiblecritical === 0 && high === 0
statussuccess | failed

Quota

js
await SystemSetting.setValue('audit_free_limit_per_wallet', 5)   // default 3

Failed audits do not consume quota

getAuditUsage counts with status: { $ne: 'failed' }. A user whose audit crashed because Etherscan timed out is not penalised.

Source acquisition

Etherscan v2 unified API — one key, all chains:

https://api.etherscan.io/v2/api?chainid={chainId}&module=contract&action=getsourcecode&address=…

Supported: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Linea, Avalanche, Fantom.

Multi-file contracts come back as a JSON blob and are flattened with // File: <path> headers.

Source is clamped to 40,000 characters

The truncation flag is stored on the audit document, so nobody mistakes a partial audit for a complete one. Large protocols get a partial pass.

Share URLs

{AUDIT_PUBLIC_BASE_URL}/report/{auditId}
{AUDIT_PUBLIC_BASE_URL}/certificate/{certificateId}

AUDIT_PUBLIC_BASE_URL defaults to https://audit.blazpay.ai. The pages themselves are frontend routes outside MainLayout.

Token safety — /v1/safety

Route file: v1/routes/safety.route.js. Service: v1/services/tokenSafety.service.js. Model: models/tokenSafetyScanModel.js.

MethodPathReturns
GET/v1/safety/token/:address?chainId=…Scan JSON
GET/v1/safety/scan/:idScan JSON by scanId
GET/v1/safety/token/:address/badge.svg?chainId=…Embeddable SVG badge

Route order

badge.svg is registered before /token/:address. Reversing them would make the badge path resolve as an address.

Scan output

FieldValues
score0–100
verdictsafe | caution | risky | dangerous
findings[]Human-readable issues
checks[]ownership_renounced, mintable, blacklist, tax, proxy, honeypot
recommendationsWhat the user should do

Pipeline

  1. Etherscan v2 getsourcecode (library files filtered out).
  2. Etherscan v2 tokeninfo — symbol, decimals, supply, holders.
  3. Etherscan v2 tokenholderlist — top 10 → concentration, burn and locker detection (PinkLock, Unicrypt, Team.Finance addresses hardcoded).
  4. The safety model with a rug-focused prompt in JSON mode.
  5. Persist.

Caching

A scan less than 6 hours old for the same (address, chainId) is reused, skipping the model call.

This cache is what makes Autopilot's safety gate affordable

autopilotCron checks every token it buys against safety.minTokenScore. Without the 6-hour reuse, that would be a model call per token per tick. Trusted majors are skipped entirely; unscored tokens block rather than pass.

The embed badge

GET /v1/safety/token/:address/badge.svg returns a self-contained SVG a project can embed on its own site. The full iframe view is the frontend route /token/:address/embed.

The embed is blocked from third-party iframes on defi

netlify.toml sets X-Frame-Options: SAMEORIGIN for /* with no override. netlify.ai.toml fixes it for /token/*/embed via CSP frame-ancestors * — so the badge only works once the artifact routes live on ai.blazpay.com.

Wallet card — /v1/wallet-card

Route file: v1/routes/walletCard.route.js. Service: v1/services/walletCard.service.js. Model: models/walletCardModel.js.

MethodPathPurpose
GET/v1/wallet-card/:walletCached card JSON
POST/v1/wallet-card/generateForce a fresh generate. Body { wallet }

Accepts a 0x address, a Blazpay username, or a .eth name.

Data sources

SourceProvides
portfolioService.getSummary / getPortfolio (Zerion)Balances, chain distribution, top tokens
Etherscan v2 txlist (asc + desc, offset=1)First and last transaction per chain
Etherscan v2 eth_getTransactionCount proxyPer-chain transaction counts

Chains: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Linea, Avalanche.

Labels

Heuristic, no LLM: Whale / Shark / Fish · OG (5y+) / Veteran (3y+) / Newcomer · Multi-chain / Power user / Active · Diamond hands / Degen / Stable stacker.

Approval revoker

There is no backend route

The REVOKE_APPROVALS chat intent returns the scan inline in the message payload, and revokes are client-side transactions. Service: v1/services/approvals.service.js, called from agent.service.js.

Scan

  1. Etherscan v2 module=logs getLogs, topic0 = 0x8c5be1… (the Approval event), topic1 = pad32(wallet). Paginates up to 5 × 1000 logs.
  2. Reduce to the latest log per (token, spender).
  3. Keep pairs with value > 0.
  4. Cap at the top 25 by allowance size.
  5. Per token: tokeninfo. Per spender: getsourcecode for verified flag and name.

Risk score

ConditionPoints
Unlimited allowance+40
Unverified spender+25
Very large finite allowance+15

The scan infers "active" from the most recent Approval log

It does not read the current on-chain allowance(owner, spender), so a freshly spent allowance can still appear. A multicall over allowance is the clean hardening and is not implemented.

Public frontend pages

Routed outside MainLayout so they render unauthenticated:

RoutePage
/report/:idpages/audit/AuditReport.tsx
/certificate/:idpages/audit/AuditCertificate.tsx
/token/:addresspages/audit/TokenSafety.tsx
/token/:address/embedpages/audit/TokenSafetyEmbed.tsx
/wallet/:walletpages/audit/WalletCard.tsx

SEOHead is applied to all of them, which helps Googlebot. Social bots see the global OG fallback — there is no server-side OG rendering.

Export on the wallet card and certificate lazy-loads html-to-image and jspdf. The exportable region is <div ref={cardRef}>, and elements marked data-export-hide="true" are excluded — so buttons do not appear in the download.

Environment variables

VariableUsed by
ETHERSCAN_API_KEYAll four features. Etherscan v2 multi-chain
OPENAI_API_KEYCredential for the audit and token-safety models
AUDIT_PUBLIC_BASE_URLShare-link prefix

Wallet card and approval scanning use no LLM.

Failure modes

SymptomCause
Certificate 404scertificateEligible is false — there are critical or high findings
Audit by address failsSource is not verified on that chain's explorer. The user must paste source
Partial audit40k-character clamp; check the truncation flag
Weak token-safety score on a fine tokenUnverified source — the score reflects uncertainty, not confirmed danger
Badge will not embedX-Frame-Options on defi. See the warning above
Approval list shows a spent allowanceThe scan reads logs, not current allowances
Approval list looks shortCapped at 25 by size
Wallet card shows zero balanceZerion does not index that chain
Shared report unfurls genericallyNo server-side OG rendering

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.