MỚI: Helius mua lại Light Protocol
Cách bắt đầu xây dựng với Solana Web3.js 2.0 SDK
Blog/Phát triển

Cách bắt đầu xây dựng với Solana Web3.js 2.0 SDK

Kỹ sư trải nghiệm nhà phát triểnAnam Ansari trên XAnam Ansari trên LinkedIn
Đào tạo nhà phát triểnMike MacCana trên XMike MacCana trên LinkedIn
Đọc trong 10 phút

Trước khi đi sâu vào nội dung, chúng tôi muốn cảm ơn Evan và Nick đã đánh giá bài viết này. Những phản hồi và góc nhìn quý báu của họ rất đáng trân trọng.

Giới thiệu

Solana Web3.js SDK là một thư viện TypeScript và JavaScript mạnh mẽ để xây dựng ứng dụng Solana trên Node.js, web và React Native. Ngày 7 tháng 11 năm 2024, Anza đã giới thiệu bản cập nhật 2.0 SDK rất được mong đợi, mang đến hàng loạt tính năng và cải tiến JavaScript hiện đại. Những điểm nổi bật gồm các kiểu JS tiêu chuẩn cho bigint và mật mã, cùng kích thước bundle nhỏ hơn, tạo nên một bản nâng cấp đáng kể cho các nhà phát triển.

Nếu đang sử dụng @solana/web3.js, bạn cần chuyển phần mềm sang package v2.0 mới hoặc chỉ định rõ phiên bản để khóa ở v1.x.

Trong bài viết này, chúng ta sẽ tìm hiểu các cập nhật mới nhất trong Web3.js 2.0 SDK, hướng dẫn bạn thực hiện quy trình di chuyển và cung cấp một ví dụ để giúp bạn bắt đầu.

Bài viết này giả định bạn đã nắm vững các khái niệm nền tảng của Solana, chẳng hạn như gửi transaction, mô hình account của Solana, blockhash và phí ưu tiên, đồng thời có kinh nghiệm với TypeScript hoặc JavaScript. Bạn nên quen thuộc với phiên bản Web3.js SDK trước đó, nhưng đây không phải yêu cầu bắt buộc. Hãy bắt đầu!

Web3.js 2.0 có gì mới?

Hãy cùng điểm nhanh những gì Web3.js 2.0 SDK mới mang lại:

1. Cải thiện hiệu năng

Các thao tác mật mã nhanh hơn: tạo keypair, ký transaction và xác minh message nhanh hơn tới 10 lần nhờ tận dụng API mật mã gốc trong các môi trường JavaScript hiện đại như Node.js và các trình duyệt hiện hành.

2. Ứng dụng nhỏ gọn và hiệu quả hơn

Web3.js 2.0 hỗ trợ tree-shaking hoàn toàn, cho phép bạn chỉ đưa vào những phần thư viện thực sự sử dụng để giảm thiểu kích thước bundle. Ngoài ra, SDK mới không có dependency bên ngoài, giúp bản build nhẹ và an toàn.

3. Tính linh hoạt cao hơn

Giờ đây, nhà phát triển có thể tạo giải pháp tùy chỉnh bằng cách:

  • Định nghĩa các instance RPC với method tùy chỉnh
  • Sử dụng network transport hoặc trình ký transaction chuyên biệt
  • Kết hợp các primitive tùy chỉnh cho mạng, xác nhận transaction và codec

Các client TypeScript mới cho program on-chain hiện được lưu trữ trong tổ chức GitHub @solana-program. Những client này được tự động tạo bằng Codama, giúp nhà phát triển nhanh chóng tạo client cho program tùy chỉnh.

Bạn đã nên chuyển sang Web3.js v2 chưa?

Tính đến tháng 2 năm 2025:

  • Nếu đang tạo một ứng dụng Solana mới bằng JS/TS và sử dụng các program hiện có như system program, token program, associated token program cùng các program phổ biến khác, bạn có thể dùng web3.js v2 ngay lúc này.
  • Nếu đang tạo ứng dụng on-chain tùy chỉnh bằng Anchor, bạn có thể nên chờ thêm—Anchor chưa hỗ trợ sẵn web3.js v2. Bạn có thể chờ một bản cập nhật Anchor trong tương lai. Hoặc dùng Codama để tạo client TypeScript cho ứng dụng on-chain, dù cách này sẽ cần thêm một chút công sức.

Di chuyển từ web3.js phiên bản 1

