docs

Attack Alert Messages (WebSocket)

View raw Markdown

This document describes the JSON structure of the WebSocket messages Defimon sends when it detects an on-chain attack.

Messages are delivered on two feeds:

  • /ws/attacks: every detected attack, delivered immediately. Most of this document describes this feed.
  • /ws/confirmed_attacks: a curated, lower-noise stream of detections that an LLM pipeline has reviewed and confirmed. It carries two kinds of confirmation: exploits that have already drained funds, and early-detection findings confirmed before anything is stolen. See Confirmed feed.

A client can subscribe to both feeds at the same time.

Each message is a single JSON object. The payload comes in two variants:

  • Standard attack: the attack does not touch a known protected address.
  • Protected-address attack: the attack touches an address the recipient has registered for protection. The payload carries two extra fields, victim_protocol and victim_label.

Top-level schema

{ "network": "mainnet", "severity": "HIGH", "attack_type": "suspicious_contract_call_with_profit", "transaction_hash": "0x9c8b6f3b6f6a1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b", "exploit_address": "0x1111111111111111111111111111111111111111", "block_number": 21345678, "block_timestamp": 1731609600, "proc_time": "2026-05-13 09:42:11.503127", "attacker_address": "0x2222222222222222222222222222222222222222", "input": "0xa9059cbb000000000000000000000000abcdef...", "balance_change": 248173.42, "matched_traces": "", "matched_logs": "", "matched_selectors": "", "victim_address": "0x3333333333333333333333333333333333333333", "protocols": { "...see Enriched data below..." }, "victim_protocol": "Aave V3", "victim_label": "Aave V3 Pool" }

The last two fields (victim_protocol, victim_label) appear only in the protected-address variant. For a standard attack, they are absent from the JSON.

Field reference

FieldTypeDescription / example values
networkstringSource chain. One of mainnet, bsc, arbitrum, polygon, optimism, base, avax, hyperliquid.
severitystring | nullRisk level. Typically one of "CRITICAL", "HIGH", "MEDIUM", "LOW". null when severity could not be computed.
attack_typestringThe detection that fired. See Attack types.
transaction_hashstring (0x + 64 hex)The transaction that triggered the alert.
exploit_addressstring (0x + 40 hex)The contract the transaction interacts with (the suspected exploit contract or target).
block_numberintNumber of the block containing the transaction.
block_timestampintUnix epoch seconds, e.g. 1731609600.
proc_timestringUTC time at which the alert was generated, e.g. "2026-05-13 09:42:11.503127".
attacker_addressstring (0x + 40 hex)EOA or contract that initiated the suspected attack.
inputstringRaw transaction calldata, e.g. "0xa9059cbb0000...". "0x" for suspicious_contract_deployed, which is tied to a deployment rather than a call.
balance_changefloat | nullNet change in the attacker's USD balance from this transaction, e.g. 248173.42. Can be 0.0 or negative; null when not computed. Always 0 for the early-detection types, since nothing has moved yet.
matched_tracesstringComma-separated identifiers of the traces that matched the detection rules. Often "". For suspicious_contract_deployed, this field instead carries a human-readable reason the contract was flagged; see Reading the matched_traces verdict.
matched_logsstringComma-separated identifiers of the logs that matched. Often "".
matched_selectorsstringComma-separated 4-byte selectors that matched, e.g. "0xa9059cbb,0x23b872dd". Often "".
victim_addressstring | nullThe address with the largest USD loss. null for attack types without a single identifiable victim, and "" for suspicious_contract_deployed, where no target has been identified yet.
protocolsobject | nullEnrichment data; see Enriched data. Always null for suspicious_contract_deployed, because enrichment describes value that has moved, and none has.
victim_protocolstring (protected-address variant only)Human-readable name of the protocol that owns the matched protected address, e.g. "Aave V3".
victim_labelstring (protected-address variant only)Label associated with the protected address, e.g. "Aave V3 Pool".

Attack types

