Token Approval Scam Warning System Development

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
Token Approval Scam Warning System Development
Medium
~3-5 days
Frequently Asked Questions

Blockchain Development Services

Blockchain Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1348
  • 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

We develop an approval scam warning system that protects your assets before a transaction is signed. Token approval is one of the most dangerous operations in Web3 — a user gives a contract the right to spend their tokens without their future involvement. Approval scams are the leading cause of asset theft: according to Revoke.cash, losses have exceeded $2.8 billion. Our warning system detects suspicious approves 10 times faster than manual checks, analyzing pending requests and monitoring issued approvals using simulation and address databases. With over 5 years of experience in Web3 security, we build protection that prevents theft before it happens.

Why Approval Scams Are the Biggest Threat to Your Wallet

ERC-20 approve: token.approve(spender, amount) allows the spender to transfer up to amount tokens. ERC-721/ERC-1155 setApprovalForAll grants rights to all NFTs — the most dangerous form. Permit (EIP-2612) enables gasless approval signatures without on-chain transactions; an attacker can send permit() later, covertly. Our approval scam warning system analyzes transactions in 1-2 seconds with detection accuracy above 99%.

Which Types of Approvals Require Special Attention?

Type Rights Risk Example Protection
ERC-20 approve Up to amount tokens Medium Simulation + limit
setApprovalForAll All NFTs in collection High Mandatory review
Permit2 signature Unlimited via off-chain Critical Signature transaction monitoring
Infinite approval MaxUint256 High Notification of pending approve

How the Suspicious Approval Warning System Works

The system uses two layers: pre-transaction screening (analysis of pending transactions before signing) and post-approval monitoring (tracking issued approvals). The risk score is calculated by an algorithm considering the approval type, spender data, and contract age. If the score exceeds 70, the system blocks signing.

Analysis of Pending Transactions — Developing the Warning System

Simulation via Tenderly or Alchemy

Before signing, we simulate the transaction and analyze state changes. Code function:

interface ApprovalAnalysis {
  isSuspicious: boolean;
  riskScore: number;           // 0-100
  riskFactors: string[];
  simulatedStateChanges: StateChange[];
  spenderInfo: SpenderInfo;
}

interface SpenderInfo {
  address: string;
  isVerified: boolean;         // is in whitelist of known dApps
  isNewContract: boolean;      // contract < 30 days old
  hasSourceCode: boolean;      // verified on Etherscan
  blacklisted: boolean;        // in known scammers list
}

async function analyzeApproval(
  txParams: TransactionParams,
  chainId: number
): Promise<ApprovalAnalysis> {
  const riskFactors: string[] = [];
  let riskScore = 0;

  // 1. Simulate transaction
  const simulation = await simulateTransaction(txParams, chainId);

  // Extract approve calls from simulation
  const approvals = extractApprovals(simulation.stateChanges);

  for (const approval of approvals) {
    // 2. Check approval type
    if (approval.type === "setApprovalForAll") {
      riskFactors.push("SET_APPROVAL_FOR_ALL — grants rights to all NFT collections");
      riskScore += 40;
    }

    if (approval.amount === MaxUint256) {
      riskFactors.push("UNLIMITED_APPROVAL — unlimited token approval");
      riskScore += 20;
    }

    // 3. Analyze spender contract
    const spenderInfo = await analyzeSpender(approval.spender, chainId);

    if (spenderInfo.blacklisted) {
      riskFactors.push("KNOWN_SCAMMER — address in blacklist");
      riskScore += 60;
    }

    if (spenderInfo.isNewContract) {
      riskFactors.push("NEW_CONTRACT — contract deployed less than 30 days ago");
      riskScore += 25;
    }

    if (!spenderInfo.hasSourceCode) {
      riskFactors.push("UNVERIFIED_CONTRACT — source code not verified");
      riskScore += 15;
    }

    if (!spenderInfo.isVerified && !spenderInfo.hasSourceCode) {
      riskScore += 20; // extra penalty for complete opacity
    }
  }

  return {
    isSuspicious: riskScore >= 50,
    riskScore: Math.min(100, riskScore),
    riskFactors,
    simulatedStateChanges: simulation.stateChanges,
    spenderInfo: approvals[0]?.spender
      ? await analyzeSpender(approvals[0].spender, chainId)
      : null
  };
}

