Crypto & Web3 Security
Prevent exchange impersonation, wallet drainer sites, and OTC trading fraud.
Crypto fraud losses exceeded $5.6 billion in 2023 according to the FBI, with fake exchange support lines, wallet drainer URLs, and P2P trading scams accounting for a large share. The irreversible nature of blockchain transactions makes prevention critical. Once funds leave a wallet, they are gone. The ScamVerify™ API provides URL and phone intelligence that catches these threats before users lose assets.
Primary Attack Vectors
Fake Exchange Support Lines
The FBI has issued multiple warnings about scammers impersonating Coinbase, Binance, and Kraken support. They purchase VoIP numbers, run Google Ads for "Coinbase support number," and walk victims through "account recovery" that actually drains their wallets. These numbers rotate frequently, but many accumulate FTC complaints before being discarded.
Wallet Drainer URLs
Malicious sites that mimic legitimate DApps or exchanges. When a user connects their wallet and signs a transaction, the site drains all tokens. These domains are often registered hours before use and appear in phishing emails, Discord messages, and social media ads. Many are tracked in threat intelligence feeds like URLhaus and ThreatFox.
P2P and OTC Trading Scams
Over-the-counter crypto trades often involve communicating with an unknown counterparty via phone or messaging. Scammers use disposable VoIP numbers, build trust over several small trades, then disappear with funds on a large transaction.
How ScamVerify™ Helps
- URL verification catches known wallet drainer domains, phishing sites, and newly registered lookalike domains before users connect wallets
- Phone lookup identifies disposable VoIP numbers used by fake support lines and P2P scammers
- Text analysis scans messages and support chat transcripts for social engineering patterns
Code Example: URL Pre-Check Before Wallet Connection
class Web3SafetyGate {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://scamverify.ai/api/v1';
}
async checkUrl(url) {
const response = await fetch(`${this.baseUrl}/url/lookup`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json();
}
async checkPhone(phoneNumber) {
const response = await fetch(`${this.baseUrl}/phone/lookup`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone_number: phoneNumber }),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json();
}
async preConnectCheck(dappUrl) {
const result = await this.checkUrl(dappUrl);
const assessment = {
url: dappUrl,
riskScore: result.risk_score,
verdict: result.verdict,
safe: false,
warnings: [],
};
// Strict thresholds for wallet connections
if (result.risk_score >= 30) {
assessment.warnings.push({
level: 'danger',
message: `This site has a risk score of ${result.risk_score}/100. Do not connect your wallet.`,
detail: result.explanation,
});
return assessment;
}
if (result.verdict === 'phishing' || result.verdict === 'malware') {
assessment.warnings.push({
level: 'danger',
message: `This site has been identified as ${result.verdict} by threat intelligence sources.`,
});
return assessment;
}
// Check for domain age and suspicious patterns
if (result.signals?.domain_age_days < 30) {
assessment.warnings.push({
level: 'caution',
message: 'This domain was registered less than 30 days ago.',
});
}
assessment.safe = assessment.warnings.length === 0 ||
assessment.warnings.every(w => w.level === 'caution');
return assessment;
}
async verifyOtcCounterparty(phoneNumber) {
const result = await this.checkPhone(phoneNumber);
return {
phone: phoneNumber,
riskScore: result.risk_score,
lineType: result.signals.line_type,
carrier: result.signals.carrier,
ftcComplaints: result.signals.ftc_complaints,
isDisposable: result.signals.line_type === 'voip' && result.risk_score >= 30,
safe: result.risk_score < 40 && result.signals.ftc_complaints === 0,
explanation: result.explanation,
};
}
}
// Usage: Pre-check before wallet connection (browser extension or DApp)
const gate = new Web3SafetyGate(process.env.SCAMVERIFY_API_KEY);
// API endpoint for a wallet browser extension
app.post('/api/safety/pre-connect', async (req, res) => {
const { url } = req.body;
const check = await gate.preConnectCheck(url);
if (!check.safe) {
return res.json({
allow: false,
warnings: check.warnings,
recommendation: 'Do not connect your wallet to this site.',
});
}
return res.json({
allow: true,
riskScore: check.riskScore,
});
});
// API endpoint for P2P trade counterparty verification
app.post('/api/safety/verify-counterparty', async (req, res) => {
const { phone } = req.body;
const check = await gate.verifyOtcCounterparty(phone);
if (!check.safe) {
return res.json({
proceed: false,
reason: check.explanation,
signals: {
isDisposable: check.isDisposable,
ftcComplaints: check.ftcComplaints,
lineType: check.lineType,
},
});
}
return res.json({
proceed: true,
riskScore: check.riskScore,
carrier: check.carrier,
});
});import requests
import os
class Web3SafetyGate:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://scamverify.ai/api/v1"
def _call_api(self, endpoint: str, body: dict) -> dict:
response = requests.post(
f"{self.base_url}{endpoint}",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json=body,
)
response.raise_for_status()
return response.json()
def pre_connect_check(self, dapp_url: str) -> dict:
result = self._call_api("/url/lookup", {"url": dapp_url})
if result["risk_score"] >= 30:
return {
"safe": False,
"risk_score": result["risk_score"],
"reason": result["explanation"],
}
if result["verdict"] in ("phishing", "malware"):
return {
"safe": False,
"risk_score": result["risk_score"],
"reason": f"Site classified as {result['verdict']}",
}
return {
"safe": True,
"risk_score": result["risk_score"],
"verdict": result["verdict"],
}
def verify_otc_counterparty(self, phone_number: str) -> dict:
result = self._call_api("/phone/lookup", {"phone_number": phone_number})
is_disposable = (
result["signals"]["line_type"] == "voip"
and result["risk_score"] >= 30
)
return {
"safe": result["risk_score"] < 40
and result["signals"]["ftc_complaints"] == 0,
"risk_score": result["risk_score"],
"is_disposable": is_disposable,
"line_type": result["signals"]["line_type"],
"explanation": result["explanation"],
}Threat Signal Matrix for Crypto
| Scenario | API Check | Risk Signal | Action |
|---|---|---|---|
| User clicks DApp link from Discord | URL lookup | verdict: "phishing" or risk_score >= 30 | Block wallet connection, show warning |
| "Coinbase support" number found online | Phone lookup | line_type: "voip" + ftc_complaints > 0 | Flag as fake support number |
| OTC trade counterparty | Phone lookup | line_type: "voip" + high-risk carrier | Require additional identity verification before trading |
| Token launch website | URL lookup | Domain age < 7 days, no threat intel matches | Caution warning, do not block |
| Airdrop claim URL from email | URL lookup | verdict: "malware" or known threat feed match | Block immediately |
| Exchange withdrawal to external address | URL lookup on linked sites | Any malicious signals | Hold withdrawal for manual review |
Scanning Discord and Telegram Links
Crypto communities on Discord and Telegram are prime targets for phishing link drops. Build a bot that scans shared URLs before they reach your community.
// Discord bot middleware for link scanning
async function scanDiscordMessage(message) {
const urlRegex = /https?:\/\/[^\s>]+/gi;
const urls = message.content.match(urlRegex) || [];
if (urls.length === 0) return { safe: true };
const results = [];
for (const url of urls) {
const check = await gate.preConnectCheck(url);
if (!check.safe) {
results.push({ url, ...check });
}
}
if (results.length > 0) {
// Delete message and warn the sender
await message.delete();
await message.channel.send({
content: `A message was removed because it contained a potentially dangerous link. ${results[0].warnings[0].message}`,
});
return { safe: false, flagged: results };
}
return { safe: true };
}Lower thresholds for crypto. In traditional e-commerce, a risk score of 50 might warrant a review. For wallet connections, use 30 as your threshold. The cost of a false negative (wallet drained) far exceeds the cost of a false positive (user has to verify a legitimate site). Err on the side of caution.
Best Practices
- Check before connect, not after. Once a wallet signs a malicious transaction, the funds are gone. Always verify URLs before allowing wallet connections.
- Use strict thresholds. A risk score of 30+ should block wallet connections. Save lenient thresholds for lower-stakes interactions.
- Verify OTC counterparties proactively. Before any P2P trade above your risk threshold, check the counterparty's phone number. Disposable VoIP numbers are the top signal for OTC fraud.
- Scan community channels automatically. Phishing links in Discord and Telegram are the number one vector for wallet drainer distribution. Automated scanning catches them before your community members click.
- Cache and share threat data internally. When you flag a malicious URL, add it to your internal blocklist. ScamVerify™ caches results, so re-checking the same URL is free within the cache window.
Getting Started
- Create an API key and set up your account
- Follow the Quickstart guide to make your first lookup
- Review the URL Verification API and Phone Lookup API reference docs
- Implement the pre-connect check and deploy it as middleware in your DApp or wallet extension