Розробка транзакційного декодера (human-readable)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка транзакційного декодера (human-readable)
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

Розробка декодувача транзакцій (human-readable)

Користувач підтверджує транзакцію в MetaMask, бачить поле data: 0xa9059cbb000000000000000000000000... — і натискає «Підтвердити», тому що довіряє dApp. Це проблема у всій Web3 UX. Нормальний декодувач показує: «Transfer 100 USDC to 0xABC...». Wallet Guard, Rabby Wallet, WalletConnect — вони всі вже вирішили це. Завдання: вбудувати аналогічну функціональність у ваш dApp або інструмент.

Анатомія transaction data

EVM calldata має фіксовану структуру:

0xa9059cbb                                    ← function selector (4 bytes)
000000000000000000000000742d35cc6634c0532925a3b8d2985e2b0e6d38e  ← address (32 bytes)
00000000000000000000000000000000000000000000152d02c7e14af6800000  ← uint256 (32 bytes)

Function selector — перші 4 байти keccak256 сигнатури функції. keccak256("transfer(address,uint256)")0xa9059cbb. База даних селекторів: 4byte.directory (7M+ сигнатур) — основа декодування невідомих контрактів.

Стратегії декодування

1. Декодування через ABI (відомий контракт)

Найточніший варіант. ABI точно описує типи та імена параметрів:

import { decodeFunctionData, parseAbi } from "viem"

const erc20Abi = parseAbi([
  "function transfer(address to, uint256 amount)",
  "function approve(address spender, uint256 amount)",
  "function transferFrom(address from, address to, uint256 amount)"
])

function decodeERC20Call(data: `0x${string}`) {
  try {
    const { functionName, args } = decodeFunctionData({ abi: erc20Abi, data })
    return { functionName, args }
  } catch {
    return null
  }
}

// Результат: { functionName: "transfer", args: ["0xAddress", 100000000n] }

2. Декодування через 4byte.directory API

Невідомий контракт, немає ABI — шукаємо за селектором:

async function lookupSelector(selector: string): Promise<string[]> {
  const response = await fetch(
    `https://www.4byte.directory/api/v1/signatures/?hex_signature=${selector}`
  )
  const data = await response.json()
  return data.results.map(r => r.text_signature)
  // ["transfer(address,uint256)", "transfer(address,uint256)"]
  // Може бути кілька збігів (колізії)
}

async function decodeUnknownCalldata(data: `0x${string}`) {
  const selector = data.slice(0, 10)  // "0x" + 8 hex chars
  const signatures = await lookupSelector(selector)

  for (const sig of signatures) {
    try {
      const abi = parseAbi([`function ${sig}`])
      const decoded = decodeFunctionData({ abi, data })
      return { signature: sig, args: decoded.args }
    } catch {
      continue  // спробуйте наступний варіант при колізії
    }
  }

  return null
}

3. Etherscan API для верифікованих контрактів

Etherscan зберігає ABI верифікованих контрактів:

async function fetchContractABI(contractAddress: string): Promise<any[] | null> {
  const url = `https://api.etherscan.io/api?module=contract&action=getabi` +
              `&address=${contractAddress}&apikey=${ETHERSCAN_KEY}`

  const response = await fetch(url)
  const data = await response.json()

  if (data.status !== "1") return null
  return JSON.parse(data.result)
}

Кешування обов'язково: ABI не змінюється, TTL можна встановити нескінченним.

4. Розв'язування proxy контракту

Багато контрактів використовують proxy паттерни (EIP-1967, EIP-1822, OpenZeppelin TransparentProxy). ABI proxy неінформативний — потрібно знайти реалізацію:

import { createPublicClient, http } from "viem"

async function resolveImplementation(proxyAddress: `0x${string}`): Promise<`0x${string}` | null> {
  const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) })

  // EIP-1967: implementation slot
  const EIP1967_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"

  const slotValue = await client.getStorageAt({
    address: proxyAddress,
    slot: EIP1967_SLOT
  })

  if (!slotValue || slotValue === "0x" + "0".repeat(64)) return null

  // Беріть останні 20 байт (адреса)
  return `0x${slotValue.slice(-40)}` as `0x${string}`
}

Для повного розв'язування перевірте кілька слотів (EIP-1967, EIP-1822, Gnosis Safe slot, користувацькі).

