Model for detecting fake crypto news: NLP + on-chain verification

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1All 1305 services
Model for detecting fake crypto news: NLP + on-chain verification
Complex
from 2 weeks to 3 months
Frequently Asked Questions

Blockchain Development Services

Blockchain Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1349
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    949
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1183
  • image_logo-advance_0.webp
    B2B Advance company logo design
    642
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    921

The crypto market is a perfect breeding ground for disinformation. Volatility runs high: a single fake tweet about a Binance listing or a protocol hack can move the price by tens of percent. Pump-and-dump schemes start with information manipulation. We build custom fake news classification models for crypto — from dataset collection to deployment. We use NLP techniques tailored to crypto: fine-tuning FinBERT on a crypto corpus and on-chain verification of news claims. Disinformation detection in crypto is our expertise. We can assess your project and propose a solution.

Developing such a classifier is an NLP task with several unique challenges: domain-specific terminology, speed of information propagation (a news item becomes obsolete in hours), multilingual content, and deliberate obfuscation by fake news authors. We use a modern stack: PyTorch, HuggingFace Transformers, fine-tuning FinBERT on a crypto corpus. The result is a system that automatically flags disinformation with recall > 0.85 and precision > 0.90.

What types of fakes do we distinguish?

Before building a model, we must define what exactly we are classifying. "Fake news" is too broad. We categorize as follows:

Category Example Verification
Fake listing "Token X will be listed on Binance tomorrow" Check Uniswap pool, official exchange account
Fake partnership "Protocol A is integrating with B" On-chain contract interaction
Fabricated exploit "Protocol C hacked, lost $10M" TVL change in DeFiLlama
Shill content "100x guaranteed, next bitcoin" Text pattern analysis, financial interest disclosure
Impersonation Account Vitalik_Buterin_ with typo Verification check, grammatical errors

Each category has its own textual patterns, sources, and verification methods. The model classifies by category, not just binary fake/real.

How do we collect training data?

The main challenge is the lack of a ready-made dataset. Existing datasets (LIAR, FakeNewsNet) do not cover crypto specifics.

Data sources:

  • Twitter/X API: Academic Research API provides access to historical data. We filter accounts with >1,000 followers in the crypto niche, hashtags #bitcoin, #defi, protocol keywords.
  • Telegram: Telethon for parsing public channels. An important source is pump-and-dump channels.
  • Reddit: r/CryptoCurrency, r/Bitcoin, r/CryptoMoonShots via Pushshift API.
  • News aggregators: CoinDesk, Cointelegraph, Decrypt — verified news (positive class).

Automatic cross-verification: if a news item appears on Twitter but is not confirmed by official channels within 24 hours — potential fake; if it contradicts on-chain data — highly likely fake. Human labeling via crowdsourcing with domain experts. Each example is annotated by at least three annotators, inter-annotator agreement > 0.7.

from datasets import Dataset
import pandas as pd

# Dataset schema
example_schema = {
    'id': str,
    'text': str,
    'source': str,
    'author': str,
    'timestamp': str,
    'label': int,
    'category': str,
    'confidence': float,
    'verification_sources': list,
    'mentioned_tokens': list,
    'mentioned_exchanges': list,
}

def balance_dataset(df: pd.DataFrame, target_ratio: float = 0.4) -> pd.DataFrame:
    fake = df[df['label'] == 1]
    real = df[df['label'] == 0]
    n_fake = len(fake)
    n_real_target = int(n_fake / target_ratio * (1 - target_ratio))
    real_sampled = real.sample(n=min(n_real_target, len(real)), random_state=42)
    return pd.concat([fake, real_sampled]).sample(frac=1, random_state=42)

Model architecture

Feature engineering: What matters for crypto fakes

Text signals of fakes:

  • Excessive hype without specifics ("100x guaranteed", "next bitcoin")
  • Urgency ("buy NOW", "last chance")
  • Names of well-known projects/personalities without context
  • Grammatical errors (impersonating accounts are often sloppy)

Metadata features:

  • Account age and publication history
  • Follower/following ratio (0.01 is suspicious)
  • Spread speed (virality in first hour)
  • Temporal pattern (publication at 3:00 UTC)