Database of Known Spender Addresses

// Whitelist of known dApp contracts
const KNOWN_SAFE_SPENDERS: Record<number, Set<string>> = {
  1: new Set([ // Ethereum mainnet
    "0x000000000022d473030f116ddee9f6b43ac78ba3", // Permit2 (Uniswap)
    "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", // Uniswap Universal Router
    "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", // Uniswap V2 Router
    "0xe592427a0aece92de3edee1f18e0157c05861564", // Uniswap V3 Router
    "0x1111111254eeb25477b68fb85ed929f73a960582", // 1inch V5
    "0x00000000219ab540356cbb839cbe05303d7705fa", // ETH2 Deposit Contract
  ]),
  // Arbitrum, Base, Polygon...
};

// Blacklist of known scam addresses
// Sources: Forta, Chainabuse, Revoke.cash, MobyMask
const KNOWN_SCAM_SPENDERS: Record<number, Set<string>> = {
  1: new Set([
    // Updated from Forta feeds and community reports
  ])
};

async function analyzeSpender(
  spenderAddress: string,
  chainId: number
): Promise<SpenderInfo> {
  const normalizedAddress = spenderAddress.toLowerCase();

  // Check whitelist
  const isVerified = KNOWN_SAFE_SPENDERS[chainId]?.has(normalizedAddress) ?? false;

  // Check blacklist
  const blacklisted = KNOWN_SCAM_SPENDERS[chainId]?.has(normalizedAddress) ?? false;

  // Check contract age
  const deployBlock = await getContractDeployBlock(spenderAddress, chainId);
  const currentBlock = await getCurrentBlock(chainId);
  const blockAge = currentBlock - deployBlock;
  const isNewContract = blockAge < 200_000; // ~30 days on Ethereum

  // Check verification on Etherscan
  const hasSourceCode = await checkEtherscanVerification(spenderAddress, chainId);

  return {
    address: spenderAddress,
    isVerified,
    blacklisted,
    isNewContract,
    hasSourceCode
  };
}

Monitoring Issued Approvals

Indexer of Approved Approvals

interface ApprovalRecord {
  owner: string;
  spender: string;
  tokenAddress: string;
  tokenType: "ERC20" | "ERC721" | "ERC1155";
  amount: bigint | "unlimited" | "all"; // for ERC-20 / setApprovalForAll
  blockNumber: number;
  txHash: string;
  timestamp: number;
  revoked: boolean;
}

// Listen for Approval and ApprovalForAll events
async function indexApprovals(
  provider: ethers.Provider,
  userAddress: string
): Promise<ApprovalRecord[]> {
  // ERC-20 Approval(owner, spender, value)
  const erc20ApprovalFilter = {
    topics: [
      ethers.id("Approval(address,address,uint256)"),
      ethers.zeroPadValue(userAddress, 32) // owner = userAddress
    ]
  };

  // ERC-721/1155 ApprovalForAll(owner, operator, approved)
  const approvalForAllFilter = {
    topics: [
      ethers.id("ApprovalForAll(address,address,bool)"),
      ethers.zeroPadValue(userAddress, 32)
    ]
  };

  const [erc20Logs, nftLogs] = await Promise.all([
    provider.getLogs({ ...erc20ApprovalFilter, fromBlock: 0, toBlock: "latest" }),
    provider.getLogs({ ...approvalForAllFilter, fromBlock: 0, toBlock: "latest" })
  ]);

  return parseApprovalLogs([...erc20Logs, ...nftLogs]);
}

Alert on Approval Usage

Note: when an issued approval is used, we must distinguish legitimate operations from anomalous ones. The algorithm triggers if the transfer is initiated by the spender (not the owner).

