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:
victim_protocol and victim_label.{
"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 | Type | Description / example values |
|---|---|---|
network | string | Source chain. One of mainnet, bsc, arbitrum, polygon, optimism, base, avax, hyperliquid. |
severity | string | null | Risk level. Typically one of "CRITICAL", "HIGH", "MEDIUM", "LOW". null when severity could not be computed. |
attack_type | string | The detection that fired. See Attack types. |
transaction_hash | string (0x + 64 hex) | The transaction that triggered the alert. |
exploit_address | string (0x + 40 hex) | The contract the transaction interacts with (the suspected exploit contract or target). |
block_number | int | Number of the block containing the transaction. |
block_timestamp | int | Unix epoch seconds, e.g. 1731609600. |
proc_time | string | UTC time at which the alert was generated, e.g. "2026-05-13 09:42:11.503127". |
attacker_address | string (0x + 40 hex) | EOA or contract that initiated the suspected attack. |
input | string | Raw transaction calldata, e.g. "0xa9059cbb0000...". "0x" for suspicious_contract_deployed, which is tied to a deployment rather than a call. |
balance_change | float | null | Net 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_traces | string | Comma-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_logs | string | Comma-separated identifiers of the logs that matched. Often "". |
matched_selectors | string | Comma-separated 4-byte selectors that matched, e.g. "0xa9059cbb,0x23b872dd". Often "". |
victim_address | string | null | The 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. |
protocols | object | null | Enrichment data; see Enriched data. Always null for suspicious_contract_deployed, because enrichment describes value that has moved, and none has. |
victim_protocol | string (protected-address variant only) | Human-readable name of the protocol that owns the matched protected address, e.g. "Aave V3". |
victim_label | string (protected-address variant only) | Label associated with the protected address, e.g. "Aave V3 Pool". |
For network-wide exploit monitoring (alerts not tied to a specific protected
address), attack_type is one of:
suspicious_contract_call_with_profithighly_complex_transaction_with_profitmev_tx_with_unusual_profitexploit_in_initcodeabnormal_token_mintingsuspicious_large_transferaccess_transfer_to_suspicious_addressaccess_transfer_to_suspicious_address_with_profitmalicious_proxy_upgradeproxy_upgrade_frontrun (CPIMP attack)suspicious_contract_deployed (early detection; see below)suspicious_governance_proposal (early detection; see below)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_deployed | suspicious_governance_proposal | |
|---|---|---|
| Fires on | Deployment of a contract that looks like an attack contract | Submission of a governance proposal whose payload looks malicious |
transaction_hash | The deployment transaction | The proposal transaction |
exploit_address | The newly deployed contract | The contract the proposal targets |
attacker_address | The deployer | The proposer |
victim_address | "" (no target identified yet) | The targeted protocol, when identifiable |
balance_change | 0 | 0 |
protocols | null | Enriched when the target can be resolved |
severity | HIGH when the deployer's funding trail is strongly suspicious or the bytecode looks like an exploit contract; otherwise MEDIUM | HIGH (preliminary) |
| Confirmed as | confirmed_exploit | confirmed_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.
matched_traces verdictsuspicious_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.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.
protocols)The protocols field carries enrichment data about the victim and the
addresses involved in the transaction. It takes one of three shapes:
nullNo enrichment is attached to this alert. The field is emitted as JSON null.
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"
}
}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| Field | Type | Example |
|---|---|---|
is_eoa | bool | false |
address | string | "0x3333...3333" |
name | string | null | "Aave V3", "Unverified contract", or null |
symbol | string | null | "aUSDC", "USDC", or null |
tvl | number | null | USD TVL when known; null otherwise. |
is_coingecko_pool | bool | true when the address is a liquidity pool tracked by CoinGecko. |
is_protocol | bool | true for known protocols. |
site_url | string | null | Protocol website, e.g. "https://aave.com". |
protocol_id | string | null | DeBank 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 fieldsEach 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):
| Field | Type | When present |
|---|---|---|
address | string | Always (may be "" for synthetic protocol-summary rows). |
balance_change_usd | number | Always. Negative means a loss, positive a gain. Synthetic protocol-summary rows use 1 as a placeholder. |
is_eoa | bool | Always. |
name | string | null | Set unless the address is an EOA. "Unverified contract" for unresolved contracts. |
symbol | string | null | Token symbol when known. |
tvl | number | null | Protocol or pool TVL when known. |
site_url | string | Known protocols only. |
protocol_id | string | DeBank 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_protocol | bool | Non-EOA entries. |
is_coingecko_pool | bool | Set to true only on CoinGecko pool matches. |
raw_token_balancesMaps 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_dataMaps "{network}:{token_address_lower}" to { price, decimals, symbol },
where price and decimals are strings.
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"
}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:
/ws/confirmed_attacks, with the same API-key authentication
as /ws/attacks. Both confirmation types below arrive here.Confirmed messages are published under one of two alert types, depending on what was confirmed:
confirmed_attack | confirmed_exploit | |
|---|---|---|
| Means | An exploit has executed and funds have moved | An attack contract has been confirmed on-chain, before it is used |
| Produced by | All other LLM-reviewed types | suspicious_contract_deployed |
| WebSocket | /ws/confirmed_attacks | /ws/confirmed_attacks |
| Defimon Signals (Telegram) | Yes | No, 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.
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:
| Field | Type | Description |
|---|---|---|
llm_explanation | string | LLM-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_protocol | string | null | Human-readable protocol name, or null. |
victim_label | string | null | Label 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.
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