Nếu đã dùng web3.js v1, dưới đây là phần tóm tắt nhanh những điểm khác biệt quan trọng:

Keypair

Ở mọi nơi trước đây dùng Keypair, giờ đây bạn sẽ dùng KeyPairSigner. Keypair.generate() giờ là generateKeyPairSigner(). Ngoài ra, keypair giờ được viết là keyPair ở mọi nơi, theo quy ước camelCase thông thường của JS/TS.

Secret key giờ được gọi là privateKey và có thể truy cập tại keyPairSigner.privateKey. Nhìn chung, trong web3.js v2, bạn sẽ dùng KeyPairSigner ở bất kỳ đâu từng dùng secretKey trong web3.js v1.

Địa chỉ / Public key

Những nơi dùng PublicKey trong web3.js v1 giờ chỉ dùng address trong web3.js v2. Ví dụ, KeyPairSigner có thuộc tính keypairSigner.address, chính là public key của chúng. Bạn có thể chuyển Public Key dạng chuỗi thành address bằng hàm address.

Số lượng SOL và token

Số lượng sử dụng kiểu BigInt gốc của JS. Vì vậy, bạn sẽ thêm n vào cuối các số, tạo thành 1n thay vì 1.

Factory

Nhiều tính năng có thể cấu hình, vì vậy thay vì có một implementation cài đặt sẵn (ví dụ: doThing()), sẽ có một factory (được gọi là doThingFactory()) để bạn tạo hàm doThing() riêng. Ví dụ:

  • Để gửi và xác nhận transaction, bạn chạy sendAndConfirmTransactionFactory() một lần với các tùy chọn mong muốn và nhận lại hàm sendAndConfirmTransaction() tùy chỉnh. Sau đó, bạn có thể dùng sendAndConfirmTransaction() bất cứ khi nào cần gửi và xác nhận một transaction.
  • Để nhận airdrop trên devnet hoặc localnet, bạn chạy airdropFactory() một lần và nhận hàm airdrop() tùy chỉnh có thể dùng bất cứ khi nào muốn nhận airdrop.

Cách gửi transaction bằng Web3.js 2.0

Chúng ta sẽ xây dựng một chương trình phía client bằng Web3.js 2.0 để chuyển lamport sang một ví khác. Chương trình này sẽ trình bày các kỹ thuật giúp tăng tỷ lệ thành công và rút ngắn thời gian xác nhận transaction.

Chúng ta sẽ tuân theo các phương pháp hay nhất sau đây khi gửi transaction:

  1. Lấy blockhash mới nhất với mức commitment confirmed
  2. Đặt phí ưu tiên theo khuyến nghị của Priority Fee API từ Helius
  3. Tối ưu compute unit
  4. Gửi transaction với maxRetries được đặt thành 0 và skipPreflight được đặt thành true

Cách tiếp cận này đảm bảo hiệu năng và độ tin cậy tối ưu, ngay cả khi mạng bị tắc nghẽn.

Điều kiện tiên quyết

  • Cài đặt Node.js
  • Một IDE tương thích (ví dụ: VS Code hoặc Cursor)

Cài đặt

Bắt đầu bằng cách tạo một dự án Node.js cơ bản để thiết lập cấu trúc cho ứng dụng.

Chạy lệnh sau để tạo tệp package.json dùng để quản lý dependency và metadata của dự án:

Shell commands
npm init -y

Tạo thư mục src, sau đó thêm tệp index.ts vào thư mục này để chứa mã chính:

Shell commands
mkdir src  
touch src/index.ts

Tiếp theo, dùng npm để cài đặt các dependency cần thiết khi làm việc với Web3.js 2.0 SDK của Solana:

Shell commands
npm install @solana/web3.js@2 @solana-program/system @solana-program/compute-budget esrun

Dưới đây là mô tả về từng package:

  • @solana/web3.js: Solana Web3.js 2.0 SDK, thành phần thiết yếu để xây dựng và quản lý transaction Solana
  • @solana-program/system: cung cấp quyền truy cập vào Solana System Program, cho phép thực hiện các thao tác như chuyển lamport
  • @solana-program/compute-budget: dùng để đặt phí ưu tiên và tối ưu compute unit cho transaction
  • esrun là một cách đơn giản để chạy ứng dụng TypeScript từ dòng lệnh mà không cần cấu hình hoặc hàm wrapper.

Xác định địa chỉ chuyển

