
Cách xây dựng trình xem danh mục Solana bằng Next.js và DAS API của Helius
Mục tiêu
Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng front-end để hiển thị nội dung của ví Solana bằng DAS API từ Helius. Ứng dụng sẽ trình bày nhiều loại token cùng thông tin chi tiết, bao gồm metadata, thông tin bộ sưu tập và giá token có thể thay thế.
Hướng dẫn này sẽ chỉ cho bạn quy trình xây dựng một ứng dụng tương tự Galleria bằng cách phân tích các bước cần thiết.
Điều kiện tiên quyết
- Khóa Helius API.
- Quen thuộc với React.
- Có kinh nghiệm với Next.js.
Tổng quan về các công nghệ được sử dụng
- Next.js: Framework React để xây dựng các ứng dụng được kết xuất phía máy chủ.
- DAS API từ Helius: API toàn diện để truy cập dữ liệu token Solana.
- Vercel: Nền tảng lưu trữ ứng dụng Next.js.
- Tailwind CSS: Framework CSS ưu tiên tiện ích để tạo kiểu.
Thiết lập dự án
Về DAS API
DAS API là một đặc tả và hệ thống mã nguồn mở do Helius cung cấp. API này cung cấp một giao diện đơn giản để tương tác với tài sản kỹ thuật số trên blockchain Solana. Nó hỗ trợ nhiều loại token, bao gồm token có thể thay thế, NFT thông thường và NFT nén, đồng thời cung cấp các tính năng như lập chỉ mục metadata ngoài chuỗi.
Thiết lập khóa API
Để nhận khóa Helius API, hãy truy cập Cổng thông tin dành cho nhà phát triển Helius. Sau đây là cách bắt đầu:
- Tạo tài khoản bằng ví Solana, tài khoản Google hoặc tài khoản GitHub.
- Sau khi đăng nhập, bạn sẽ được nhắc tạo khóa API.
- Lưu trữ khóa này an toàn vì chúng ta sẽ dùng nó để thực hiện các lệnh gọi API.
Xây dựng ứng dụng Next.js
Bước 1: Thiết lập dự án Next.js
Tạo ứng dụng Next.js mới với Tailwind
npx create-next-app@latest helius-portfolio --typescript --eslintSử dụng các cấu hình sau:
Điều này cho phép chúng ta sử dụng Tailwind CSS để tạo kiểu cũng như dùng App router mới.
cd helius-portfolioBước 2: Tạo các thành phần
Tạo thư mục components trong thư mục app để duy trì cấu trúc có tổ chức.
Thành phần SearchBar (components/SearchBar.js):
Thành phần này xử lý dữ liệu nhập của người dùng cho địa chỉ ví. Nó sử dụng hook useState để quản lý trạng thái địa chỉ và useRouter để điều hướng sau khi gửi biểu mẫu. Biểu mẫu được tạo kiểu bằng các lớp Tailwind CSS để có giao diện tinh gọn.
Thành phần TokenCard (components/TokenCard.js):
Thành phần này được thiết kế để hiển thị thông tin token. Nó kết xuất có điều kiện các bố cục khác nhau cho token có thể thay thế và không thể thay thế, đồng thời sử dụng các kiểu Token và Attribute từ ../types/Token.
Thành phần SearchBar:
components/searchBar.ts
import { useRouter } from "next/navigation";
import React, { useState } from "react";
export default function SearchBar() {
const router = useRouter();
const [address, setAddress] = useState("");
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
router.push(`/${address}`);
}
return (
setAddress(e.target.value)}
className="border-2 border-gray-300 rounded-md w-full text-center px-4 py-2 mb-4 flex-grow text-black"
/>
Search
);
}Thành phần TokenCard:
Thành phần này phân tích dữ liệu token từ API và hiển thị thông tin liên quan trên từng thẻ token.
Trong ví dụ dưới đây, chúng ta hiển thị một số thông tin cơ bản như hình ảnh, ký hiệu, số lượng và giá trị của từng token có thể thay thế. Với token không thể thay thế, chúng ta hiển thị hình ảnh, tên, mô tả và thuộc tính. Bạn có thể trích xuất bất kỳ thông tin nào do API cung cấp và hiển thị trên thẻ.
Ví dụ, bạn có thể hiển thị thông tin dành riêng cho tính năng nén của cNFT hoặc thông tin inscription của tài sản đã được inscription. Để tìm hiểu thêm về những thông tin cụ thể có thể sử dụng, hãy xem tài liệu DAS.
"use client";
import React from "react";
import { Token, Attribute } from "../types/Token";
interface TokenCardProps {
token: Token;
tokenType: string;
}
const TokenCard = ({ token, tokenType }: TokenCardProps) => {
return (
{/* Other token information */}
{tokenType === "fungible" ? (
{token.content.metadata.symbol}
{/* {token.content.metadata.description} */}
Amount:{token.token_info.balance}
{token.token_info.price_info?.total_price ? (
Value: ${token.token_info.price_info.total_price}
) : null}
) : (
{token.content.metadata.name}
{token.content.metadata.description}
{token.content.metadata?.attributes?.map(
(attribute: Attribute, index: number) => (
{attribute.trait_type}: {attribute.value}
)
)}
)}
);
}
export default TokenCard;Bước 3: Lấy dữ liệu từ DAS
Chúng ta sẽ dùng phương thức searchAssets từ DAS API để lấy dữ liệu token. Phương thức này rất linh hoạt, cho phép chỉ định các tiêu chí như địa chỉ ví và loại token.
Tạo thư mục lib:
- Tạo thư mục
libtrong thư mục app cho lệnh gọi API. - Tạo tệp
searchAssets.tsbên trong thư mục đó.
Hàm gọi API (lib/searchAssets.ts):
- Hàm này lấy token dựa trên địa chỉ ví.
- Hàm xử lý các lỗi có thể xảy ra và ghi nhật ký phản hồi để gỡ lỗi
interface Tokens {
items: any[];
}
const fetchTokens = async (walletAddress: string): Promise => {
const url = `https://mainnet.helius-rpc.com/?api-key=`;
console.log(
`Starting search for tokens for wallet address: ${walletAddress}`
);
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "my-id",
method: "searchAssets",
params: {
ownerAddress: walletAddress,
tokenType: "all",
displayOptions: {
showCollectionMetadata: true,
},
},
}),
});
const data = await response.json();
console.log(
`Data returned for wallet address ${walletAddress}:`,
data.result
);
return { items: data.result };
} catch (error) {
console.error("Error fetching tokens:", error);
return { items: [] };
}
};
export default fetchTokens;Thiết lập kiểu token
- Chúng ta cần thiết lập một kiểu cho phản hồi từ DAS API. Đây là phương pháp hay khi làm việc với TypeScript. Bạn có thể tạo thư mục có tên types và tạo tệp có tên
token.ts
export interface ApiResponse {
total: number;
limit: number;
cursor?: string;
items: Token[];
}
export interface Token {
interface: string;
id: string;
content: Content;
authorities: Authority[];
compression: Compression;
grouping: Grouping[]; // or any[]
royalty: Royalty;
creators: Creator[]; // or any[]
ownership: Ownership;
supply: Supply | null | number;
mutable: boolean;
burnt: boolean;
token_info: TokenInfo;
mint_extensions: MintExtensions;
inscription: Inscription;
spl20?: Spl20;
}
export interface Content {
$schema: string;
json_uri: string;
files: any[];
metadata: Record;
links: Record;
}
export interface Authority {
address: string;
scopes: string[];
}
export interface Compression {
eligible: boolean;
compressed: boolean;
data_hash: string;
creator_hash: string;
asset_hash: string;
tree: string;
seq: number;
leaf_id: number;
}
export interface Grouping {
group_key: string;
group_value: string;
collection_metadata: CollectionMetadata;
}
export interface Royalty {
royalty_model: string;
target: string | null;
percent: number;
basis_points: number;
primary_sale_happened: boolean;
locked: boolean;
}
export interface Creator {
address: string;
share: number;
verified: boolean;
}
export interface Ownership {
frozen: boolean;
delegated: boolean;
delegate: null | string;
ownership_model: string;
owner: string;
}
export interface Supply {
print_max_supply: number;
print_current_supply: number;
edition_nonce: number;
}
export interface TokenInfo {
symbol: string;
balance: number;
supply: number;
decimals: number;
token_program: string;
associated_token_address: string;
price_info: PriceInfo;
}
export interface Inscription {
order: number;
size: number;
contentType: string;
encoding: string;
validationHash: string;
inscriptionDataAccount: string;
}
export interface Spl20 {
p: string;
op: string;
tick: string;
amt: string;
}
export interface File {
uri: string;
cdn_uri: string;
mime: string;
}
export interface Metadata {
attributes: Attribute[];
description: string;
name: string;
symbol: string;
}
export interface Attribute {
value: string;
trait_type: string;
}
export interface CollectionMetadata {
name: string;
symbol: string;
image: string;
description: string;
external_url: string;
}
export interface PriceInfo {
price_per_token: number;
total_price: number;
currency: string;
}
export interface MintExtensions {}Bước 4: Hiển thị thông tin token
Chúng ta sẽ phân loại phản hồi API thành token có thể thay thế và không thể thay thế. Các token này sẽ được hiển thị bằng thành phần Token Card. Nút chuyển đổi cho phép người dùng chuyển đổi giữa hai loại token.
Tích hợp các thành phần trên trang tìm kiếm
- Trang tìm kiếm (pages/index.ts)
- Trang đích có thanh tìm kiếm để người dùng nhập địa chỉ ví.
- Trang được tạo kiểu bằng Tailwind CSS để có thiết kế gọn gàng và thích ứng.
"use client";
import React from "react";
import SearchBar from "./components/searchBar";
export default function Home() {
return (
Solana Portfolio Viewer
{"Built with Helius's DAS API"}
);
}Thiết lập trang token
Chúng ta sẽ sử dụng các route động trong Next.js cho từng địa chỉ ví. Điều này cải thiện trải nghiệm người dùng nhờ hỗ trợ điều hướng và chia sẻ URL dễ dàng.
Thiết lập một thư mục có tên [wallet]; các dấu ngoặc cho phép định tuyến động. Sau đó, tạo tệp có tên page.tsx để chứa toàn bộ logic nghiệp vụ cho tất cả các trang được tải động.
Thiết lập trang token (pages/[wallet]/page.tsx):
Trang này hiển thị các token liên kết với địa chỉ ví trong URL. Trang thực hiện lệnh gọi API để lấy dữ liệu token và hiển thị dữ liệu đó bằng thành phần Token Card.
"use client";
import React, { useState, useEffect } from "react";
import fetchTokens from "../lib/searchAssets";
import TokenCard from "../components/tokenCard";
import { Token, Attribute } from "../types/Token";
interface Tokens {
items: Token[];
}
interface PageProps {
params: {
wallet: string;
};
}
export default function Page({ params }: PageProps) {
const [tokens, setTokens] = useState(null);
const [tokenType, setTokenType] = useState("fungible");
useEffect(() => {
fetchTokens(params.wallet).then(setTokens).catch(console.error);
}, [params.wallet]);
console.log("tokens", tokens);
const fungibleTokens = tokens
? tokens.items.filter(
(token) =>
token.interface === "FungibleToken" ||
token.interface === "FungibleAsset"
)
: [];
const nonFungibleTokens = tokens
? tokens.items.filter(
(token) =>
token.interface !== "FungibleToken" &&
token.interface !== "FungibleAsset"
)
: [];
const displayedTokens =
tokenType === "fungible" ? fungibleTokens : nonFungibleTokens;
return (
Portfolio Viewer
{params.wallet}
setTokenType("fungible")}
>
Fungible Tokens
setTokenType("nonFungible")}
>
Non-Fungible Tokens
{displayedTokens.length > 0 ? (
{displayedTokens.map((token) => (
))}
) : (
Loading...
)}
);
}Sau khi hoàn tất mọi thiết lập, thư mục dự án của bạn sẽ có dạng như sau:
Bước 5: Tạo kiểu cho ứng dụng
Tailwind CSS sẽ là framework tạo kiểu của chúng ta. Cách tiếp cận ưu tiên tiện ích cho phép tạo kiểu nhanh cho các thành phần, đồng thời đảm bảo thiết kế nhất quán và thích ứng. Bạn có thể dùng Tailwind để tùy chỉnh ứng dụng theo bất kỳ cách nào mình muốn.
Bước 6: Kiểm thử ứng dụng
Chạy ứng dụng Next.js để kiểm thử cục bộ:
npm run devKiểm thử bằng cách nhập nhiều địa chỉ ví khác nhau và đảm bảo dữ liệu chính xác được hiển thị.
Triển khai
Chúng ta sẽ triển khai ứng dụng bằng Vercel. Vercel cung cấp dịch vụ lưu trữ miễn phí cùng các thông tin phân tích và chuyên sâu hữu ích, nên đây là lựa chọn lý tưởng để lưu trữ ứng dụng Next.js. Bạn có thể thiết lập tại https://vercel.com/.
Kết luận
Chúc mừng bạn đã hoàn thành hướng dẫn này! Hy vọng bạn thấy nội dung hữu ích.
DAS API là một công cụ rất mạnh mẽ và nhanh chóng, cho phép truy xuất dữ liệu token từ chuỗi.
Trình xem danh mục này là một ví dụ điển hình về khả năng của API.
Hy vọng bạn có thể phát triển trang web cơ bản vừa tạo thành một sản phẩm tuyệt vời. Nếu có bất kỳ câu hỏi nào, đừng ngần ngại tham gia Discord của chúng tôi và hỏi trực tiếp!
Bạn có thể xem mã hoàn chỉnh tại đây: https://github.com/helius-labs/galleria
Tài nguyên học tập bổ sung
Để hiểu sâu hơn về Next.js, blockchain Solana và tích hợp API, hãy khám phá các tài nguyên sau:
Hướng dẫn này cung cấp nền tảng vững chắc để xây dựng các ứng dụng dựa trên blockchain bằng Next.js và tương tác với blockchain Solana qua DAS API.
Bài viết liên quan
Đăng ký nhận tin từ Helius
Luôn cập nhật những thông tin mới nhất về phát triển Solana và nhận thông báo khi chúng tôi đăng bài