For network-wide exploit monitoring (alerts not tied to a specific protected address), attack_type is one of:

  • suspicious_contract_call_with_profit
  • highly_complex_transaction_with_profit
  • mev_tx_with_unusual_profit
  • exploit_in_initcode
  • abnormal_token_minting
  • suspicious_large_transfer
  • access_transfer_to_suspicious_address
  • access_transfer_to_suspicious_address_with_profit
  • malicious_proxy_upgrade
  • proxy_upgrade_frontrun (CPIMP attack)
  • suspicious_contract_deployed (early detection; see below)
  • suspicious_governance_proposal (early detection; see below)

Early detection (pre-drain) types

Two of the types above fire before an attack executes. Nothing has been stolen when they arrive, so they behave differently from every other type and should be handled as a separate class:

suspicious_contract_deployedsuspicious_governance_proposal
Fires onDeployment of a contract that looks like an attack contractSubmission of a governance proposal whose payload looks malicious
transaction_hashThe deployment transactionThe proposal transaction
exploit_addressThe newly deployed contractThe contract the proposal targets
attacker_addressThe deployerThe proposer
victim_address"" (no target identified yet)The targeted protocol, when identifiable
balance_change00
protocolsnullEnriched when the target can be resolved
severityHIGH when the deployer's funding trail is strongly suspicious or the bytecode looks like an exploit contract; otherwise MEDIUMHIGH (preliminary)
Confirmed asconfirmed_exploitconfirmed_attack

Both types are high-volume and noisy by design. suspicious_contract_deployed alone produces roughly 70 alerts a day on Ethereum, because it is tuned for recall rather than precision: the goal is to spot an attacker's contract as soon as it is deployed, while there is still time to act. Treat the raw alert as something to watch and the confirmation as the signal to act on; see Confirmed feed.

Reading the matched_traces verdict

suspicious_contract_deployed has no selectors or traces worth reporting, so it uses matched_traces to explain why it fired, as key/value pairs separated by ·:

tier=tornado_direct · funding=tornado/direct on mainnet · deployer nonce=0 · many external calls · hardcoded targets
  • tier: which detection tier matched. One of tornado_direct, tornado_1hop, tornado_2hop, railgun, operator_scored, unfunded_tight.
  • funding: where the deployer's gas came from, as family/hop. It may also include the chain the withdrawal happened on (which need not be the chain the contract was deployed to) and the intermediate funder's fan-out (how many addresses it funded). unfunded when tracing the funding backwards found no anonymizing source.
  • deployer nonce: the deploying account's nonce, or ? when unknown. A fresh account is a much stronger signal than an established one.
  • Everything after those three is bytecode evidence, most incriminating first. It is present only for contracts whose bytecode could be analyzed.

The string is meant for human readers, and new fields may be added. Parse it leniently (split on ·, then on the first =) or treat it as opaque text.

Enriched data (protocols)

The protocols field carries enrichment data about the victim and the addresses involved in the transaction. It takes one of three shapes:

1. null

No enrichment is attached to this alert. The field is emitted as JSON null.

2. Lightweight enrichment

Returned when only victim information is available:

{ "is_full_enrichment": false, "victim_info": { "is_eoa": false, "address": "0x3333333333333333333333333333333333333333", "name": "Aave V3", "symbol": "aUSDC", "tvl": 4123456789.12, "is_coingecko_pool": false, "is_protocol": true, "site_url": "https://aave.com", "protocol_id": "aave3" } }

3. Full enrichment

Returned for high-impact alerts. In addition to victim_info, it contains every address-level balance change in the transaction:

{ "is_full_enrichment": true, "hacker_profit": 248173.42, "victim_info": { "is_eoa": false, "address": "0x3333333333333333333333333333333333333333", "name": "Aave V3", "symbol": "aUSDC", "tvl": 4123456789.12, "is_coingecko_pool": false, "is_protocol": true, "site_url": "https://aave.com", "protocol_id": "aave3" }, "balance_changes": [ { "address": "0x2222222222222222222222222222222222222222", "balance_change_usd": 248173.42, "is_eoa": true }, { "address": "0x3333333333333333333333333333333333333333", "balance_change_usd": -248173.42, "is_eoa": false, "name": "Aave V3", "symbol": "aUSDC", "tvl": 4123456789.12, "site_url": "https://aave.com", "protocol_id": "aave3", "is_protocol": true }, { "address": "0x4444444444444444444444444444444444444444", "balance_change_usd": -1532.10, "is_eoa": false, "name": "USDC/WETH 0.05%", "symbol": "USDC", "tvl": 123456789.0, "is_coingecko_pool": true, "is_protocol": false }, { "address": "0x5555555555555555555555555555555555555555", "balance_change_usd": -42.0, "is_eoa": false, "name": "Unverified contract", "symbol": null, "tvl": null, "is_protocol": false }, { "address": "", "balance_change_usd": 1, "is_eoa": false, "name": "Uniswap V3", "symbol": null, "tvl": 5000000000.0, "site_url": "https://uniswap.org", "is_protocol": true } ], "raw_token_balances": { "0x2222222222222222222222222222222222222222": { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "248173420000" }, "0x3333333333333333333333333333333333333333": { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "-248173420000" } }, "token_prices": { "token_data": { "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { "price": "1.00", "decimals": "6", "symbol": "USDC" }, "ethereum:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { "price": "3450.12", "decimals": "18", "symbol": "WETH" } } } }
victim_info fields
FieldTypeExample
is_eoaboolfalse
addressstring"0x3333...3333"
namestring | null"Aave V3", "Unverified contract", or null
symbolstring | null"aUSDC", "USDC", or null
tvlnumber | nullUSD TVL when known; null otherwise.
is_coingecko_poolbooltrue when the address is a liquidity pool tracked by CoinGecko.
is_protocolbooltrue for known protocols.
site_urlstring | nullProtocol website, e.g. "https://aave.com".
protocol_idstring | nullDeBank protocol id: a string slug, not a number. Unprefixed on Ethereum (e.g. "aave3"), prefixed with the DeBank chain id elsewhere (e.g. "bsc_venus", "matic_aave3"). null when the victim is not a known DeBank protocol. On lightweight enrichment the key may be absent rather than null, so read it with a safe accessor.
balance_changes[] entry fields

Each entry always includes address, balance_change_usd, and is_eoa. The remaining fields depend on how the address was classified (known protocol, CoinGecko pool, verified contract, or unresolved):

FieldTypeWhen present
addressstringAlways (may be "" for synthetic protocol-summary rows).
balance_change_usdnumberAlways. Negative means a loss, positive a gain. Synthetic protocol-summary rows use 1 as a placeholder.
is_eoaboolAlways.
namestring | nullSet unless the address is an EOA. "Unverified contract" for unresolved contracts.
symbolstring | nullToken symbol when known.
tvlnumber | nullProtocol or pool TVL when known.
site_urlstringKnown protocols only.
protocol_idstringDeBank protocol matches only, e.g. "aave3". Same slug format as victim_info.protocol_id. Omitted for CoinGecko pools, Etherscan-named contracts, unresolved contracts, and the synthetic protocol-summary rows (address: ""); those rows carry name, tvl and site_url but no id.
is_protocolboolNon-EOA entries.
is_coingecko_poolboolSet to true only on CoinGecko pool matches.
raw_token_balances

Maps owner_address → { token_address: signed_amount_as_string }. Amounts are raw on-chain integers in the token's smallest unit (not adjusted for decimals) and can be negative.

token_prices.token_data

Maps "{network}:{token_address_lower}" to { price, decimals, symbol }, where price and decimals are strings.

End-to-end example

A WebSocket payload for an attack on a protected address, with full enrichment:

{ "network": "mainnet", "severity": "HIGH", "attack_type": "suspicious_contract_call_with_profit", "transaction_hash": "0x9c8b6f3b6f6a1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b", "exploit_address": "0x1111111111111111111111111111111111111111", "block_number": 21345678, "block_timestamp": 1731609600, "proc_time": "2026-05-13 09:42:11.503127", "attacker_address": "0x2222222222222222222222222222222222222222", "input": "0xa9059cbb0000000000000000000000003333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000000003b9aca00", "balance_change": 248173.42, "matched_traces": "", "matched_logs": "", "matched_selectors": "0xa9059cbb", "victim_address": "0x3333333333333333333333333333333333333333", "protocols": { "is_full_enrichment": true, "hacker_profit": 248173.42, "victim_info": { "is_eoa": false, "address": "0x3333333333333333333333333333333333333333", "name": "Aave V3", "symbol": "aUSDC", "tvl": 4123456789.12, "is_coingecko_pool": false, "is_protocol": true, "site_url": "https://aave.com", "protocol_id": "aave3" }, "balance_changes": [ { "address": "0x2222222222222222222222222222222222222222", "balance_change_usd": 248173.42, "is_eoa": true }, { "address": "0x3333333333333333333333333333333333333333", "balance_change_usd": -248173.42, "is_eoa": false, "name": "Aave V3", "symbol": "aUSDC", "tvl": 4123456789.12, "site_url": "https://aave.com", "protocol_id": "aave3", "is_protocol": true } ], "raw_token_balances": { "0x2222222222222222222222222222222222222222": { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "248173420000" }, "0x3333333333333333333333333333333333333333": { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "-248173420000" } }, "token_prices": { "token_data": { "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { "price": "1.00", "decimals": "6", "symbol": "USDC" } } } }, "victim_protocol": "Aave V3", "victim_label": "Aave V3 Pool" }

Confirmed feed

Detections of certain types also go through an LLM review. When the review confirms that a finding is genuine, rather than a false positive such as legitimate MEV, arbitrage or normal protocol activity, the service rebroadcasts it as a confirmed message:

  • Endpoint: /ws/confirmed_attacks, with the same API-key authentication as /ws/attacks. Both confirmation types below arrive here.
  • Timing: a confirmation arrives after the regular alert, because the LLM review takes anywhere from a few seconds to a couple of minutes. Not every detection is confirmed: false positives are dropped.

Two confirmation types

Confirmed messages are published under one of two alert types, depending on what was confirmed:

confirmed_attackconfirmed_exploit
MeansAn exploit has executed and funds have movedAn attack contract has been confirmed on-chain, before it is used
Produced byAll other LLM-reviewed typessuspicious_contract_deployed
WebSocket/ws/confirmed_attacks/ws/confirmed_attacks
Defimon Signals (Telegram)YesNo, WebSocket only

confirmed_exploit is the earliest actionable signal Defimon produces: an attacker's contract, reviewed and confirmed as an attack tool, before the drain it was built for has happened.

Both types are delivered over WebSocket, so a /ws/confirmed_attacks client receives both by default. The payload has no alert_type field; use the underlying detection type to tell them apart:

const isEarlyDetection = msg.attack_type === "suspicious_contract_deployed";

If your subscription restricts alert types (the alerts setting), it must list confirmed_exploit explicitly to receive it. A subscription with alerts unset receives every alert type, including both of these.

Payload

The confirmed payload, for either type, is a strict superset of the standard attack payload. Every field documented above is present with the same name and type, so existing /ws/attacks clients can parse a confirmed message with their current parser and ignore the extra fields. The confirmed payload adds:

FieldTypeDescription
llm_explanationstringLLM-generated report, formatted for Telegram, describing the exploit: affected protocol, loss amount, token and price, vulnerability type, a short description, and links to the transaction and victim. Always present.
victim_protocolstring | nullHuman-readable protocol name, or null.
victim_labelstring | nullLabel of the protected address, or null.

A confirmed early-detection message keeps its underlying type's placeholder values: balance_change is 0, protocols is null, victim_address is "", and matched_traces carries the tier verdict. The useful information is in llm_explanation, which describes what the contract is built to do and which target it appears to be aimed at.

This differs from the /ws/attacks feed. There, victim_protocol and victim_label are omitted for standard attacks and appear only in the protected-address variant. On /ws/confirmed_attacks they are always present and default to null when the attack does not touch a protected address, so every confirmed message has the same shape. To detect a confirmed attack on a protected address, check victim_protocol != null rather than whether the key exists.

The attack_type field keeps the underlying detection type (e.g. suspicious_contract_call_with_profit, or suspicious_contract_deployed for an early-detection confirmation). confirmed_attack and confirmed_exploit are used only for subscription routing and never appear as attack_type values.

Put Defimon in your stack

Defimon detects exploits on major chains before they execute and streams them to you: human-readable alerts in Telegram, or structured JSON over WebSocket for your own systems.

websocket $200 / mo · signals $50 / mo · channel free