Human-readable форматування

Декодування само по собі недостатньо — відображайте зрозуміло. Параметри типу uint256 для ERC-20 потребують конвертування з урахуванням decimals:

interface HumanReadableParam {
  name: string
  type: string
  value: string  // завжди рядок для відображення
  rawValue: unknown
}

async function humanizeParam(
  name: string,
  type: string,
  value: unknown
): Promise<HumanReadableParam> {
  if (type === "address") {
    const address = value as string
    const ensName = await resolveENS(address)  // 0xABC... → vitalik.eth
    return {
      name, type,
      value: ensName || formatAddress(address),  // "0x1234...5678"
      rawValue: value
    }
  }

  if (type === "uint256" && name.toLowerCase().includes("amount")) {
    // Спробуйте отримати decimals, якщо адреса токена є в сусідніх параметрах
    return { name, type, value: formatBigInt(value as bigint), rawValue: value }
  }

  if (type === "bytes") {
    const bytes = value as string
    // Рекурсивно декодуйте, якщо це вкладений виклик
    const nested = await tryDecodeNested(bytes as `0x${string}`)
    return { name, type, value: nested || bytes, rawValue: value }
  }

  return { name, type, value: String(value), rawValue: value }
}

ENS розв'язування — ключова деталь. 0x742d35Cc...vitalik.eth кардинально змінює читаємість.

Декодування event logs

Логи транзакцій часто не менш важливі, ніж calldata:

import { decodeEventLog } from "viem"

async function decodeTransactionLogs(txHash: `0x${string}`) {
  const receipt = await client.getTransactionReceipt({ hash: txHash })

  const decodedLogs = await Promise.allSettled(
    receipt.logs.map(async (log) => {
      const abi = await fetchContractABI(log.address)
      if (!abi) return { raw: log, decoded: null }

      try {
        const decoded = decodeEventLog({ abi, data: log.data, topics: log.topics })
        return { raw: log, decoded }
      } catch {
        return { raw: log, decoded: null }
      }
    })
  )

  return decodedLogs
    .filter(r => r.status === "fulfilled")
    .map(r => (r as PromiseFulfilledResult<any>).value)
}

Візуалізація ланцюга викликів

Для складних транзакцій (aggregator routes, batch calls), візуалізуйте call trace. Alchemy та Tenderly надають API для отримання трейсів:

// Tenderly simulation + trace
const trace = await fetch("https://api.tenderly.co/api/v1/simulate", {
  method: "POST",
  headers: { "X-Access-Key": TENDERLY_KEY },
  body: JSON.stringify({
    network_id: "1",
    from: senderAddress,
    to: contractAddress,
    input: calldata,
    gas: 500000,
    save: false
  })
})

Результат — дерево викликів з декодованими функціями на кожному рівні. Використовуйте для відображення multi-hop свапів, flash loan операцій.

Компонент декодувача

function TransactionDecoder({ txData, contractAddress }: {
  txData: `0x${string}`
  contractAddress: `0x${string}`
}) {
  const { data, isLoading } = useDecodeTransaction(txData, contractAddress)

  if (isLoading) return <Skeleton />

  if (!data) return (
    <div className="font-mono text-sm text-muted">
      Unknown function: {txData.slice(0, 10)}
    </div>
  )

  return (
    <div className="space-y-2">
      <div className="font-semibold">{data.functionName}</div>
      {data.params.map(param => (
        <div key={param.name} className="flex gap-2 text-sm">
          <span className="text-muted">{param.name}:</span>
          <span className="font-mono">{param.value}</span>
        </div>
      ))}
    </div>
  )
}

Часові рамки розробки

День 1: Ядро декодування — viem decodeFunctionData, інтеграція 4byte.directory, Etherscan ABI fetcher з кешуванням.

День 2: Proxy розв'язування, ENS lookup, human-readable форматування параметрів.

День 3: Декодування event logs, базовий React компонент для відображення.

День 4-5: Call trace через Tenderly/Alchemy, відображення мультишагових транзакцій, edge cases (multicall, batch операції).

Базовий декодувач calldata + logs — 3 дні. Повнофункціональний інструмент із трейсами та багатим форматуванням — 4-5 днів.