Xây dựng dapp trên cardano từ con số không
About Lesson

Query Data Onchain

Bài viết này hướng dẫn cách truy vấn dữ liệu on-chain từ blockchain Cardano bằng cách sử dụng dịch vụ Blockfrost, một nhà cung cấp API phổ biến để tương tác với blockchain. Nội dung được thiết kế cho các nhà phát triển xây dựng ứng dụng phi tập trung (dApps) trên Cardano, đặc biệt là trong môi trường backend sử dụng Node.js. Bằng cách làm theo hướng dẫn này, bạn sẽ hiểu cách thiết lập Blockfrost, truy vấn dữ liệu từ blockchain, và tối ưu hóa hiệu suất ứng dụng.

Yêu Cầu Chuẩn Bị

Trước khi bắt đầu, hãy đảm bảo bạn có:

  • Node.js và npm: Cài đặt Node.js (khuyến nghị phiên bản 16 trở lên) và npm để quản lý phụ thuộc JavaScript.
  • Trình Soạn Thảo Mã: Sử dụng trình soạn thảo như Visual Studio Code.
  • Tài Khoản Blockfrost: Đăng ký tài khoản tại Blockfrost Dashboard để nhận Project ID.
  • Ví Cardano: Thiết lập ví Cardano (như Eternl) với một lượng test ADA (tADA) trên mạng thử nghiệm Cardano testnet để kiểm tra các giao dịch.
  • Kiến Thức Cơ Bản: Hiểu biết về JavaScript, API REST, và các khái niệm blockchain.

Tổng Quan Về Truy Vấn Dữ Liệu On-Chain

Truy vấn dữ liệu on-chain là quá trình lấy thông tin từ sổ cái blockchain Cardano (như số dư ví, giao dịch, hoặc tài sản) để phục vụ các mục đích ứng dụng, chẳng hạn như hiển thị số dư, theo dõi lịch sử giao dịch, hoặc phân tích hiệu suất staking.

Cardano cung cấp nhiều công cụ để truy vấn dữ liệu, bao gồm:

  • Blockfrost: Một dịch vụ API bên thứ ba cung cấp các endpoint để truy vấn dữ liệu blockchain và gửi giao dịch.
  • Koios: Một giải pháp tương tự Blockfrost, cung cấp các endpoint để truy vấn trực tiếp từ blockchain.
  • MeshJS Provider: Một phần của thư viện MeshJS, tích hợp với các nhà cung cấp như Blockfrost hoặc Koios để truy vấn dữ liệu.

Trong bài viết này, chúng ta sẽ tập trung vào Blockfrost vì tính dễ sử dụng và tài liệu chi tiết.

Tại Sao Nên Sử Dụng Blockfrost?

Sử dụng dịch vụ bên thứ ba như Blockfrost có những lợi ích sau:

  1. Dễ Tích Hợp: Blockfrost cung cấp API REST với các endpoint dễ sử dụng, cho phép lập trình viên gửi yêu cầu HTTP để lấy dữ liệu hoặc gửi giao dịch mà không cần chạy node Cardano.
  2. Hiệu Suất Cao: Không cần đồng bộ hóa toàn bộ blockchain, giúp tiết kiệm tài nguyên máy tính.
  3. Gói Miễn Phí: Blockfrost cung cấp gói Starter miễn phí với 15 triệu request mỗi tháng, đủ cho các dự án thử nghiệm.

Nhược điểm:

  • Chi Phí: Nếu vượt quá giới hạn request miễn phí, bạn cần nâng cấp lên gói trả phí.
  • Phụ Thuộc Bên Thứ Ba: Dựa vào Blockfrost thay vì tự chạy node có thể giới hạn tính linh hoạt.

So sánh với việc tự chạy node Cardano:

  • Ưu điểm của chạy node: Kiểm soát hoàn toàn dữ liệu và không phụ thuộc vào bên thứ ba.
  • Nhược điểm: Yêu cầu máy tính mạnh để đồng bộ blockchain và cần xây dựng công cụ truy vấn riêng.

Thiết Lập Blockfrost

Bước 1: Tạo Tài Khoản và Project ID

  1. Truy cập Blockfrost Dashboard và đăng ký tài khoản.
  2. Tạo một dự án mới:
    • Chọn mạng Preprod (mạng thử nghiệm Cardano).
    • Đặt tên dự án, ví dụ: PreprodTest.
    • Sau khi tạo, bạn sẽ nhận được Project ID (bắt đầu bằng preprod và kèm theo một chuỗi ký tự). Lưu lại Project ID này để sử dụng trong các yêu cầu API.