Trong index.ts, hãy xác định địa chỉ nguồn và địa chỉ đích để chuyển lamport. Chúng ta sẽ dùng hàm address() để tạo public key đích từ chuỗi đã cung cấp.

Đối với nguồn, chúng ta sẽ dẫn xuất KeyPair bằng secretKey của nó.

send-transaction.ts
import { address, createKeyPairSignerFromBytes, getBase58Encoder } from "@solana/web3.js";

const destinationAddress = address("public-key-to-send-lamports-to");
const secretKey = "add-your-private-key";
const sourceKeypair = await createKeyPairSignerFromBytes(getBase58Encoder().encode(secretKey));

Cấu hình kết nối RPC

Tiếp theo, chúng ta có thể thiết lập các kết nối RPC liên quan. Hàm createSolanaRpc thiết lập giao tiếp với RPC server bằng HTTP transport mặc định, đủ đáp ứng hầu hết trường hợp sử dụng.

Tương tự, chúng ta dùng createSolanaRpcSubscriptions để thiết lập kết nối WebSocket. rpc_url và wss_url nằm trong Helius Dashboard—chỉ cần đăng ký hoặc đăng nhập rồi chuyển đến mục “Endpoints”.

Hàm sendAndConfirmTransactionFactory tạo một trình gửi transaction có thể tái sử dụng. Trình gửi này cần kết nối RPC để gửi transaction và subscription RPC để theo dõi trạng thái transaction.

send-transaction.ts
import {
  // ...
  createSolanaRpcSubscriptions,
  createSolanaRpc,
  sendAndConfirmTransactionFactory,
} from "@solana/web3.js";

const rpc_url = "https://mainnet.helius-rpc.com/?api-key=<your-key>";
const wss_url = "wss://mainnet.helius-rpc.com/?api-key=<your-key>";

const rpc = createSolanaRpc(rpc_url);
const rpcSubscriptions = createSolanaRpcSubscriptions(wss_url);

const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
  rpc,
  rpcSubscriptions,
});

Tạo instruction chuyển

Việc đưa blockhash gần đây vào sẽ ngăn trùng lặp và cung cấp vòng đời cho transaction—mọi transaction đều phải chứa một blockhash hợp lệ mới được chấp nhận thực thi. Với transaction này, chúng ta sẽ lấy blockhash mới nhất bằng mức commitment confirmed.

Tiếp theo, chúng ta sẽ dùng getTransferSolInstruction() để tạo instruction chuyển được System Program cung cấp sẵn. Thao tác này yêu cầu chỉ định số lượng, nguồn

và đích. Nguồn luôn phải là một Signer, còn đích phải là một địa chỉ công khai.

send-transaction.ts
import {
  // ...
  lamports,
} from "@solana/web3.js";
import { getTransferSolInstruction } from "@solana-program/system";

/**
 * STEP 1: CREATE THE TRANSFER TRANSACTION
 */
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const instruction = getTransferSolInstruction({
  amount: lamports(1n),
  destination: destinationAddress,
  source: sourceKeypair,
});

Tạo transaction message

Sau đó, chúng ta sẽ tạo transaction message. Tất cả transaction message giờ đều nhận biết phiên bản, loại bỏ nhu cầu xử lý các kiểu khác nhau (ví dụ: Transaction so với VersionedTransaction).

Chúng ta sẽ đặt nguồn làm bên trả phí, thêm blockhash và thêm instruction để chuyển lamport.

send-transaction.ts
import {
  // ...
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
} from "@solana/web3.js";

// ...

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (message) => setTransactionMessageFeePayer(sourceKeypair.address, message),
  (message) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message),
  (message) => appendTransactionMessageInstruction(instruction, message),
);

console.log("Transaction message created");

Hàm pipe, thường được dùng trong lập trình hàm, tạo ra một chuỗi hàm mà output của hàm trước trở thành input của hàm tiếp theo. Ở đây, hàm này xây dựng transaction message theo từng bước, áp dụng các phép biến đổi như đặt bên trả phí và vòng đời, đồng thời thêm instruction.

Khởi tạo transaction message:

createTransactionMessage({ version: 0 }) bắt đầu với một transaction message cơ bản.

Đặt bên trả phí:

message => setTransactionMessageFeePayer(fromKeypair.address, message) thêm địa chỉ của bên trả phí.

Đặt vòng đời bằng blockhash

message => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message) sử dụng blockhash mới nhất để đảm bảo transaction hợp lệ trong một khoảng thời gian.

Thêm instruction chuyển

message => appendTransactionMessageInstruction(instruction, message) nối thêm hành động (ví dụ: chuyển lamport) vào message.

Mỗi arrow function message => (...) sửa đổi rồi chuyển message đã cập nhật sang bước tiếp theo, từ đó tạo ra một transaction message mới hoàn chỉnh.

Ký transaction

Chúng ta sẽ ký transaction bằng signer đã chỉ định, tức Keypair nguồn.

send-transaction.ts
import {
  // ...
  signTransactionMessageWithSigners,
} from "@solana/web3.js";
// ...

/**
 * STEP 2: SIGN THE TRANSACTION
 */
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
console.log("Transaction signed");

Ước tính phí ưu tiên

Ở bước này, chúng ta có thể tiếp tục gửi và xác nhận transaction. Tuy nhiên, nên tối ưu transaction bằng cách đặt phí ưu tiên và điều chỉnh compute unit. Những biện pháp tối ưu này giúp tăng tỷ lệ thành công và giảm thời gian xác nhận transaction, đặc biệt khi mạng bị tắc nghẽn.

Để đặt phí ưu tiên, chúng ta sẽ dùng Priority Fee API của Helius. API này yêu cầu transaction đã được serialize ở định dạng Base64. Mặc dù API cũng hỗ trợ mã hóa Base58, SDK hiện tại trực tiếp cung cấp transaction ở định dạng Base64, giúp đơn giản hóa quy trình.

send-transaction.ts
import {
  // ...
  getBase64EncodedWireTransaction,
} from "@solana/web3.js";

/**
 * STEP 3: GET PRIORITY FEE FROM SIGNED TRANSACTION
 */

const base64EncodedWireTransaction = getBase64EncodedWireTransaction(signedTransaction);

const response = await fetch(rpc_url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "helius-example",
    method: "getPriorityFeeEstimate",
    params: [
      {
        transaction: base64EncodedWireTransaction,
        options: {
          transactionEncoding: "base64",
          priorityLevel: "High",
        },
      },
    ],
  }),
});
const { result } = await response.json();
const priorityFee = result.priorityFeeEstimate;
console.log("Setting priority fee to ", priorityFee);

Đặt priorityLevel thành High thường là đủ. Tuy nhiên, việc triển khai các chiến lược phí ưu tiên nâng cao, chẳng hạn như sử dụng transaction đã serialize và account key, có thể cải thiện đáng kể tỷ lệ thành công của transaction khi mạng bị tắc nghẽn.

Tối ưu compute unit

Tiếp theo, chúng ta sẽ ước tính số compute unit thực tế mà transaction message sử dụng.

Sau đó, chúng ta thêm khoảng đệm 10% bằng cách nhân giá trị này với 1.1. Khoảng đệm này tính đến compute unit được phí ưu tiên và các instruction compute unit bổ sung sử dụng, những thành phần sẽ được thêm vào sau.

Một số instruction, chẳng hạn như chuyển lamport, có thể có mức ước tính compute unit thấp hơn. Để đảm bảo đủ tài nguyên, chúng ta đã thêm một biện pháp bảo vệ nhằm đặt compute unit tối thiểu là 1000 nếu giá trị ước tính thấp hơn ngưỡng này.

send-transaction.ts
import {
  // ...
  getComputeUnitEstimateForTransactionMessageFactory,
} from "@solana/web3.js";

/**
 * STEP 4: OPTIMIZE COMPUTE UNITS
 */
const getComputeUnitEstimateForTransactionMessage = getComputeUnitEstimateForTransactionMessageFactory({
  rpc,
});
// Request an estimate of the actual compute units this message will consume.
let computeUnitsEstimate = await getComputeUnitEstimateForTransactionMessage(transactionMessage);
computeUnitsEstimate = computeUnitsEstimate < 1000 ? 1000 : Math.ceil(computeUnitsEstimate * 1.1);
console.log("Setting compute units to ", computeUnitsEstimate);

Xây dựng lại và ký transaction

Giờ đây, chúng ta đã có phí ưu tiên và compute unit cần thiết cho transaction này. Vì transaction đã được ký nên không thể trực tiếp thêm instruction mới. Thay vào đó, chúng ta sẽ xây dựng lại toàn bộ transaction message với một blockhash mới.

