Skip to content

Events

Every contract module DappQL generates ships typed event helpers. You can decode logs you already have, compute topic hashes, and parse receipts, all with the ABI embedded, no hand-rolled topic0 math.

Generated surface

For a contract with any events in its ABI, the generated module exports:

ts
import { Token } from './src/contracts'

Token.events            // typed object: { Transfer, Approval, ... }

Token.getEventTopic('Transfer')
// → '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'

Token.parseEvents('Transfer', logs)
// → ParsedEvent<'Transfer'>[], typed args

These are thin wrappers over viem's encodeEventTopics and parseEventLogs, but with the ABI baked in so you never pass it manually.

Typical flow: filter by topic, parse to typed args

ts
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'
import { Token } from './src/contracts'

const client = createPublicClient({ chain: base, transport: http() })

// Filter logs by topic, topic hash comes from the generated helper
const logs = await client.getLogs({
  address: Token.deployAddress,
  topics: [Token.getEventTopic('Transfer')],
  fromBlock: 44_000_000n,
  toBlock: 'latest',
})

// Decode into typed args
const parsed = Token.parseEvents('Transfer', logs)
for (const { event, parsed: [decoded] } of parsed) {
  console.log(decoded.eventName)        // 'Transfer'
  console.log(decoded.args.from, decoded.args.to, decoded.args.value)
  console.log(event.blockNumber)        // raw log data available too
}

Watching live

Wagmi's useWatchContractEvent is the React way to subscribe. DappQL doesn't wrap it, use wagmi directly for live event subscriptions:

tsx
import { useWatchContractEvent } from 'wagmi'
import { Token } from './src/contracts'

useWatchContractEvent({
  address: Token.deployAddress,
  abi: Token.abi,
  eventName: 'Transfer',
  onLogs: (logs) => {
    const parsed = Token.parseEvents('Transfer', logs)
    // ...
  },
})

Pass the generated Token.abi straight in, it's already typed.

Via @dappql/mcp: getEvents tool

For scripts, debugging, or agent workflows, @dappql/mcp exposes events via a tool:

json
{
  "tool": "getEvents",
  "arguments": {
    "contract": "Token",
    "event": "Transfer",
    "fromBlock": "44000000",
    "toBlock": "latest",
    "args": { "from": "0xabc..." },
    "limit": 100
  }
}

Returns decoded events with { blockNumber, txHash, logIndex, args }. No topic hashing, no eth_getLogs gymnastics. See Tools reference.

Decoding an unknown log

If you have a log but don't know which contract emitted it, @dappql/mcp's getTransaction tool auto-decodes every log in a receipt against every project contract's ABI, first match wins. Useful for "what did this tx do":

json
{
  "tool": "getTransaction",
  "arguments": { "hash": "0x..." }
}

Response includes logs: [{ decoded: { contract, eventName, args } | null, ... }].

Event arg types

Indexed args come from topics; non-indexed args come from the log's data field. parseEvents gives you the union correctly typed:

ts
// ABI: event Transfer(address indexed from, address indexed to, uint256 value)
decoded.args.from   // `0x${string}`, indexed
decoded.args.to     // `0x${string}`, indexed
decoded.args.value  // bigint, non-indexed

Gotchas

  • uint256 event args are bigint. Don't compare with Number().
  • Topic filtering only works for indexed args. args: { value: 1000n } on the MCP getEvents tool does nothing if value is non-indexed.
  • Large block ranges time out. RPCs cap eth_getLogs to ~10k block ranges. For bigger windows, chunk queries or use an indexer. The MCP tool returns truncated: true when total > limit.
  • Template contracts need an explicit address. Token.getEventTopic('Transfer') works because topic is ABI-only, but getLogs needs the address passed in.