Bước 2: Khám Phá Các Endpoint của Blockfrost

Blockfrost cung cấp nhiều endpoint để truy vấn dữ liệu. Xem tài liệu chi tiết tại Blockfrost API Documentation. Một số module chính bao gồm:

  1. Accounts: Truy vấn thông tin liên quan đến tài khoản staking, như số dư, phần thưởng staking, hoặc lịch sử giao dịch staking. Ví dụ:

    • /accounts/{stake_address}/rewards: Lấy lịch sử phần thưởng staking.
    • Ứng dụng: Theo dõi hiệu suất staking hoặc phân tích tài khoản.
  2. Addresses: Truy vấn chi tiết về địa chỉ ví, như danh sách UTXO (unspent transaction outputs) hoặc lịch sử giao dịch. Ví dụ:

    • /addresses/{address}/transactions: Lấy danh sách giao dịch của một địa chỉ.
    • Ứng dụng: Hiển thị lịch sử giao dịch hoặc kiểm tra số dư.
  3. Assets: Truy vấn thông tin về tài sản (token, NFT) trên Cardano. Ví dụ:

    • /assets/{asset}/transactions: Lấy giao dịch liên quan đến một tài sản.
    • /assets/{asset}/addresses: Lấy danh sách địa chỉ sở hữu tài sản.
  4. Transactions: Truy vấn chi tiết giao dịch hoặc gửi giao dịch mới. Ví dụ:

    • /txs/{hash}: Lấy thông tin giao dịch theo hash.
    • /tx/submit: Gửi giao dịch mới lên blockchain (phương thức POST).
  5. Khác: Các module như blockspoolsmetadata, và scripts cung cấp thông tin về block, stake pool, metadata, hoặc hợp đồng thông minh.

Bước 3: Tích Hợp Blockfrost Vào Backend

Để đảm bảo bảo mật và hiệu suất, nên thực hiện các yêu cầu API từ backend thay vì frontend. Lý do:

  • Bảo mật: Project ID cần được giữ bí mật. Nếu gọi API từ frontend, người dùng có thể truy cập Project ID và sử dụng trái phép, đặc biệt với gói trả phí.
  • Kiểm Soát Request: Backend cho phép giới hạn và quản lý số lượng request, tránh vượt quá giới hạn miễn phí (15 triệu request/tháng với gói Starter).
  • Tối Ưu Hiệu Suất: Backend có thể lưu trữ dữ liệu vào cơ sở dữ liệu (database) để giảm số lần gọi API trực tiếp đến Blockfrost, cải thiện tốc độ phản hồi.

Nhược điểm khi lưu trữ dữ liệu:

  • Dữ liệu trong database có thể bị trễ (delay) so với blockchain nếu không được đồng bộ thường xuyên.

Ví Dụ: Truy Vấn Lịch Sử Giao Dịch Của Một Địa Chỉ

Dưới đây là cách tích hợp Blockfrost vào một dự án Node.js với backend sử dụng Next.js API Routes.

  1. Tạo API Route:
    • Trong thư mục app/api/cardano/[address]/route.js, tạo một dynamic route để truy vấn lịch sử giao dịch của một địa chỉ ví:
import { NextResponse } from 'next/server';