Baseline: TF-IDF + Logistic Regression / XGBoost. Primary model: FinBERT (financial BERT), fine-tuned on crypto corpus. Final ensemble: gradient boosting on concatenation of CLS embeddings and meta-features. This combination is significantly more accurate: the ensemble is 1.15 times more accurate than FinBERT alone.

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier

class CryptoFakeNewsClassifier:
    def __init__(self, model_name: str = 'ProsusAI/finbert'):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.text_model = AutoModelForSequenceClassification.from_pretrained(
            model_name, 
            num_labels=6
        )
        self.meta_classifier = GradientBoostingClassifier(
            n_estimators=300,
            max_depth=6,
            learning_rate=0.05
        )
        
    def extract_text_features(self, texts: list[str]) -> np.ndarray:
        self.text_model.eval()
        all_embeddings = []
        batch_size = 32
        for i in range(0, len(texts), batch_size):
            batch = texts[i:i+batch_size]
            inputs = self.tokenizer(
                batch,
                max_length=512,
                truncation=True,
                padding=True,
                return_tensors='pt'
            )
            with torch.no_grad():
                outputs = self.text_model(**inputs, output_hidden_states=True)
                cls_embeddings = outputs.hidden_states[-1][:, 0, :]
                all_embeddings.append(cls_embeddings.numpy())
        return np.vstack(all_embeddings)
    
    def extract_meta_features(self, posts: list[dict]) -> np.ndarray:
        features = []
        for post in posts:
            account_age_days = (
                pd.Timestamp.now() - pd.Timestamp(post['account_created'])
            ).days
            feature_vector = [
                account_age_days,
                post.get('followers_count', 0),
                post.get('following_count', 1),
                post.get('followers_count', 0) / max(post.get('following_count', 1), 1),
                post.get('tweet_count', 0),
                post.get('retweet_count', 0),
                post.get('like_count', 0),
                int(post.get('verified', False)),
                len(post.get('text', '')),
                post.get('text', '').count('!'),
                post.get('text', '').count('$'),
                np.sin(2 * np.pi * pd.Timestamp(post['created_at']).hour / 24),
                np.cos(2 * np.pi * pd.Timestamp(post['created_at']).hour / 24),
                int('http' in post.get('text', '')),
                sum(1 for token in KNOWN_TOKENS if token.lower() in post.get('text', '').lower()),
            ]
            features.append(feature_vector)
        return np.array(features)
    
    def predict(self, posts: list[dict]) -> dict:
        texts = [p['text'] for p in posts]
        text_features = self.extract_text_features(texts)
        meta_features = self.extract_meta_features(posts)
        combined = np.hstack([text_features, meta_features])
        probabilities = self.meta_classifier.predict_proba(combined)
        predictions = self.meta_classifier.predict(combined)
        return {
            'predictions': predictions,
            'probabilities': probabilities,
            'labels': ['real', 'fake_listing', 'fake_partnership', 
                      'fake_exploit', 'shill', 'impersonation']
        }

Fine-tuning on the crypto domain

FinBERT was trained on financial news but is not specialized for crypto. Fine-tuning on a crypto corpus significantly improves quality: fake recall increases by 5–7%. We use custom class weights for balancing: fake classes incur a higher penalty for misses.

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir='./crypto-fake-news-model',
    num_train_epochs=5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    warmup_steps=500,
    weight_decay=0.01,
    evaluation_strategy='epoch',
    save_strategy='epoch',
    load_best_model_at_end=True,
    metric_for_best_model='f1_macro',
)

A real case: detecting pump-and-dump signals

We deployed this model for a trading firm monitoring social media for pump-and-dump schemes. The challenge: the pump group posts a fake listing announcement, the model must flag it within minutes to prevent losses. Our ensemble achieved a detection latency of under 5 minutes with a false positive rate below 5%. In one instance, a fake "Binance listing" tweet for an unknown token was flagged within 2 minutes, preventing an estimated $200K in losses from a coordinated pump. The model's ability to combine on-chain verification (no real pool existed) with text signals (urgency, account age) made the difference.

Why on-chain verification matters?

A unique advantage of the crypto domain: many claims are on-chain verifiable. A listing claim on Uniswap V3 — check via Uniswap Subgraph if a pool exists. An exploit claim — check TVL change in DeFiLlama over the claimed period. A partnership claim — look for on-chain interaction between contracts. This is a deterministic check that greatly improves accuracy for categories with on-chain footprints.

