Skip to main content
Glama

decodeTx

Decode blockchain transactions into structured JSON with function names, parameters, event logs, and gas information for Ethereum and other EVM chains.

Instructions

트랜잭션을 구조화된 JSON으로 해석합니다 (함수명, 파라미터, 이벤트 로그, 가스 정보)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
txHashYes트랜잭션 해시 (0x...)
chainNo체인 (ethereum, polygon, arbitrum, base, optimism)ethereum

Implementation Reference

  • The main handler function that executes transaction decoding, including fetching transaction data, decoding function calls, and event logs.
    async function handler(args: z.infer<typeof inputSchema>): Promise<ToolResult<DecodeTxData>> {
      const { txHash, chain } = args;
    
      if (!isSupportedChain(chain)) {
        return makeError(`Unsupported chain: ${chain}`, "CHAIN_NOT_SUPPORTED");
      }
    
      if (!txHash.startsWith("0x") || txHash.length !== 66) {
        return makeError(`Invalid transaction hash: ${txHash}`, "INVALID_INPUT");
      }
    
      const cacheKey = `decodetx:${chain}:${txHash.toLowerCase()}`;
      const cached = cache.get<DecodeTxData>(cacheKey);
      if (cached.hit) return makeSuccess(chain as SupportedChain, cached.data, true);
    
      const client = getClient(chain as SupportedChain);
    
      try {
        const tx = await client.getTransaction({ hash: txHash as `0x${string}` });
    
        let status: "success" | "failed" | "pending" = "pending";
        let gasUsed: string | null = null;
        let gasPrice: string | null = null;
        let timestamp: number | null = null;
        let logs: Array<{ address: string; data: string; topics: string[] }> = [];
    
        if (tx.blockNumber) {
          try {
            const receipt = await client.getTransactionReceipt({ hash: txHash as `0x${string}` });
            status = receipt.status === "success" ? "success" : "failed";
            gasUsed = receipt.gasUsed.toString();
            gasPrice = formatGwei(receipt.effectiveGasPrice);
            logs = receipt.logs.map((l) => ({
              address: l.address,
              data: l.data,
              topics: l.topics as string[],
            }));
          } catch {
            status = "success";
          }
    
          try {
            const block = await client.getBlock({ blockNumber: tx.blockNumber });
            timestamp = Number(block.timestamp);
          } catch {
            // 무시
          }
        }
    
        // 함수 디코딩
        let decodedFunction: DecodedFunction | null = null;
        const inputData = tx.input;
    
        if (inputData && inputData !== "0x" && inputData.length >= 10) {
          const selector = inputData.slice(0, 10);
    
          // ABI가 있으면 정밀 디코딩
          if (tx.to) {
            const abiResult = await getABI(tx.to, chain as SupportedChain);
            if (abiResult) {
              try {
                const decoded = decodeFunctionData({
                  abi: abiResult.abi as Abi,
                  data: inputData as `0x${string}`,
                });
                decodedFunction = {
                  name: decoded.functionName,
                  signature: null,
                  // decoded.args는 readonly 배열일 수 있으므로 unknown으로 먼저 변환 후 직렬화
                  args: decoded.args ? Object.fromEntries(
                    Object.entries(decoded.args as unknown as Record<string, unknown>).map(([k, v]) => [k, serializeArg(v)])
                  ) : {},
                };
              } catch {
                // ABI 디코딩 실패 시 셀렉터 폴백
              }
            }
          }
    
          // ABI 디코딩 실패 시 셀렉터 기반 조회
          if (!decodedFunction) {
            const sigInfo = await resolveFunctionSelector(selector);
            if (sigInfo) {
              decodedFunction = {
                name: sigInfo.name,
                signature: sigInfo.signature,
                args: { _raw: inputData },
              };
            } else {
              decodedFunction = {
                name: `unknown(${selector})`,
                signature: null,
                args: { _raw: inputData },
              };
            }
          }
        }
    
        // 이벤트 로그 디코딩
        const decodedEvents: DecodedEvent[] = [];
        for (const log of logs) {
          // ABI가 있으면 디코딩 시도
          const logAbiResult = await getABI(log.address, chain as SupportedChain);
          if (logAbiResult) {
            try {
              const decoded = decodeEventLog({
                abi: logAbiResult.abi as Abi,
                data: log.data as `0x${string}`,
                topics: log.topics as [`0x${string}`, ...`0x${string}`[]],
              });
              decodedEvents.push({
                // eventName이 undefined일 수 있으므로 fallback 처리
                name: decoded.eventName ?? "unknown",
                address: log.address,
                // decoded.args는 readonly 배열일 수 있으므로 unknown으로 먼저 변환 후 직렬화
                args: decoded.args ? Object.fromEntries(
                  Object.entries(decoded.args as unknown as Record<string, unknown>).map(([k, v]) => [k, serializeArg(v)])
                ) : {},
              });
              continue;
            } catch {
              // 디코딩 실패 시 raw 로그
            }
          }
    
          decodedEvents.push({
            name: log.topics[0]?.slice(0, 10) ?? "unknown",
            address: log.address,
            args: { _topics: log.topics, _data: log.data },
          });
        }
    
        const data: DecodeTxData = {
          hash: tx.hash,
          from: tx.from,
          to: tx.to,
          value: formatEther(tx.value),
          status,
          function: decodedFunction,
          events: decodedEvents,
          gasUsed,
          gasPrice,
          blockNumber: tx.blockNumber ? Number(tx.blockNumber) : null,
          timestamp,
        };
    
        if (status !== "pending") {
          cache.set(cacheKey, data, DECODE_CACHE_TTL);
        }
    
        return makeSuccess(chain as SupportedChain, data, false);
      } catch (err) {
        const message = sanitizeError(err);
        if (message.includes("not found") || message.includes("could not be found")) {
          return makeError(`Transaction not found: ${txHash}`, "TX_NOT_FOUND");
        }
        return makeError(`Failed to decode transaction: ${message}`, "RPC_ERROR");
      }
    }
  • Input validation schema for the decodeTx tool.
    const inputSchema = z.object({
      txHash: z.string().describe("트랜잭션 해시 (0x...)"),
      chain: z.string().default("ethereum").describe("체인 (ethereum, polygon, arbitrum, base, optimism)"),
    });
  • Registration of the decodeTx tool in the MCP server.
    export function register(server: McpServer) {
      server.tool(
        "decodeTx",
        "트랜잭션을 구조화된 JSON으로 해석합니다 (함수명, 파라미터, 이벤트 로그, 가스 정보)",
        inputSchema.shape,
        async (args) => {
          const result = await handler(args as z.infer<typeof inputSchema>);
          return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
        },
      );
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/calintzy/evmscope'

If you have feedback or need assistance with the MCP directory API, please join our Discord server