Blockhash chỉ hợp lệ trong khoảng 1–2 phút, còn việc lấy phí ưu tiên và compute unit cần một khoảng thời gian. Để tránh nguy cơ blockhash hết hạn trong lúc gửi transaction, lấy blockhash mới khi xây dựng lại transaction sẽ an toàn hơn.

Trong transaction được xây dựng lại này, chúng ta sẽ thêm hai instruction bổ sung:

  1. Một instruction để đặt phí ưu tiên; và
  2. Một instruction khác để đặt compute unit

Cuối cùng, chúng ta sẽ ký transaction đã cập nhật này để chuẩn bị gửi đi:

send-transaction.ts
import {
  // ...
  appendTransactionMessageInstructions,
} from "@solana/web3.js";
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";

/**
 * STEP 5: REBUILD AND SIGN FINAL TRANSACTION
 */
const { value: finalLatestBlockhash } = await rpc.getLatestBlockhash().send();

const finalTransactionMessage = appendTransactionMessageInstructions(
  [
    getSetComputeUnitPriceInstruction({ microLamports: priorityFee }),
    getSetComputeUnitLimitInstruction({ units: computeUnitsEstimate }),
  ],
  transactionMessage,
);

setTransactionMessageLifetimeUsingBlockhash(finalLatestBlockhash, finalTransactionMessage);

const finalSignedTransaction = await signTransactionMessageWithSigners(finalTransactionMessage);
console.log("Rebuilt the transaction and signed it");

Gửi và xác nhận transaction

Tiếp theo, transaction đã ký được gửi và xác nhận bằng hàm sendAndConfirmTransaction.

Mức commitment được đặt thành confirmed, nhất quán với blockhash đã lấy trước đó, còn maxRetries được đặt thành 0. Tùy chọn skipPreflight được đặt thành true, bỏ qua kiểm tra preflight để thực thi nhanh hơn; tuy nhiên, chỉ nên sử dụng cách này khi bạn chắc chắn chữ ký transaction đã được xác minh và không có lỗi nào khác.

Hàm sendAndConfirmTransaction đã được tạo trước đó bằng cách cung cấp cả URL RPC và URL subscription RPC. Việc sử dụng URL subscription RPC giúp kiểm tra trạng thái transaction, loại bỏ nhu cầu polling thủ công.

Trong phần xử lý lỗi, mã kiểm tra các lỗi xảy ra trong quá trình kiểm tra preflight. Vì chúng ta đặt skipPreflight thành true, bước kiểm tra này là dư thừa. Tuy nhiên, nó sẽ hữu ích nếu bạn không đặt giá trị này thành true.

send-transaction.ts
import {
  getSignatureFromTransaction,
  isSolanaError,
  SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
} from "@solana/web3.js";
import { getSystemErrorMessage, isSystemError } from "@solana-program/system";

/**
 * STEP 6: SEND AND CONFIRM THE FINAL TRANSACTION
 */

console.log("Sending and confirming transaction");
await sendAndConfirmTransaction(finalSignedTransaction, {
  commitment: "confirmed",
  maxRetries: 0n,
  skipPreflight: true,
});
console.log("Transfer confirmed: ", getSignatureFromTransaction(finalSignedTransaction));

Chạy mã

Cuối cùng, chúng ta có thể chạy mã: npx esrun send-transaction.ts

Kết luận

Việc phát hành Web3.js 2.0 SDK của Solana là một bản cập nhật mang tính chuyển đổi, giúp nhà phát triển tạo ra các ứng dụng nhanh hơn, hiệu quả hơn và có khả năng mở rộng trên Solana. Bằng cách áp dụng các tiêu chuẩn JavaScript hiện đại và giới thiệu những tính năng như API mật mã gốc, khả năng tree-shaking và client TypeScript được tạo tự động, SDK cải thiện đáng kể trải nghiệm của nhà phát triển và hiệu năng ứng dụng.

Mã nguồn đầy đủ của ví dụ lập trình có trên GitHub.

Nếu đã đọc đến đây, cảm ơn bạn, anon! Hãy nhập địa chỉ email bên dưới để không bao giờ bỏ lỡ thông tin cập nhật về những điểm mới trên Solana. Sẵn sàng tìm hiểu sâu hơn? Khám phá các bài viết mới nhất trên blog Helius và tiếp tục hành trình Solana ngay hôm nay.

Tài nguyên

Đă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

Hình ảnh phóng to