export async function GET(request, { params }) {
  const { address } = params;
  const projectId = 'preprodYourProjectIdHere'; // Thay bằng Project ID của bạn
  const url = `https://cardano-preprod.blockfrost.io/api/v0/addresses/${address}/transactions`;

  try {
    const response = await fetch(url, {
      headers: {
        project_id: projectId,
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    return NextResponse.json(data);
  } catch (error) {
    console.error('Error fetching transactions:', error);
    return NextResponse.json({ error: 'Failed to fetch transactions' }, { status: 500 });
  }
}
 
  1. Giải Thích Mã:

    • Dynamic Route[address] cho phép truyền địa chỉ ví qua URL (ví dụ: /api/cardano/addr_test1...).
    • Project ID: Đặt trong headers của yêu cầu HTTP để xác thực với Blockfrost.
    • Endpoint/addresses/{address}/transactions trả về danh sách hash giao dịch liên quan đến địa chỉ ví.
    • Xử Lý Lỗi: Kiểm tra trạng thái phản hồi để xử lý lỗi từ API.
  2. Chạy Dự Án:

    • Chạy dự án Next.js bằng lệnh:
      npm run dev
       
    • Truy cập URL, ví dụ:
      http://localhost:3000/api/cardano/addr_test1...
      
       

      Thay addr_test1... bằng địa chỉ ví thực tế của bạn (có thể lấy từ ví Eternl).

    • Kết quả trả về sẽ là danh sách hash giao dịch, ví dụ:
      [
        { "tx_hash": "hash1" },
        { "tx_hash": "hash2" }
      ]
       
  3. Kiểm Tra Giao Dịch:

Bước 4: Tối Ưu Hóa Với Database

Để giảm số lần gọi API và tăng tốc độ, bạn có thể lưu trữ dữ liệu từ Blockfrost vào một database (như MongoDB hoặc PostgreSQL) và chỉ đồng bộ định kỳ.

  1. Lưu Dữ Liệu Vào Database:
    • Khi nhận dữ liệu từ Blockfrost, lưu vào database với thời gian cập nhật.
    • Ví dụ (sử dụng MongoDB):
import { MongoClient } from 'mongodb';

async function saveToDatabase(address, transactions) {
  const client = new MongoClient('mongodb://localhost:27017');
  try {
    await client.connect();
    const db = client.db('cardano');
    const collection = db.collection('transactions');
    await collection.updateOne(
      { address },
      { $set: { transactions, updatedAt: new Date() } },
      { upsert: true }
    );
  } finally {
    await client.close();
  }
}
 
  1. Truy Vấn Từ Database:
    • Khi frontend gửi yêu cầu, kiểm tra database trước:
      • Nếu dữ liệu còn mới (ví dụ: dưới 5 phút), trả về từ database.
      • Nếu dữ liệu cũ, gọi lại Blockfrost và cập nhật database.
import { NextResponse } from 'next/server';
import { MongoClient } from 'mongodb';

export async function GET(request, { params }) {
  const { address } = params;
  const projectId = 'preprodYourProjectIdHere';
  const url = `https://cardano-preprod.blockfrost.io/api/v0/addresses/${address}/transactions`;

  // Kiểm tra database trước
  const client = new MongoClient('mongodb://localhost:27017');
  try {
    await client.connect();
    const db = client.db('cardano');
    const collection = db.collection('transactions');
    const cached = await collection.findOne({ address });

    // Nếu dữ liệu còn mới (dưới 5 phút)
    if (cached && new Date() - new Date(cached.updatedAt) < 5 * 60 * 1000) {
      return NextResponse.json(cached.transactions);
    }

    // Gọi Blockfrost nếu dữ liệu cũ hoặc không có
    const response = await fetch(url, {
      headers: { project_id: projectId },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    // Lưu vào database
    await collection.updateOne(
      { address },
      { $set: { transactions: data, updatedAt: new Date() } },
      { upsert: true }
    );

    return NextResponse.json(data);
  } catch (error) {
    console.error('Error fetching transactions:', error);
    return NextResponse.json({ error: 'Failed to fetch transactions' }, { status: 500 });
  } finally {
    await client.close();
  }
}
 

Lợi ích:

  • Giảm số lần gọi API đến Blockfrost, tiết kiệm request.
  • Tăng tốc độ phản hồi vì truy vấn database thường nhanh hơn gọi API.
  • Cải thiện trải nghiệm người dùng.

Nhược điểm:

  • Dữ liệu có thể bị trễ nếu không đồng bộ thường xuyên. Để khắc phục, cài đặt cron job để định kỳ cập nhật dữ liệu (ví dụ: mỗi 5 phút).

Tài Liệu Tham Khảo

Kết Luận

Sử dụng Blockfrost để truy vấn dữ liệu on-chain từ Cardano là một cách hiệu quả và dễ dàng để tích hợp blockchain vào ứng dụng của bạn. Bằng cách gọi API từ backend và tối ưu hóa với database, bạn có thể giảm chi phí, tăng tốc độ và đảm bảo bảo mật. Hướng dẫn này đã cung cấp các bước cụ thể để thiết lập Blockfrost, truy vấn lịch sử giao dịch, và lưu trữ dữ liệu. Để mở rộng, bạn có thể khám phá các endpoint khác của Blockfrost để hỗ trợ các chức năng như truy vấn tài sản, staking, hoặc gửi giao dịch.

Bài Tập

📝 Bài tập 1: Hiểu về Blockfrost và Lý do sử dụng

Đề bài

Giải thích tại sao nên sử dụng Blockfrost API để truy vấn dữ liệu on-chain thay vì chạy node Cardano trực tiếp.

Yêu cầu

  • Mô tả cách Blockfrost API hoạt động.
  • Liệt kê ít nhất 2 ưu điểm và 2 nhược điểm khi sử dụng Blockfrost.
  • Đưa ra ví dụ về một trường hợp sử dụng Blockfrost trong dự án.
Cách giải
  1. Cách Blockfrost hoạt động:
    • Blockfrost cung cấp các API endpoint để truy vấn dữ liệu từ blockchain Cardano (như tài khoản, giao dịch, tài sản) thông qua HTTP requests, sử dụng project ID để xác thực.
  2. Ưu điểm và nhược điểm:
    • Ưu điểm: Dễ tích hợp, không cần chạy node Cardano; cung cấp gói miễn phí (15 triệu request/tháng).
    • Nhược điểm: Có chi phí nếu vượt giới hạn request; phụ thuộc vào dịch vụ bên thứ ba.
  3. Ví dụ sử dụng:
    • Theo dõi số dư ví hoặc lịch sử giao dịch trong ứng dụng DApp.

Đáp án

  • Cách hoạt động: Blockfrost cung cấp API HTTP để truy vấn dữ liệu blockchain Cardano, sử dụng project ID trong header để xác thực.
  • Ưu điểm: Dễ tích hợp vào dự án; gói miễn phí hỗ trợ 15 triệu request/tháng.
  • Nhược điểm: Chi phí khi vượt giới hạn request; phụ thuộc vào dịch vụ bên thứ ba.
  • Ví dụ: Xây dựng DApp hiển thị lịch sử giao dịch của ví người dùng.

📝 Bài tập 2: Cài đặt Blockfrost trong dự án Next.js

Đề bài

Tích hợp Blockfrost SDK vào dự án Next.js để hiển thị thông tin mạng Cardano.

Yêu cầu

  • Cài đặt Blockfrost SDK.
  • Sử dụng API /network để lấy thông tin mạng (như epoch hiện tại).
  • Hiển thị thông tin trên trang chủ.
  • Đảm bảo gọi API từ server-side để bảo mật project ID.
Cách giải
  1. Cài đặt Blockfrost SDK:
    • Chạy lệnh: npm install @blockfrost/blockfrost-js.
    • Tạo project ID trên Blockfrost dashboard (mạng Preprod).
  2. Tạo API route:
    • Tạo file app/api/network/route.ts để gọi API /network từ server-side.
  3. Hiển thị dữ liệu:
    • Fetch dữ liệu từ API route trong app/page.tsx và hiển thị.
  4. Kiểm tra:
    • Chạy ứng dụng và kiểm tra thông tin mạng.

Đáp án

Cài đặt:

npm install @blockfrost/blockfrost-js
 

Tạo file app/api/network/route.ts:

import { NextResponse } from "next/server";
import { BlockFrostAPI } from "@blockfrost/blockfrost-js";

export async function GET() {
  try {
    const api = new BlockFrostAPI({
      projectId: "preprodYourProjectIdHere", // Thay bằng project ID của bạn
    });
    const networkInfo = await api.network();
    return NextResponse.json(networkInfo);
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
 

Sửa file app/page.tsx:

"use client";
import { useState, useEffect } from "react";

export default function Home() {
  const [networkInfo, setNetworkInfo] = useState(null);

  useEffect(() => {
    async function fetchNetworkInfo() {
      try {
        const response = await fetch("/api/network");
        const data = await response.json();
        setNetworkInfo(data);
      } catch (error) {
        setNetworkInfo({ error: "Lỗi khi lấy thông tin mạng" });
      }
    }
    fetchNetworkInfo();
  }, []);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      <h1>Cardano Network Info</h1>
      {networkInfo && !networkInfo.error ? (
        <div>
          <p>Epoch: {networkInfo.epoch}</p>
          <p>Slot: {networkInfo.slot}</p>
        </div>
      ) : (
        <p>{networkInfo?.error || "Đang tải..."}</p>
      )}
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000 để thấy thông tin mạng (epoch, slot). Đăng ký project ID tại Blockfrost.


📝 Bài tập 3: Truy vấn lịch sử giao dịch của ví

Đề bài

Tạo một API route để truy vấn lịch sử giao dịch của một địa chỉ ví Cardano bằng Blockfrost.

Yêu cầu

  • Tạo API route /api/address/[address]/transactions để lấy danh sách giao dịch.
  • Hiển thị danh sách giao dịch (Tx Hash) trên trang /transactions/[address].
  • Sử dụng dynamic routing để lấy địa chỉ ví từ URL.
  • Định dạng danh sách giao diện đẹp mắt.
Cách giải
  1. Tạo API route:
    • Tạo file app/api/address/[address]/transactions/route.ts để gọi API /addresses/{address}/transactions của Blockfrost.
  2. Tạo trang dynamic:
    • Tạo file app/transactions/[address]/page.tsx để fetch và hiển thị danh sách giao dịch.
  3. Định dạng giao diện:
    • Sử dụng CSS inline hoặc file CSS để định dạng danh sách.

Đáp án

Tạo file app/api/address/[address]/transactions/route.ts:

import { NextResponse } from "next/server";
import { BlockFrostAPI } from "@blockfrost/blockfrost-js";

export async function GET(
  request: Request,
  { params }: { params: { address: string } }
) {
  try {
    const api = new BlockFrostAPI({
      projectId: "preprodYourProjectIdHere", // Thay bằng project ID của bạn
    });
    const transactions = await api.addressesTransactions(params.address);
    return NextResponse.json(transactions);
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
 

Tạo file app/transactions/[address]/page.tsx:

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";

export default function Transactions() {
  const { address } = useParams();
  const [transactions, setTransactions] = useState([]);

  useEffect(() => {
    async function fetchTransactions() {
      try {
        const response = await fetch(`/api/address/${address}/transactions`);
        const data = await response.json();
        setTransactions(data);
      } catch (error) {
        setTransactions([{ error: "Lỗi khi lấy giao dịch" }]);
      }
    }
    fetchTransactions();
  }, [address]);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      <h1>Lịch sử giao dịch của ví: {address}</h1>
      <ul style={{ listStyle: "none", padding: 0 }}>
        {transactions.length > 0 && !transactions[0].error ? (
          transactions.map((tx: any) => (
            <li
              key={tx.tx_hash}
              style={{
                margin: "10px 0",
                padding: "10px",
                border: "1px solid #ccc",
              }}
            >
              Tx Hash: {tx.tx_hash}
            </li>
          ))
        ) : (
          <p>{transactions[0]?.error || "Không có giao dịch"}</p>
        )}
      </ul>
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000/transactions/addr_test1... (thay bằng địa chỉ ví Preprod) để thấy danh sách Tx Hash.


📝 Bài tập 4: Tối ưu truy vấn với Database Cache

Đề bài

Tối ưu truy vấn lịch sử giao dịch bằng cách lưu dữ liệu vào database (giả lập bằng biến) để giảm số lượng request đến Blockfrost.

Yêu cầu

  • Tạo API route /api/address/[address]/transactions với cơ chế cache giả lập.
  • Chỉ gọi Blockfrost nếu cache không có dữ liệu hoặc quá 1 phút.
  • Hiển thị danh sách giao dịch trên trang /transactions/[address].
  • Hiển thị thời gian cache được cập nhật.
Cách giải
  1. Tạo cache giả lập:
    • Sử dụng biến Map trong API route để lưu dữ liệu và thời gian cập nhật.
  2. Kiểm tra cache:
    • Nếu cache tồn tại và chưa quá 1 phút, trả về dữ liệu cache. Ngược lại, gọi Blockfrost.
  3. Hiển thị giao diện:
    • Hiển thị danh sách giao dịch và thời gian cache trong app/transactions/[address]/page.tsx.
  4. Kiểm tra:
    • Kiểm tra console log để xác nhận cache hoạt động.

Đáp án

Tạo file app/api/address/[address]/transactions/route.ts:

import { NextResponse } from "next/server";
import { BlockFrostAPI } from "@blockfrost/blockfrost-js";

// Giả lập cache
const cache = new Map<string, { data: any; timestamp: number }>();

export async function GET(
  request: Request,
  { params }: { params: { address: string } }
) {
  const cacheKey = params.address;
  const cacheData = cache.get(cacheKey);

  // Kiểm tra cache (hết hạn sau 1 phút)
  if (cacheData && Date.now() - cacheData.timestamp < 60 * 1000) {
    console.log("Returning cached data for:", cacheKey);
    return NextResponse.json({
      ...cacheData.data,
      cachedAt: new Date(cacheData.timestamp).toISOString(),
    });
  }

  try {
    const api = new BlockFrostAPI({
      projectId: "preprodYourProjectIdHere", // Thay bằng project ID của bạn
    });
    const transactions = await api.addressesTransactions(params.address);
    cache.set(cacheKey, { data: transactions, timestamp: Date.now() });
    console.log("Fetched new data for:", cacheKey);
    return NextResponse.json({
      transactions,
      cachedAt: new Date().toISOString(),
    });
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
 

Sửa file app/transactions/[address]/page.tsx:

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";

export default function Transactions() {
  const { address } = useParams();
  const [data, setData] = useState({ transactions: [], cachedAt: "" });

  useEffect(() => {
    async function fetchTransactions() {
      try {
        const response = await fetch(`/api/address/${address}/transactions`);
        const result = await response.json();
        setData(result);
      } catch (error) {
        setData({
          transactions: [{ error: "Lỗi khi lấy giao dịch" }],
          cachedAt: "",
        });
      }
    }
    fetchTransactions();
  }, [address]);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      <h1>Lịch sử giao dịch của ví: {address}</h1>
      <p>Cached at: {data.cachedAt || "N/A"}</p>
      <ul style={{ listStyle: "none", padding: 0 }}>
        {data.transactions.length > 0 && !data.transactions[0].error ? (
          data.transactions.map((tx: any) => (
            <li
              key={tx.tx_hash}
              style={{
                margin: "10px 0",
                padding: "10px",
                border: "1px solid #ccc",
              }}
            >
              Tx Hash: {tx.tx_hash}
            </li>
          ))
        ) : (
          <p>{data.transactions[0]?.error || "Không có giao dịch"}</p>
        )}
      </ul>
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000/transactions/addr_test1.... Console log sẽ hiển thị “Returning cached data” nếu truy cập lại trong vòng 1 phút.


📝 Bài tập 5: Truy vấn chi tiết giao dịch

Đề bài

Tạo trang hiển thị chi tiết giao dịch dựa trên Tx Hash bằng Blockfrost API.

Yêu cầu

  • Tạo API route /api/transaction/[txHash] để lấy chi tiết giao dịch.
  • Tạo trang /transaction/[txHash] để hiển thị thông tin giao dịch (block, fee, inputs, outputs).
  • Xử lý lỗi nếu Tx Hash không hợp lệ.
  • Định dạng giao diện đẹp mắt.
Cách giải
  1. Tạo API route:
    • Tạo file app/api/transaction/[txHash]/route.ts để gọi API /txs/{txHash} của Blockfrost.
  2. Tạo trang dynamic:
    • Tạo file app/transaction/[txHash]/page.tsx để fetch và hiển thị chi tiết giao dịch.
  3. Xử lý lỗi:
    • Trả về thông báo lỗi nếu giao dịch không tồn tại.
  4. Định dạng:
    • Sử dụng CSS inline để hiển thị thông tin rõ ràng.

Đáp án

Tạo file app/api/transaction/[txHash]/route.ts:

import { NextResponse } from "next/server";
import { BlockFrostAPI } from "@blockfrost/blockfrost-js";

export async function GET(
  request: Request,
  { params }: { params: { txHash: string } }
) {
  try {
    const api = new BlockFrostAPI({
      projectId: "preprodYourProjectIdHere", // Thay bằng project ID của bạn
    });
    const transaction = await api.txs(params.txHash);
    return NextResponse.json(transaction);
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
 

Tạo file app/transaction/[txHash]/page.tsx:

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";

export default function TransactionDetail() {
  const { txHash } = useParams();
  const [transaction, setTransaction] = useState(null);

  useEffect(() => {
    async function fetchTransaction() {
      try {
        const response = await fetch(`/api/transaction/${txHash}`);
        const data = await response.json();
        setTransaction(data);
      } catch (error) {
        setTransaction({ error: "Lỗi khi lấy chi tiết giao dịch" });
      }
    }
    fetchTransaction();
  }, [txHash]);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      <h1>Chi tiết giao dịch: {txHash}</h1>
      {transaction && !transaction.error ? (
        <div
          style={{
            border: "1px solid #ccc",
            padding: "20px",
            borderRadius: "5px",
          }}
        >
          <p>Block: {transaction.block}</p>
          <p>Fee: {transaction.fees / 1000000} ADA</p>
          <p>Inputs: {transaction.input_count}</p>
          <p>Outputs: {transaction.output_count}</p>
        </div>
      ) : (
        <p style={{ color: "red" }}>{transaction?.error || "Đang tải..."}</p>
      )}
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000/transaction/<tx_hash> (thay bằng Tx Hash hợp lệ từ Preprod) để thấy chi tiết giao dịch.