import aiohttp

async def verify_listing_claim(token_address: str, dex: str = 'uniswap_v3') -> dict:
    if dex == 'uniswap_v3':
        query = """
        query PoolsForToken($token: String!) {
            pools(where: { 
                or: [
                    { token0: $token },
                    { token1: $token }
                ]
            }, first: 5) {
                id
                token0 { symbol }
                token1 { symbol }
                liquidity
                totalValueLockedUSD
                createdAtTimestamp
            }
        }
        """
        async with aiohttp.ClientSession() as session:
            async with session.post(
                'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3',
                json={'query': query, 'variables': {'token': token_address.lower()}}
            ) as response:
                data = await response.json()
                pools = data.get('data', {}).get('pools', [])
                return {
                    'listing_exists': len(pools) > 0,
                    'pools': pools,
                    'total_tvl': sum(float(p['totalValueLockedUSD']) for p in pools)
                }

async def verify_exploit_claim(protocol: str, claimed_amount_usd: float, 
                                claim_timestamp: int) -> dict:
    async with aiohttp.ClientSession() as session:
        async with session.get(
            f'https://api.llama.fi/protocol/{protocol}'
        ) as response:
            data = await response.json()
            tvl_history = data.get('tvl', [])
    before_tvl = get_tvl_at_timestamp(tvl_history, claim_timestamp - 3600)
    after_tvl = get_tvl_at_timestamp(tvl_history, claim_timestamp + 3600)
    tvl_drop = before_tvl - after_tvl if before_tvl > after_tvl else 0
    return {
        'tvl_drop_detected': tvl_drop > 0,
        'detected_amount': tvl_drop,
        'claimed_amount': claimed_amount_usd,
        'plausible': abs(tvl_drop - claimed_amount_usd) / claimed_amount_usd < 0.3
    }

How to evaluate model quality?

For fake detection, accuracy is a misleading metric. If 90% of examples are real, a model that always predicts "real" gets 90% accuracy. Key metrics: precision, recall, F1 per class. Special attention to recall for fake classes: missing a fake is worse than a false alarm.

Target production metrics: Fake detection recall > 0.85, real precision > 0.90, F1 macro > 0.82. We monitor temporal stability: the model must maintain quality on new data, so we retrain monthly.

Approach comparison:

Model Accuracy (F1 macro) Processing speed Implementation complexity
TF-IDF + XGBoost 0.72 1000 requests/s Low
FinBERT (no fine-tuning) 0.78 100 requests/s Medium
FinBERT + ensemble (ours) 0.85 80 requests/s High

Our architecture delivers F1 18% higher than TF-IDF + XGBoost. The typical damage from a single successful fake tweet is estimated at $10,000–$500,000.

from sklearn.metrics import classification_report, roc_auc_score
import pandas as pd

def evaluate_model(y_true, y_pred, y_proba, class_names):
    report = classification_report(
        y_true, y_pred, 
        target_names=class_names,
        output_dict=True
    )
    df_report = pd.DataFrame(report).T
    fake_classes = [c for c in class_names if c != 'real']
    fake_f1_avg = df_report.loc[fake_classes, 'f1-score'].mean()
    print(f"Fake detection F1 (macro avg): {fake_f1_avg:.3f}")
    print(f"Real precision: {df_report.loc['real', 'precision']:.3f}")
    binary_labels = (y_true > 0).astype(int)
    binary_proba = 1 - y_proba[:, 0]
    auc = roc_auc_score(binary_labels, binary_proba)
    print(f"AUC-ROC (fake vs real): {auc:.3f}")
    return df_report

What is included in the work

  • Dataset collection and labeling (50,000+ examples) with automatic and manual verification
  • Fine-tuning FinBERT on your corpus (or our public one)
  • Pipeline development with on-chain verification via The Graph, DeFiLlama
  • Deployment in a Docker container with FastAPI, Kafka for streaming, concept drift monitoring
  • Alerting on fake detection with configurable thresholds
  • API documentation and user manual