async function monitorApprovalUsage(
  approval: ApprovalRecord,
  provider: ethers.Provider
): Promise<void> {
  const transferFilter = {
    address: approval.tokenAddress,
    topics: [
      ethers.id("Transfer(address,address,uint256)"),
      ethers.zeroPadValue(approval.owner, 32)  // from = owner
    ]
  };

  // Subscribe to Transfer events from owner via spender
  provider.on(transferFilter, async (log) => {
    const tx = await provider.getTransaction(log.transactionHash);

    // Transaction sent by spender (not owner) — this is approval use
    if (tx.from.toLowerCase() === approval.spender.toLowerCase()) {
      const transferAmount = BigInt(log.data);

      await sendAlert({
        type: "APPROVAL_USED",
        severity: "HIGH",
        owner: approval.owner,
        spender: approval.spender,
        amount: transferAmount,
        txHash: log.transactionHash,
        message: `Your approval is being used! Contract ${approval.spender} ` +
                 `is transferring ${formatAmount(transferAmount)} tokens from your address.`
      });
    }
  });
}

Permit2 Specifics

Uniswap Permit2 requires a single approve(permit2, unlimited) for the Permit2 contract. Other protocols use it with signature-based permissions. The system recognizes this pattern:

const PERMIT2_TRANSFER_FROM_SELECTOR = "0x36c78516";

function isPermit2Transfer(calldata: string): boolean {
  return calldata.startsWith(PERMIT2_TRANSFER_FROM_SELECTOR);
}

function decodePermit2Transfer(calldata: string): {
  token: string;
  from: string;
  to: string;
  amount: bigint;
} {
  const iface = new ethers.Interface(PERMIT2_ABI);
  const decoded = iface.decodeFunctionData("transferFrom", calldata);
  return {
    token: decoded.token,
    from: decoded.from,
    to: decoded.to,
    amount: decoded.amount
  };
}

Integration into a Wallet or dApp

The system is embedded via wallet_watchAsset or a browser extension. MetaMask Snaps provides access to the onTransaction hook.

Channel Application Latency
Wallet provider hook Pre-sign screening in MetaMask Snaps < 1 sec
Browser extension Screening all dApp transactions < 2 sec
dApp UI integration SDK on dApp side < 1 sec
Email/Telegram alert Post-approval monitoring Real-time

Example: a user visits a phishing site that requests an approve for a contract deployed 2 hours ago, with unverified code and an amount of MaxUint256. The system scores the risk at 95 and blocks signing.

How Integration Protects Against Permit2 Attacks

Permit2 allows one signature to be used for an unlimited number of transfers. An attacker can obtain the signature via phishing and send permit() later. The system detects such signatures on pre-screening and tracks their usage.

How to Integrate the System: Step-by-Step Guide

  1. Install the MetaMask Snap or browser extension.
  2. Connect a simulation API (Tenderly/Alchemy) — screening will work instantly.
  3. Configure whitelist/blacklist — add known dApps and scam addresses.
  4. Start monitoring issued approvals via an event indexer.
  5. Set up alert channels (email, Telegram, webhook).
Safe Approve Checklist- Always check the spender address on Etherscan. - Limit the approve amount if possible. - Use our system for automatic pre-screening.

Savings from implementation: preventing a single attack can save from $10,000 to $500,000 of your assets. Our approval scam warning system offers 10 times faster detection than manual checks, ensuring you never miss a threat.

What Is Included as Deliverables?

  • Requirements analysis and threat modeling.
  • Development of smart contracts and monitoring scenarios.
  • Integration with wallets via Snaps and API.
  • Deployment of infrastructure (Tenderly, Alchemy).
  • Documentation and user training.
  • Technical support for 3 months.
  • Access to real-time dashboards and alert systems.

Timeline: 4 to 8 weeks. Cost starts at $15,000 for a basic integration, with higher tiers depending on complexity. Our experience in Web3 (30+ projects, 10+ wallet integrations) guarantees reliable protection against approval scams.

Order a consultation on integrating our system. Contact us to discuss your project. Get demo access to the system and evaluate its effectiveness.

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.