Work process

  1. Analysis: we study your data sources, define target fake categories
  2. Design: choose the stack, design the dataset and verification pipeline
  3. Implementation: data collection, baseline and transformer model training, iterations
  4. Testing: evaluation on historical and fresh data, A/B testing
  5. Deployment: rollout in your infrastructure, integration with existing services

Estimated timeline

From 3 to 4 months to a production-ready system. Dataset collection and labeling — 6–8 weeks, model training — 4–6 weeks, deployment and monitoring — 3–4 weeks. Cost is calculated individually.

Our team has 8+ years of experience in NLP and blockchain, with 30+ completed content classification projects. Savings from preventing losses due to fake news can reach hundreds of thousands of dollars. Contact us for a project assessment — we will select the optimal solution. Request model development today.

How Do We Find What the Compiler Misses?

When a protocol loses $197M through a flash loan attack on a function that auditors reviewed live — it's not an accident. It's a systemic gap in methodology. Our experience shows: vulnerabilities live in a contract for over a year, while the compiler remains silent. We restructured the audit process to catch such cases before deployment.

What Static Analysis Won't Find?

Slither is the standard first tool. It finds reentrancy, integer overflow (in older Solidity versions), improper use of tx.origin, variable shadowing, uninitialized storage. On a real project, Slither produces dozens of warnings, of which critical ones are 0‑2. The rest is informational noise.

Slither won't find logical vulnerabilities. If withdraw correctly checks balance and correctly updates state, but business logic allows double deduction through two different code paths — Slither stays silent.

Mythril uses symbolic execution: builds a graph of all possible execution paths and searches for reachable states violating properties. Works well on isolated contracts. On a protocol of 20 contracts with cross‑contract calls — path explosion, analysis hangs or returns false positives.

Both tools are mandatory as a first pass. But they don't replace manual analysis.

Fuzzing: Where Echidna and Foundry Find Real Bugs

Echidna is a property‑based fuzzer from Trail of Bits. The idea: formulate contract invariants as Solidity functions (echidna_invariant), Echidna generates random call sequences and tries to break the invariant.

Example invariant for a lending protocol:

function echidna_total_assets_ge_liabilities() public view returns (bool) {
    return totalAssets() >= totalLiabilities();
}

Echidna will find a sequence deposit → borrow → liquidate → repay that violates this invariant. You can't build such a case manually — too many combinations.

Foundry fuzzing (forge test --fuzz-runs 100000) is easier to integrate if the team is already on Foundry. Supports stateful fuzzing via invariant tests. In a real project: auditing a vault contract, Foundry fuzzed for 40 minutes and found an edge case where maxWithdraw returned a value larger than actual balance at a specific shares/assets ratio after several donations. Hardhat unit tests missed it — they didn't have that combination of parameters.

Medusa (from Trail of Bits, newer than Echidna) supports corpus‑guided fuzzing and runs faster on large contracts. If the codebase exceeds 5000 lines of Solidity — we look at Medusa.

How Invariants Help Identify Critical Vulnerabilities

Formal verification proves that the contract satisfies specifications for all possible inputs — not for N random ones, but mathematically for all. Tools: Certora Prover, K Framework, Halmos.

Certora works with CVL (Certora Verification Language): write rules and invariants, the Prover translates them into SMT formulas and checks via Z3/CVC5. MakerDAO, Aave, Uniswap use Certora in CI/CD pipeline — every PR is automatically verified.

Limitations: doesn't work with unbounded loops, struggles with hash functions and signature verification. For contracts with simple math (AMM, lending) — excellent. For contracts with arbitrary external calls — difficult to write sufficiently complete specifications.

Formal verification makes sense for contracts that: manage over $50M, are rarely updated, have clearly formalizable invariants. For fast‑iterating products — the cost‑benefit ratio doesn't favor verification.

What Attack Vectors Do Junior Auditors Miss?

Storage collision in proxy pattern. Transparent proxy and UUPS use specific slots for implementation address (EIP‑1967). If an implementation accidentally declares a variable in slot 0 that overlaps with proxy storage — we get silent override. Slither won't catch this if proxy and implementation are in different files.

Read‑only reentrancy. Classic reentrancy guard protects against state changes during recursive calls. But if an external contract reads state via a view function mid‑transaction — guard doesn't help. Years ago, Curve pools became an attack vector precisely through this: an external protocol read get_virtual_price during a reentrancy‑vulnerable state of Curve.

Oracle manipulation via TWAP. Spot price is a standard target for flash loan attack. TWAP is harder to manipulate, but not impossible: on low‑liquidity Uniswap v2 pairs, TWAP can be shifted over several blocks with enough capital. Proper protection: use Chainlink as primary oracle with TWAP as fallback, with deviation threshold check.

Gas griefing on unbounded loop. A function iterates over an array of users. Attacker adds thousands of addresses with zero balances — the function's gas cost rises to the gas limit, making it inaccessible. Protection: pull pattern instead of push, limit array lengths, batch processing with position tracking.

Front‑running on MEV. Transaction is visible in mempool before inclusion in block. MEV bot sees addLiquidity for a significant amount, inserts its own swap before it (sandwich attack). For AMM this is part of the model. For protocols with price functions — require minAmountOut / deadline parameter and its mandatory verification.

Structure of a Full Audit

  1. Scope definition and automated analysis (1‑2 days). Fix commit hash, compiler version, list of out‑of‑scope items. Run Slither, Mythril, Aderyn. Triage: separate real critical bugs from false positives. Build contract dependency map.

  2. Manual analysis (5‑15 days). Each contract line by line. Special attention: all external and public functions, all transfer/call/delegatecall, all places where state changes before a check or after an external call, all math operations with user inputs. On average, 95% of found vulnerabilities are logical, not technical.

  3. Fuzzing and testing (2‑5 days). Echidna or Foundry invariant tests for critical invariants. Fork mainnet tests — verify behavior in real environment with real oracles. For example, in 4 days fuzzing finds on average 3 edge cases not covered by unit tests.

  4. Report and mitigation. Report with severity (Critical/High/Medium/Low/Informational), attack vector description, PoC code for Critical/High. Developers fix, auditors perform re‑audit of fixes.

Severity Examples Requires re‑audit?
Critical Drain funds, unauthorized ownership transfer Always
High Manipulation, DoS on key functions Always
Medium Incorrect behavior on edge cases Recommended
Low Gas inefficiency, typos in events Optional

Audit in CI/CD

Common practice for mature protocols: Slither and Aderyn run in GitHub Actions on every PR. Certora Prover — on merge to main. This doesn't replace a full audit before deployment, but catches regressions.

# .github/workflows/audit.yml
- name: Run Slither
  uses: crytic/[email protected]
  with:
    target: 'src/'
    slither-args: '--filter-paths "test|mock|script"'
Checklist of mandatory checks before deployment
  • All external functions have access controls (onlyOwner, onlyRole)
  • Use SafeERC20 for external tokens
  • No delegatecall to unknown addresses
  • Reentrancy check in all functions with external calls
  • Presence of minAmountOut and deadline in AMM functions
  • Use of a trusted oracle (Chainlink) with deviation threshold

Audit Tools Comparison

Tool Type of Analysis What It Finds Limitations
Slither Static Reentrancy, integer overflow, access control Misses logical vulnerabilities
Mythril Symbolic execution Reachable states violating properties Path explosion on large codebases
Echidna Fuzzing (property‑based) Invariant violations Requires writing invariants
Certora Formal verification Mathematical proof of properties Doesn't work with hashes/signatures

Deliverables

  • Full report in PDF with CVSS scores for each vulnerability
  • PoC code for all Critical and High (reproducible in test environment)
  • Remediation recommendations with code examples
  • Re‑audit after fixes (up to two iterations)
  • Brief guide for developers on ongoing operation
  • Post‑deployment support for 30 days (consultations and incident analysis)

Timeline

Audit of a simple token or NFT contract — 3‑5 business days. DeFi protocol with lending/AMM — 2‑4 weeks. Full stack with multiple protocols, cross‑chain, proxy upgrades — 4‑8 weeks. Re‑audit of fixes — 3‑7 days separately.

Our team has 7+ years of experience in smart contract security, having audited over 100 projects. We guarantee we won't miss any known attack vectors — we use licensed versions of Slither and best fuzzer configurations. Assess your project — we will analyze your code for free and provide a commercial offer within 2 days. Order an audit with quality guarantee and get a discount on re‑audit for repeat customers.