Thêm Solana vào Sàn Giao Dịch của Bạn

Hướng dẫn này mô tả cách thêm token gốc SOL của Solana vào sàn giao dịch tiền điện tử của bạn.

Thiết Lập Node

Chúng tôi khuyến nghị mạnh mẽ việc thiết lập ít nhất hai node trên máy tính/phiên bản đám mây hiệu năng cao, nâng cấp lên phiên bản mới kịp thời, và theo dõi hoạt động dịch vụ bằng công cụ giám sát tích hợp.

Thiết lập này cho phép bạn:

  • có một cổng kết nối tự quản lý đến cụm mainnet của Solana để lấy dữ liệu và gửi các giao dịch rút tiền
  • có toàn quyền kiểm soát lượng dữ liệu khối lịch sử được lưu giữ
  • duy trì tính khả dụng của dịch vụ ngay cả khi một node gặp sự cố

Các node Solana đòi hỏi sức mạnh tính toán tương đối cao để xử lý các khối nhanh và TPS cao của chúng tôi. Để biết các yêu cầu cụ thể, vui lòng xem khuyến nghị phần cứng.

Để chạy một api node:

  1. Cài đặt bộ công cụ dòng lệnh Solana
  2. Khởi động validator với ít nhất các tham số sau:
solana-validator \
--ledger <LEDGER_PATH> \
--identity <VALIDATOR_IDENTITY_KEYPAIR> \
--entrypoint <CLUSTER_ENTRYPOINT> \
--expected-genesis-hash <EXPECTED_GENESIS_HASH> \
--rpc-port 8899 \
--no-voting \
--enable-rpc-transaction-history \
--limit-ledger-size \
--known-validator <VALIDATOR_ADDRESS> \
--only-known-rpc

Tùy chỉnh --ledger theo vị trí lưu trữ ledger mong muốn của bạn, và --rpc-port theo cổng bạn muốn mở.

Các tham số --entrypoint--expected-genesis-hash đều dành riêng cho cụm bạn đang tham gia. Tham số hiện tại cho Mainnet

Tham số --limit-ledger-size cho phép bạn chỉ định số lượng shred ledger mà node của bạn lưu giữ trên đĩa. Nếu bạn không bao gồm tham số này, validator sẽ giữ toàn bộ ledger cho đến khi hết dung lượng đĩa. Giá trị mặc định cố gắng giữ mức sử dụng đĩa ledger dưới 500GB. Có thể yêu cầu sử dụng đĩa nhiều hơn hoặc ít hơn bằng cách thêm đối số vào --limit-ledger-size nếu muốn. Kiểm tra solana-validator --help để biết giá trị giới hạn mặc định được sử dụng bởi --limit-ledger-size. Thêm thông tin về việc chọn giá trị giới hạn tùy chỉnh có tại đây.

Chỉ định một hoặc nhiều tham số --known-validator có thể bảo vệ bạn khỏi việc khởi động từ một snapshot độc hại. Tìm hiểu thêm về giá trị của việc khởi động với các validator đã biết

Các tham số tùy chọn cần xem xét:

  • --private-rpc ngăn cổng RPC của bạn không bị công bố để các node khác sử dụng
  • --rpc-bind-address cho phép bạn chỉ định một địa chỉ IP khác để gắn kết cổng RPC

Khởi Động Lại Tự Động và Giám Sát

Chúng tôi khuyến nghị cấu hình mỗi node của bạn để tự động khởi động lại khi thoát, nhằm đảm bảo bạn bỏ lỡ ít dữ liệu nhất có thể. Chạy phần mềm Solana như một dịch vụ systemd là một lựa chọn tuyệt vời.

Để giám sát, chúng tôi cung cấp solana-watchtower, có thể giám sát validator của bạn và phát hiện khi tiến trình solana-validator không hoạt động bình thường. Công cụ này có thể được cấu hình trực tiếp để gửi cảnh báo qua Slack, Telegram, Discord, hoặc Twilio. Để biết chi tiết, chạy solana-watchtower --help.

solana-watchtower --validator-identity <YOUR VALIDATOR IDENTITY>

Bạn có thể tìm thêm thông tin về các phương pháp hay nhất cho Solana Watchtower tại đây trong tài liệu.

Thông Báo Phát Hành Phần Mềm Mới

Chúng tôi phát hành phần mềm mới thường xuyên (khoảng 1 bản phát hành/tuần). Đôi khi các phiên bản mới hơn bao gồm các thay đổi giao thức không tương thích, đòi hỏi cập nhật phần mềm kịp thời để tránh lỗi trong quá trình xử lý khối.

Các thông báo phát hành chính thức của chúng tôi cho tất cả các loại bản phát hành (thông thường và bảo mật) được thông báo qua kênh discord có tên #mb-announcement (mb là viết tắt của mainnet-beta).

Giống như các validator có stake, chúng tôi kỳ vọng bất kỳ validator nào do sàn giao dịch vận hành cũng được cập nhật sớm nhất có thể trong vòng một hoặc hai ngày làm việc sau thông báo phát hành thông thường. Đối với các bản phát hành liên quan đến bảo mật, có thể cần hành động khẩn cấp hơn.

Tính Liên Tục của Ledger

Theo mặc định, mỗi node của bạn sẽ khởi động từ một snapshot được cung cấp bởi một trong các validator đã biết của bạn. Snapshot này phản ánh trạng thái hiện tại của chuỗi, nhưng không chứa ledger lịch sử đầy đủ. Nếu một trong các node của bạn thoát và khởi động từ một snapshot mới, có thể có khoảng trống trong ledger trên node đó. Để ngăn chặn vấn đề này, hãy thêm tham số --no-snapshot-fetch vào lệnh solana-validator của bạn để nhận dữ liệu ledger lịch sử thay vì một snapshot.

Không truyền tham số --no-snapshot-fetch trong lần khởi động đầu tiên vì không thể khởi động node từ đầu tất cả đường từ khối genesis. Thay vào đó, hãy khởi động từ một snapshot trước, sau đó thêm tham số --no-snapshot-fetch cho các lần khởi động lại.

Điều quan trọng cần lưu ý là lượng ledger lịch sử có sẵn cho các node của bạn từ phần còn lại của mạng bị giới hạn tại bất kỳ thời điểm nào. Khi đã hoạt động, nếu các validator của bạn trải qua thời gian ngừng hoạt động đáng kể, chúng có thể không thể bắt kịp mạng và sẽ cần tải xuống một snapshot mới từ một validator đã biết. Khi làm như vậy, các validator của bạn sẽ có khoảng trống trong dữ liệu ledger lịch sử mà không thể lấp đầy.

Giảm Thiểu Phơi Bày Cổng của Validator

Validator yêu cầu nhiều cổng UDP và TCP phải mở cho lưu lượng đến từ tất cả các validator Solana khác. Mặc dù đây là chế độ hoạt động hiệu quả nhất và được khuyến nghị mạnh mẽ, nhưng có thể hạn chế validator chỉ yêu cầu lưu lượng đến từ một validator Solana khác.

Đầu tiên, thêm đối số --restricted-repair-only-mode. Điều này sẽ khiến validator hoạt động ở chế độ bị hạn chế, trong đó nó sẽ không nhận các đẩy từ phần còn lại của các validator, mà thay vào đó sẽ cần liên tục thăm dò các validator khác để lấy khối. Validator sẽ chỉ truyền các gói UDP đến các validator khác bằng cổng GossipServeR ("serve repair"), và chỉ nhận các gói UDP trên cổng GossipRepair của nó.

Cổng Gossip là hai chiều và cho phép validator của bạn duy trì liên lạc với phần còn lại của cụm. Validator của bạn truyền trên ServeR để thực hiện các yêu cầu sửa chữa nhằm lấy các khối mới từ phần còn lại của mạng, vì Turbine hiện đã bị vô hiệu hóa. Validator của bạn sau đó sẽ nhận các phản hồi sửa chữa trên cổng Repair từ các validator khác.

Để tiếp tục hạn chế validator chỉ yêu cầu khối từ một hoặc nhiều validator, trước tiên hãy xác định identity pubkey cho validator đó và thêm các đối số --gossip-pull-validator PUBKEY --repair-validator PUBKEY cho mỗi PUBKEY. Điều này sẽ khiến validator của bạn tiêu tốn tài nguyên của mỗi validator mà bạn thêm vào, vì vậy hãy thực hiện điều này một cách hạn chế và chỉ sau khi tham khảo với validator mục tiêu.

Validator của bạn bây giờ chỉ nên giao tiếp với các validator được liệt kê rõ ràng và chỉ trên các cổng Gossip, RepairServeR.

Thiết Lập Tài Khoản Nạp Tiền

Tài khoản Solana không yêu cầu bất kỳ khởi tạo onchain nào; một khi chúng chứa một lượng SOL, chúng tồn tại. Để thiết lập tài khoản nạp tiền cho sàn giao dịch của bạn, chỉ cần tạo một keypair Solana bằng bất kỳ công cụ ví nào của chúng tôi.

Chúng tôi khuyến nghị sử dụng một tài khoản nạp tiền riêng biệt cho mỗi người dùng của bạn.

Các tài khoản Solana phải được miễn rent bằng cách chứa SOL tương đương 2 năm rent. Để tìm số dư tối thiểu miễn rent cho các tài khoản nạp tiền của bạn, hãy truy vấn endpoint getMinimumBalanceForRentExemption:

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getMinimumBalanceForRentExemption",
"params": [0]
}'
Kết Quả
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }

Tài Khoản Ngoại Tuyến

Bạn có thể muốn giữ khóa cho một hoặc nhiều tài khoản thu thập ngoại tuyến để tăng cường bảo mật. Nếu vậy, bạn sẽ cần chuyển SOL đến các tài khoản nóng bằng cách sử dụng các phương thức ngoại tuyến của chúng tôi.

Lắng Nghe Các Khoản Nạp Tiền

Khi người dùng muốn nạp SOL vào sàn giao dịch của bạn, hãy hướng dẫn họ gửi chuyển khoản đến địa chỉ nạp tiền phù hợp.

Di Chuyển Giao Dịch Có Phiên Bản

Khi mạng Mainnet bắt đầu xử lý các giao dịch có phiên bản, các sàn giao dịch BẮT BUỘC phải thực hiện các thay đổi. Nếu không có thay đổi nào được thực hiện, việc phát hiện khoản nạp sẽ không còn hoạt động đúng nữa vì việc lấy một giao dịch có phiên bản hoặc một khối chứa các giao dịch có phiên bản sẽ trả về lỗi.

  • {"maxSupportedTransactionVersion": 0}

    Tham số maxSupportedTransactionVersion phải được thêm vào các yêu cầu getBlockgetTransaction để tránh gián đoạn việc phát hiện khoản nạp. Phiên bản giao dịch mới nhất là 0 và nên được chỉ định là giá trị phiên bản giao dịch được hỗ trợ tối đa.

Điều quan trọng là phải hiểu rằng các giao dịch có phiên bản cho phép người dùng tạo các giao dịch sử dụng một tập hợp khóa tài khoản khác được tải từ các bảng tra cứu địa chỉ onchain.

  • {"encoding": "jsonParsed"}

    Khi lấy các khối và giao dịch, hiện nay được khuyến nghị sử dụng mã hóa "jsonParsed" vì nó bao gồm tất cả các khóa tài khoản giao dịch (bao gồm cả những khóa từ bảng tra cứu) trong danh sách "accountKeys" của tin nhắn. Điều này giúp dễ dàng giải quyết các thay đổi số dư được mô tả chi tiết trong preBalances / postBalancespreTokenBalances / postTokenBalances.

    Nếu thay vào đó sử dụng mã hóa "json", các mục trong preBalances / postBalancespreTokenBalances / postTokenBalances có thể tham chiếu đến các khóa tài khoản KHÔNG có trong danh sách "accountKeys" và cần được giải quyết bằng cách sử dụng các mục "loadedAddresses" trong siêu dữ liệu giao dịch.

Thăm Dò Các Khối

Để theo dõi tất cả các tài khoản nạp tiền cho sàn giao dịch của bạn, hãy thăm dò từng khối đã xác nhận và kiểm tra các địa chỉ quan tâm, sử dụng dịch vụ JSON-RPC của node API Solana của bạn.

  • Để xác định khối nào có sẵn, hãy gửi yêu cầu getBlocks, truyền khối cuối cùng bạn đã xử lý làm tham số start-slot:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlocks",
"params": [160017005, 160017015]
}'
Kết Quả
{
"jsonrpc": "2.0",
"result": [
160017005, 160017006, 160017007, 160017012, 160017013, 160017014, 160017015
],
"id": 1
}

Không phải mọi slot đều tạo ra một khối, vì vậy có thể có khoảng trống trong chuỗi số nguyên.

  • Đối với mỗi khối, hãy yêu cầu nội dung của nó bằng yêu cầu getBlock:

Mẹo Lấy Khối

  • {"rewards": false}

Theo mặc định, các khối được lấy sẽ trả về thông tin về phí validator trên mỗi khối và phần thưởng staking tại các ranh giới epoch. Nếu bạn không cần thông tin này, hãy tắt nó bằng tham số "rewards".

  • {"transactionDetails": "accounts"}

Theo mặc định, các khối được lấy sẽ trả về nhiều thông tin giao dịch và siêu dữ liệu không cần thiết cho việc theo dõi số dư tài khoản. Hãy đặt tham số "transactionDetails" để tăng tốc độ lấy khối.

curl https://api.devnet.solana.com -X POST -H 'Content-Type: application/json' -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
166974442,
{
"encoding": "jsonParsed",
"maxSupportedTransactionVersion": 0,
"transactionDetails": "accounts",
"rewards": false
}
]
}'
Kết Quả
{
"jsonrpc": "2.0",
"result": {
"blockHeight": 157201607,
"blockTime": 1665070281,
"blockhash": "HKhao674uvFc4wMK1Cm3UyuuGbKExdgPFjXQ5xtvsG3o",
"parentSlot": 166974441,
"previousBlockhash": "98CNLU4rsYa2HDUyp7PubU4DhwYJJhSX9v6pvE7SWsAo",
"transactions": [
... (omit)
{
"meta": {
"err": null,
"fee": 5000,
"postBalances": [
1110663066,
1,
1040000000
],
"postTokenBalances": [],
"preBalances": [
1120668066,
1,
1030000000
],
"preTokenBalances": [],
"status": {
"Ok": null
}
},
"transaction": {
"accountKeys": [
{
"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "11111111111111111111111111111111",
"signer": false,
"source": "transaction",
"writable": false
},
{
"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"signer": false,
"source": "lookupTable",
"writable": true
}
],
"signatures": [
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"
]
},
"version": 0
},
... (omit)
]
},
"id": 1
}

Các trường preBalancespostBalances cho phép bạn theo dõi sự thay đổi số dư trong mỗi tài khoản mà không cần phân tích toàn bộ giao dịch. Chúng liệt kê số dư ban đầu và số dư cuối của mỗi tài khoản tính bằng lamport, được lập chỉ mục theo danh sách accountKeys. Ví dụ: nếu địa chỉ nạp tiền cần quan tâm là G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o, giao dịch này thể hiện một lần chuyển 1.040.000.000 - 1.030.000.000 = 10.000.000 lamport = 0,01 SOL

Nếu bạn cần thêm thông tin về loại giao dịch hoặc các chi tiết khác, bạn có thể yêu cầu block từ RPC ở định dạng nhị phân và phân tích nó bằng Rust SDK hoặc Javascript SDK của chúng tôi.

Lịch Sử Địa Chỉ

Bạn cũng có thể truy vấn lịch sử giao dịch của một địa chỉ cụ thể. Đây thường không phải là phương pháp khả thi để theo dõi tất cả các địa chỉ nạp tiền qua tất cả các slot, nhưng có thể hữu ích để kiểm tra một vài tài khoản trong một khoảng thời gian cụ thể.

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN",
{
"limit": 3
}
]
}'
Kết Quả
{
"jsonrpc": "2.0",
"result": [
{
"blockTime": 1662064640,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "3EDRvnD5TbbMS2mCusop6oyHLD8CgnjncaYQd5RXpgnjYUXRCYwiNPmXb6ZG5KdTK4zAaygEhfdLoP7TDzwKBVQp",
"slot": 148697216
},
{
"blockTime": 1662064434,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "4rPQ5wthgSP1kLdLqcRgQnkYkPAZqjv5vm59LijrQDSKuL2HLmZHoHjdSLDXXWFwWdaKXUuryRBGwEvSxn3TQckY",
"slot": 148696843
},
{
"blockTime": 1662064341,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "36Q383JMiqiobuPV9qBqy41xjMsVnQBm9rdZSdpbrLTGhSQDTGZJnocM4TQTVfUGfV2vEX9ZB3sex6wUBUWzjEvs",
"slot": 148696677
}
],
"id": 1
}
  • Với mỗi chữ ký được trả về, hãy lấy chi tiết giao dịch bằng cách gửi yêu cầu getTransaction:
curl https://api.devnet.solana.com -X POST -H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0",
"id":1,
"method":"getTransaction",
"params":[
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM",
{
"encoding":"jsonParsed",
"maxSupportedTransactionVersion":0
}
]
}'
Kết Quả
{
"jsonrpc": "2.0",
"result": {
"blockTime": 1665070281,
"meta": {
"err": null,
"fee": 5000,
"innerInstructions": [],
"logMessages": [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"postBalances": [1110663066, 1, 1040000000],
"postTokenBalances": [],
"preBalances": [1120668066, 1, 1030000000],
"preTokenBalances": [],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 166974442,
"transaction": {
"message": {
"accountKeys": [
{
"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "11111111111111111111111111111111",
"signer": false,
"source": "transaction",
"writable": false
},
{
"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"signer": false,
"source": "lookupTable",
"writable": true
}
],
"addressTableLookups": [
{
"accountKey": "4syr5pBaboZy4cZyF6sys82uGD7jEvoAP2ZMaoich4fZ",
"readonlyIndexes": [],
"writableIndexes": [3]
}
],
"instructions": [
{
"parsed": {
"info": {
"destination": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"lamports": 10000000,
"source": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde"
},
"type": "transfer"
},
"program": "system",
"programId": "11111111111111111111111111111111"
}
],
"recentBlockhash": "BhhivDNgoy4L5tLtHb1s3TP19uUXqKiy4FfUR34d93eT"
},
"signatures": [
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"
]
},
"version": 0
},
"id": 1
}

Gửi Lệnh Rút Tiền

Để đáp ứng yêu cầu rút SOL của người dùng, bạn phải tạo một giao dịch chuyển tiền Solana và gửi nó đến api node để chuyển tiếp đến cluster của bạn.

Đồng Bộ

Gửi một lệnh chuyển tiền đồng bộ đến cluster Solana cho phép bạn dễ dàng đảm bảo rằng một lần chuyển tiền đã thành công và được cluster xác nhận hoàn tất.

Công cụ dòng lệnh của Solana cung cấp một lệnh đơn giản, solana transfer, để tạo, gửi và xác nhận các giao dịch chuyển tiền. Theo mặc định, phương thức này sẽ chờ và theo dõi tiến trình trên stderr cho đến khi giao dịch được cluster xác nhận hoàn tất. Nếu giao dịch thất bại, nó sẽ báo cáo bất kỳ lỗi giao dịch nào.

solana transfer <USER_ADDRESS> <AMOUNT> --allow-unfunded-recipient --keypair <KEYPAIR> --url http://localhost:8899

Solana Javascript SDK cung cấp cách tiếp cận tương tự cho hệ sinh thái JS. Sử dụng SystemProgram để xây dựng một giao dịch chuyển tiền và gửi nó bằng phương thức sendAndConfirmTransaction.

Bất Đồng Bộ

Để linh hoạt hơn, bạn có thể gửi các lệnh rút tiền một cách bất đồng bộ. Trong các trường hợp này, bạn có trách nhiệm xác minh rằng giao dịch đã thành công và được cluster xác nhận hoàn tất.

Lưu ý: Mỗi giao dịch chứa một blockhash gần đây để chỉ ra tính hợp lệ của nó. Điều quan trọng là phải chờ cho đến khi blockhash này hết hạn trước khi thử lại một lệnh rút tiền có vẻ chưa được cluster xác nhận hoặc xác nhận hoàn tất. Nếu không, bạn có nguy cơ chi tiêu gấp đôi. Xem thêm về thời hạn blockhash bên dưới.

Đầu tiên, lấy một blockhash gần đây bằng cách sử dụng endpoint getFees hoặc lệnh CLI:

solana fees --url http://localhost:8899

Trong công cụ dòng lệnh, truyền đối số --no-wait để gửi một lệnh chuyển tiền bất đồng bộ và bao gồm blockhash gần đây của bạn với đối số --blockhash:

solana transfer <USER_ADDRESS> <AMOUNT> --no-wait --allow-unfunded-recipient --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:8899

Bạn cũng có thể xây dựng, ký và tuần tự hóa giao dịch theo cách thủ công, và gửi nó đến cluster bằng endpoint JSON-RPC sendTransaction.

Xác Nhận Giao Dịch & Tính Hoàn Tất

Lấy trạng thái của một loạt giao dịch bằng cách sử dụng endpoint JSON-RPC getSignatureStatuses. Trường confirmations báo cáo có bao nhiêu block đã được xác nhận đã trôi qua kể từ khi giao dịch được xử lý. Nếu confirmations: null, giao dịch đã được xác nhận hoàn tất.

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0",
"id":1,
"method":"getSignatureStatuses",
"params":[
[
"4cdd1oX7cfVALfr26tP52BZ6cSzrgnNGtYD7BFhm6FFeZV5sPTnRvg6NRn8yC6DbEikXcrNChBM5vVJnTgKhGhVu",
"5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"
]
]
}'
Kết Quả
{
"jsonrpc": "2.0",
"result": {
"context": {
"slot": 82
},
"value": [
{
"slot": 72,
"confirmations": 10,
"err": null,
"status": {
"Ok": null
}
},
{
"slot": 48,
"confirmations": null,
"err": null,
"status": {
"Ok": null
}
}
]
},
"id": 1
}

Thời Hạn Blockhash

Bạn có thể kiểm tra xem một blockhash cụ thể còn hợp lệ hay không bằng cách gửi yêu cầu getFeeCalculatorForBlockhash với blockhash làm tham số. Nếu giá trị phản hồi là null, blockhash đã hết hạn và giao dịch rút tiền sử dụng blockhash đó sẽ không bao giờ thành công.

Xác Thực Địa Chỉ Tài Khoản Do Người Dùng Cung Cấp Cho Lệnh Rút Tiền

Vì các lệnh rút tiền không thể hoàn tác, đây có thể là một thực hành tốt để xác thực địa chỉ tài khoản do người dùng cung cấp trước khi cho phép rút tiền nhằm ngăn ngừa tổn thất tiền của người dùng do sơ suất.

Xác Minh Cơ Bản

Địa chỉ Solana là một mảng 32 byte, được mã hóa bằng bảng chữ cái base58 của bitcoin. Điều này tạo ra một chuỗi văn bản ASCII khớp với biểu thức chính quy sau:

[1-9A-HJ-NP-Za-km-z]{32,44}

Kiểm tra này tự nó là chưa đủ vì các địa chỉ Solana không có checksum, nên các lỗi đánh máy không thể được phát hiện. Để xác thực thêm đầu vào của người dùng, chuỗi có thể được giải mã và độ dài của mảng byte kết quả được xác nhận là 32. Tuy nhiên, có một số địa chỉ có thể giải mã thành 32 byte mặc dù có lỗi đánh máy như thiếu một ký tự, đảo ngược ký tự và bỏ qua chữ hoa/thường

Xác Minh Nâng Cao

Do lỗ hổng với các lỗi đánh máy được mô tả ở trên, nên truy vấn số dư cho các địa chỉ rút tiền tiềm năng và nhắc người dùng xác nhận ý định của họ nếu phát hiện số dư khác không.

Kiểm Tra pubkey ed25519 Hợp Lệ

Địa chỉ của một tài khoản thông thường trong Solana là một chuỗi mã hóa Base58 của khóa công khai ed25519 256-bit. Không phải tất cả các mẫu bit đều là khóa công khai hợp lệ cho đường cong ed25519, vì vậy có thể đảm bảo rằng địa chỉ tài khoản do người dùng cung cấp ít nhất là các pubkey ed25519 hợp lệ.

Java

Đây là một ví dụ Java về việc xác thực địa chỉ do người dùng cung cấp là một khóa công khai ed25519 hợp lệ:

Mẫu code sau đây giả định rằng bạn đang sử dụng Maven.

pom.xml:

<repositories>
...
<repository>
<id>spring</id>
<url>https://repo.spring.io/libs-release/</url>
</repository>
</repositories>
...
<dependencies>
...
<dependency>
<groupId>io.github.novacrypto</groupId>
<artifactId>Base58</artifactId>
<version>0.1.3</version>
</dependency>
<dependency>
<groupId>cafe.cryptography</groupId>
<artifactId>curve25519-elisabeth</artifactId>
<version>0.1.0</version>
</dependency>
<dependencies>
import io.github.novacrypto.base58.Base58;
import cafe.cryptography.curve25519.CompressedEdwardsY;
public class PubkeyValidator
{
public static boolean verifyPubkey(String userProvidedPubkey)
{
try {
return _verifyPubkeyInternal(userProvidedPubkey);
} catch (Exception e) {
return false;
}
}
public static boolean _verifyPubkeyInternal(String maybePubkey) throws Exception
{
byte[] bytes = Base58.base58Decode(maybePubkey);
return !(new CompressedEdwardsY(bytes)).decompress().isSmallOrder();
}
}

Số Tiền Nạp & Rút Tối Thiểu

Mỗi lần nạp và rút SOL phải lớn hơn hoặc bằng số dư miễn phí rent tối thiểu cho tài khoản tại địa chỉ ví (một tài khoản SOL cơ bản không chứa dữ liệu), hiện tại là: 0,000890880 SOL

Tương tự, mỗi tài khoản nạp tiền phải chứa ít nhất số dư này.

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getMinimumBalanceForRentExemption",
"params": [0]
}'
Kết Quả
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }

Phí Ưu Tiên và Đơn Vị Tính Toán

Trong các giai đoạn có nhu cầu cao, một giao dịch có thể hết hạn trước khi validator đưa các giao dịch đó vào block của họ vì họ đã chọn các giao dịch khác có giá trị kinh tế cao hơn. Các giao dịch hợp lệ trên Solana có thể bị trì hoãn hoặc bị loại bỏ nếu Phí Ưu Tiên không được triển khai đúng cách.

Phí Ưu Tiên là các khoản phí bổ sung có thể được thêm vào trên Phí Giao Dịch Cơ Bản để đảm bảo giao dịch được đưa vào các block trong các tình huống này và giúp đảm bảo khả năng giao nhận.

Các khoản phí ưu tiên này được thêm vào giao dịch bằng cách thêm một lệnh Compute Budget đặc biệt đặt mức phí ưu tiên mong muốn cần thanh toán.

Lưu Ý Quan Trọng

Việc không triển khai các lệnh này có thể dẫn đến gián đoạn mạng và các giao dịch bị loại bỏ. Chúng tôi khuyến nghị mạnh mẽ rằng mọi sàn giao dịch hỗ trợ Solana nên sử dụng phí ưu tiên để tránh gián đoạn.

Phí Ưu Tiên Là Gì?

Phí Ưu Tiên được tính bằng micro-lamport trên mỗi Đơn Vị Tính Toán (ví dụ: các khoản nhỏ của SOL) được thêm vào đầu giao dịch để làm cho chúng có sức hấp dẫn kinh tế đối với các nút validator để đưa vào các block trên mạng.

Phí Ưu Tiên Nên Là Bao Nhiêu?

Phương pháp thiết lập phí ưu tiên của bạn nên bao gồm việc truy vấn các phí ưu tiên gần đây để đặt một mức phí có khả năng hấp dẫn đối với mạng. Sử dụng phương thức RPC getRecentPrioritizationFees, bạn có thể truy vấn các phí ưu tiên cần thiết để đưa giao dịch vào một block gần đây.

Chiến lược định giá cho các khoản phí ưu tiên này sẽ thay đổi tùy theo trường hợp sử dụng của bạn. Không có cách chính tắc nào để thực hiện điều này. Một chiến lược để đặt Phí Ưu Tiên của bạn có thể là tính toán tỷ lệ thành công giao dịch của bạn và sau đó tăng Phí Ưu Tiên dựa trên truy vấn đến API phí giao dịch gần đây và điều chỉnh cho phù hợp. Giá cả cho Phí Ưu Tiên sẽ biến động dựa trên hoạt động trên mạng và các đề nghị của những người tham gia khác, chỉ có thể biết sau khi thực tế xảy ra.

Một thách thức khi sử dụng lệnh gọi API getRecentPrioritizationFees là nó có thể chỉ trả về mức phí thấp nhất cho mỗi block. Điều này thường sẽ bằng không, đây không phải là một xấp xỉ hoàn toàn hữu ích về mức Phí Ưu Tiên cần sử dụng để tránh bị các nút validator từ chối.

API getRecentPrioritizationFees nhận các pubkey của tài khoản làm tham số, và sau đó trả về mức cao nhất trong số các phí ưu tiên tối thiểu cho các tài khoản này. Khi không có tài khoản nào được chỉ định, API sẽ trả về mức phí thấp nhất để đưa vào block, thường là không (trừ khi block đầy).

Các sàn giao dịch và ứng dụng nên truy vấn endpoint RPC với các tài khoản mà một giao dịch sẽ write-lock. Endpoint RPC sẽ trả về max(account_1_min_fee, account_2_min_fee, ... account_n_min_fee), đây nên là điểm cơ sở để người dùng đặt phí ưu tiên cho giao dịch đó.

Có nhiều cách tiếp cận khác nhau để đặt Phí Ưu Tiên và có một số API của bên thứ ba có sẵn để xác định mức phí tốt nhất cần áp dụng. Do tính chất động của mạng, sẽ không có cách "hoàn hảo" nào để định giá Phí Ưu Tiên của bạn và cần phân tích cẩn thận trước khi chọn hướng đi tiếp theo.

Cách Triển Khai Phí Ưu Tiên

Thêm phí ưu tiên vào một giao dịch bao gồm việc thêm hai lệnh Compute Budget vào đầu một giao dịch nhất định:

  • một lệnh để đặt giá đơn vị tính toán, và
  • một lệnh khác để đặt giới hạn đơn vị tính toán

Tại đây, bạn cũng có thể tìm thấy một hướng dẫn chi tiết hơn dành cho nhà phát triển về cách sử dụng phí ưu tiên bao gồm thêm thông tin về việc triển khai phí ưu tiên.

Tạo một lệnh setComputeUnitPrice để thêm Phí Ưu Tiên trên Phí Giao Dịch Cơ Bản (5.000 Lamport).

// import { ComputeBudgetProgram } from "@solana/web3.js"
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: number });

Giá trị được cung cấp tính bằng micro-lamport sẽ được nhân với ngân sách Đơn Vị Tính Toán (CU) để xác định Phí Ưu Tiên tính bằng Lamport. Ví dụ: nếu ngân sách CU của bạn là 1M CU và bạn thêm 1 microLamport/CU, Phí Ưu Tiên sẽ là 1 lamport (1M * 0. 000001). Tổng phí khi đó sẽ là 5.001 lamport.

Để đặt ngân sách đơn vị tính toán mới cho giao dịch, hãy tạo một lệnh setComputeUnitLimit

// import { ComputeBudgetProgram } from "@solana/web3.js"
ComputeBudgetProgram.setComputeUnitLimit({ units: number });

Giá trị units được cung cấp sẽ thay thế giá trị ngân sách tính toán mặc định của Solana runtime.

Đặt Lượng CU Tối Thiểu Cần Thiết Cho Giao Dịch

Các giao dịch nên yêu cầu lượng đơn vị tính toán (CU) tối thiểu cần thiết cho quá trình thực thi để tối đa hóa thông lượng và giảm thiểu tổng phí.

Bạn có thể biết lượng CU mà một giao dịch tiêu thụ bằng cách gửi giao dịch trên một cluster Solana khác, chẳng hạn như devnet. Ví dụ: một lệnh chuyển token đơn giản tốn 300 CU.

// import { ... } from "@solana/web3.js"
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
// note: set this to be the lowest actual CU consumed by the transaction
units: 300
});
const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 1
});
const transaction = new Transaction()
.add(modifyComputeUnits)
.add(addPriorityFee)
.add(
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: toAccount,
lamports: 10000000
})
);

Phí Ưu Tiên và Durable Nonces

Nếu thiết lập của bạn sử dụng Durable Nonce Transactions, điều quan trọng là phải triển khai đúng cách Prioritization Fees kết hợp với Durable Transaction Nonces để đảm bảo giao dịch thành công. Nếu không làm vậy, các giao dịch Durable Nonce dự định sẽ không được nhận diện đúng cách.

Nếu bạn ĐANG sử dụng Durable Transaction Nonces, lệnh AdvanceNonceAccount BẮT BUỘC phải được chỉ định ĐẦU TIÊN trong danh sách lệnh, ngay cả khi các lệnh compute budget được sử dụng để chỉ định phí ưu tiên.

Bạn có thể tìm thấy một ví dụ mã cụ thể sử dụng durable nonces và phí ưu tiên cùng nhau trong hướng dẫn dành cho nhà phát triển này.

Hỗ trợ Tiêu chuẩn SPL Token

SPL Token là tiêu chuẩn để tạo và trao đổi token wrapped/synthetic trên blockchain Solana.

Quy trình làm việc của SPL Token tương tự như token SOL gốc, nhưng có một vài điểm khác biệt sẽ được thảo luận trong phần này.

Token Mints

Mỗi loại SPL Token được khai báo bằng cách tạo một mint account. Tài khoản này lưu trữ metadata mô tả các tính năng của token như nguồn cung, số chữ số thập phân, và các quyền hạn khác nhau kiểm soát mint. Mỗi SPL Token account tham chiếu đến mint liên kết của nó và chỉ có thể tương tác với SPL Token thuộc loại đó.

Cài đặt Công cụ CLI spl-token

Các SPL Token account được truy vấn và chỉnh sửa bằng tiện ích dòng lệnh spl-token. Các ví dụ được cung cấp trong phần này phụ thuộc vào việc đã cài đặt nó trên hệ thống cục bộ.

spl-token được phân phối từ Rust crates.io thông qua tiện ích dòng lệnh cargo của Rust. Phiên bản mới nhất của cargo có thể được cài đặt bằng một lệnh đơn giản cho nền tảng của bạn tại rustup.rs. Sau khi cargo được cài đặt, spl-token có thể được lấy bằng lệnh sau:

cargo install spl-token-cli

Sau đó bạn có thể kiểm tra phiên bản đã cài đặt để xác nhận

spl-token --version

Kết quả sẽ trông giống như

spl-token-cli 2.0.1

Tạo Tài khoản

Các SPL Token account có thêm các yêu cầu mà các tài khoản System Program gốc không có:

  1. Các SPL Token account phải được tạo trước khi có thể nạp một lượng token. Token account có thể được tạo một cách rõ ràng bằng lệnh spl-token create-account, hoặc ngầm định bằng lệnh spl-token transfer --fund-recipient ....
  2. Các SPL Token account phải duy trì trạng thái miễn phí tiền thuê trong suốt thời gian tồn tại của chúng và do đó yêu cầu một lượng nhỏ token SOL gốc được nạp khi tạo tài khoản. Đối với SPL Token account, số lượng này là 0.00203928 SOL (2.039.280 lamport).

Dòng Lệnh

Để tạo một SPL Token account với các thuộc tính sau:

  1. Được liên kết với mint đã cho
  2. Thuộc sở hữu của keypair tài khoản cấp vốn
spl-token create-account <TOKEN_MINT_ADDRESS>

Ví dụ

spl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir

Cho kết quả tương tự:

Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV
Signature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7

Hoặc để tạo một SPL Token account với một keypair cụ thể:

solana-keygen new -o token-account.json
spl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir token-account.json

Cho kết quả tương tự:

Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV
Signature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7

Kiểm tra Số dư Tài khoản

Dòng Lệnh

spl-token balance <TOKEN_ACCOUNT_ADDRESS>

Ví dụ

solana balance 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV

Cho kết quả tương tự:

0

Chuyển Token

Tài khoản nguồn cho một lần chuyển là token account thực tế chứa số lượng token.

Tuy nhiên, địa chỉ người nhận có thể là một tài khoản ví thông thường. Nếu associated token account cho mint đã cho chưa tồn tại đối với ví đó, việc chuyển sẽ tạo ra nó với điều kiện đối số --fund-recipient được cung cấp.

Dòng Lệnh

spl-token transfer <SENDER_ACCOUNT_ADDRESS> <AMOUNT> <RECIPIENT_WALLET_ADDRESS> --fund-recipient

Ví dụ

spl-token transfer 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BN 1

Cho kết quả tương tự:

6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV
Transfer 1 tokens
Sender: 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BN
Recipient: 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV
Signature: 3R6tsog17QM8KfzbcbdP4aoMfwgo6hBggJDVy7dZPVmH2xbCWjEj31JKD53NzMrf25ChFjY7Uv2dfCDq4mGFFyAj

Nạp Tiền

Vì mỗi cặp (ví, mint) yêu cầu một tài khoản riêng biệt trên chuỗi. Bạn được khuyến nghị rằng các địa chỉ cho những tài khoản này được lấy từ các ví nạp SOL bằng cách sử dụng sơ đồ Associated Token Account (ATA) và chỉ chấp nhận các khoản nạp từ địa chỉ ATA.

Việc theo dõi các giao dịch nạp tiền nên tuân theo phương pháp thăm dò block được mô tả ở trên. Mỗi block mới cần được quét để tìm các giao dịch thành công bao gồm địa chỉ token account của người dùng và sàn giao dịch.

Các trường preTokenBalancespostTokenBalances từ metadata của giao dịch phải được sử dụng để xác định sự thay đổi số dư thực tế. Các trường này bao gồm mint của token, chủ sở hữu token account (địa chỉ ví), và số dư của các token account trước và sau giao dịch.

Nếu một token account được tạo như một phần của giao dịch (chẳng hạn khi nhận token lần đầu tiên), nó sẽ không xuất hiện trong mảng preTokenBalances vì nó chưa tồn tại trước giao dịch. Trong trường hợp này, bạn nên coi số dư ban đầu là không khi tính toán số tiền nạp. Tài khoản mới tạo sẽ chỉ xuất hiện trong mảng postTokenBalances với số dư cuối cùng sau khi giao dịch hoàn tất.

Ví dụ 1: Chuyển Token Đơn lẻ

Chi tiết giao dịch dưới đây cho thấy một ví dụ về giao dịch bao gồm một lệnh chuyển token đơn lẻ.

Giao dịch chuyển 100 đơn vị cơ sở của token (chưa điều chỉnh theo số thập phân của mint) và bao gồm các tài khoản sau:

  • Người gửi (chủ sở hữu): 4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw
  • Token Account Người gửi: 6zhjktfYBRUp7fgXLoWU7GFFrCuZa9iQkYTQzDzuvsAS
  • Token Account Người nhận: G5nNekUhhWFqJAiCMpKHootZ5Bfa7MXwuQ5vvKcvuxKM
  • ID Token Extension Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Lưu ý rằng tài khoản người nhận (chủ sở hữu) và mint account không bắt buộc trong một lệnh chuyển token. Để tham khảo, chúng được liệt kê ở đây vì các địa chỉ được bao gồm trong metadata giao dịch đã phân tích cú pháp.

  • Người nhận (chủ sở hữu): 8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n
  • Mint: Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2
Transaction Metadata
{
"blockTime": "1741240211",
"meta": {
"computeUnitsConsumed": "1551",
"err": null,
"fee": "5000",
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]",
"Program log: Instruction: Transfer",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1551 of 200000 compute units",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"
],
"postBalances": ["994375240", "2074080", "2074080", "1141440"],
"postTokenBalances": [
{
"accountIndex": 1,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
},
{
"accountIndex": 2,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
}
],
"preBalances": ["994380240", "2074080", "2074080", "1141440"],
"preTokenBalances": [
{
"accountIndex": 1,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
},
{
"accountIndex": 2,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
}
],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": "3916",
"transaction": {
"message": {
"accountKeys": [
"4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw",
"6zhjktfYBRUp7fgXLoWU7GFFrCuZa9iQkYTQzDzuvsAS",
"G5nNekUhhWFqJAiCMpKHootZ5Bfa7MXwuQ5vvKcvuxKM",
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
],
"addressTableLookups": [],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [1, 2, 0],
"data": "3WBgs5fm8oDy",
"programIdIndex": 3,
"stackHeight": null
}
],
"recentBlockhash": "8soh8j2dkEniZW6Jpx9cJaWtnvrGoGUqpbaUVwUkX5R3"
},
"signatures": [
"3vr6Gj3GnBQmsZW1TtBJ3hvfFMi3h9BxLs2oaZkV41LRWeGWPVmeo16JTN8MdP3ypU5VgWAziYUjybhyZoisryQ6"
]
},
"version": "0"
}

Ví dụ 2: Tạo Token Account và Chuyển Token

Chi tiết giao dịch dưới đây cho thấy một ví dụ về giao dịch trong đó token account của người nhận được tạo trong cùng giao dịch với lần chuyển token.

Lưu ý rằng mảng preTokenBalances không bao gồm token account của người nhận vì nó chưa tồn tại trước giao dịch. Token account của người nhận chỉ xuất hiện trong mảng postTokenBalances với số dư cuối cùng sau khi giao dịch hoàn tất.

Transaction Metadata
{
"blockTime": "1740541705",
"meta": {
"computeUnitsConsumed": "17416",
"err": null,
"fee": "5000",
"innerInstructions": [
{
"index": 0,
"instructions": [
{
"accounts": [4],
"data": "84eT",
"programIdIndex": 7,
"stackHeight": 2
},
{
"accounts": [0, 1],
"data": "11119ExAoTptm6xKUTUcw2V69MKmyEdDmRins3j3bK43o9nHeiYUtSiaT9pc292PhNQvxj",
"programIdIndex": 3,
"stackHeight": 2
},
{
"accounts": [1],
"data": "P",
"programIdIndex": 7,
"stackHeight": 2
},
{
"accounts": [1, 4],
"data": "6b8ZSccu4ezujyhGG8KNmg75iCWbQRyjxeSfi38u8ED8N",
"programIdIndex": 7,
"stackHeight": 2
}
]
}
],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]",
"Program log: Create",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]",
"Program log: Instruction: GetAccountDataSize",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 928 of 394613 compute units",
"Program return: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb qgAAAAAAAAA=",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success",
"Program 11111111111111111111111111111111 invoke [2]",
"Program 11111111111111111111111111111111 success",
"Program log: Initialize the associated token account",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]",
"Program log: Instruction: InitializeImmutableOwner",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 487 of 388755 compute units",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]",
"Program log: Instruction: InitializeAccount3",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1440 of 385879 compute units",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success",
"Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 15865 of 400000 compute units",
"Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]",
"Program log: Instruction: Transfer",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1551 of 384135 compute units",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"
],
"postBalances": [
"994375240",
"2074080",
"2074080",
"1",
"1461600",
"731913600",
"0",
"1141440"
],
"postTokenBalances": [
{
"accountIndex": 1,
"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK",
"owner": "EzrmgRNGN9duiDAk3ABSC8eKhd1b2EUFwXVrYZDJw4hQ",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
},
{
"accountIndex": 2,
"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK",
"owner": "CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
}
],
"preBalances": [
"996454320",
"0",
"2074080",
"1",
"1461600",
"731913600",
"0",
"1141440"
],
"preTokenBalances": [
{
"accountIndex": 2,
"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK",
"owner": "CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
}
],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": "81051",
"transaction": {
"message": {
"accountKeys": [
"CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS",
"571u96hRRmxbRCTmp5oqC5WpJfvZhPaSXEbihLVCR5wQ",
"8y8KjtZN9tyGeAeKwr8doSpbBVVgfsZMtMjCGUDH7mmU",
"11111111111111111111111111111111",
"3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK",
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
"EzrmgRNGN9duiDAk3ABSC8eKhd1b2EUFwXVrYZDJw4hQ",
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
],
"addressTableLookups": [],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 5,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [0, 1, 6, 4, 3, 7],
"data": "1",
"programIdIndex": 5,
"stackHeight": null
},
{
"accounts": [2, 1, 0],
"data": "3WBgs5fm8oDy",
"programIdIndex": 7,
"stackHeight": null
}
],
"recentBlockhash": "77QC38Q2hKFYZzUXk8JWmsAqGNKhw4k2Lm2XVUme9uqP"
},
"signatures": [
"4kuGhMeZxBHgEtej4Uv4n2arhe3jqT2GdTDPFri4JLFXYgcAtbeeXdBdzvG98HENe1tZSZqyFkm3SEvB6CfCMaM9"
]
},
"version": "0"
}

Ví dụ 3: Thay đổi Chủ sở hữu Token Account

Chi tiết giao dịch dưới đây cho thấy một ví dụ về giao dịch trong đó trường owner của token account bị thay đổi.

Việc chấp nhận tiền nạp bằng cách cho phép người nạp tiền chuyển quyền sở hữu token account (bằng cách thay đổi trường owner) là điều rất không được khuyến khích.

Nếu bạn chọn hỗ trợ phương thức này như một cách nạp tiền, bạn phải xác minh rằng trường owner mới trong postTokenBalances khớp với địa chỉ ví mà sàn giao dịch của bạn kiểm soát và có khóa riêng tư.

Nếu người nạp tiền thay đổi owner của một token account thành một địa chỉ không phải là ví (chẳng hạn như địa chỉ token account khác), tiền có thể trở nên không thể truy cập vĩnh viễn.

Transaction Metadata
{
"blockTime": "1740598556",
"meta": {
"computeUnitsConsumed": "1167",
"err": null,
"fee": "5000",
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]",
"Program log: Instruction: SetAuthority",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1167 of 200000 compute units",
"Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"
],
"postBalances": ["996479120", "2039280", "1141440"],
"postTokenBalances": [
{
"accountIndex": 1,
"mint": "ELRBdV4gcuxqYb6jHkV4ySJ7dwYx9344cgFNzUSww1ra",
"owner": "A9FK8XxT2Hfefz8H3vQJHLwvbibGQJWErBsqMumgUYeP",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
}
],
"preBalances": ["996484120", "2039280", "1141440"],
"preTokenBalances": [
{
"accountIndex": 1,
"mint": "ELRBdV4gcuxqYb6jHkV4ySJ7dwYx9344cgFNzUSww1ra",
"owner": "DLvpDgEABKfEaRDz5Qh9tSrJhuZzsiiZMcYXmvdek1zV",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
}
],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": "137902",
"transaction": {
"message": {
"accountKeys": [
"DLvpDgEABKfEaRDz5Qh9tSrJhuZzsiiZMcYXmvdek1zV",
"5Qj4uNGuAEBdryPg8k2UTewpnNfYAc9Ux9fCcDrNAjGs",
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
],
"addressTableLookups": [],
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"instructions": [
{
"accounts": [1, 0],
"data": "bmb6sys4wqZErNeiV7hrM4vQVHF8AVBhi3XeR5TgbQM68MH",
"programIdIndex": 2,
"stackHeight": null
}
],
"recentBlockhash": "CvTdX9MSYkqFMkALUHeGPMQN5yBeUdJptRdce2qMEkPr"
},
"signatures": [
"3rHUaKMh4KDDfdaATAL4J5WDEV7oFm7ykkNaMf1Eo5EwvDUjLE6dWsbxNDmyENrhb2w5gE4KqRxZ3ZwQxuM18SVR"
]
},
"version": "0"
}

Tính toán Số tiền Nạp Token

Để theo dõi chính xác các khoản nạp token, bạn phải so sánh các trường preTokenBalancespostTokenBalance trong metadata giao dịch. Các trường này hiển thị số dư token và chủ sở hữu token account trước và sau giao dịch, cho phép bạn tính toán số lượng token được chuyển chính xác. Cách tiếp cận này đảm bảo bạn nắm bắt được các thay đổi số dư thực tế.

  • Nếu trường owner của các trường preTokenBalancespostTokenBalances vẫn giữ nguyên, hãy tính hiệu giữa các trường amount.
  • Nếu quyền sở hữu token account thay đổi (trường owner khác nhau giữa preTokenBalancespostTokenBalances), và chủ sở hữu mới trong postTokenBalance khớp với địa chỉ owner mong đợi của sàn giao dịch của bạn, thì hãy coi toàn bộ số dư được hiển thị trong trường amount của postTokenBalances là số tiền nạp.
Transaction Metadata
"meta": {
// --snip--
"postBalances": ["994375240", "2074080", "2074080", "1141440"],
"postTokenBalances": [
{
"accountIndex": 1,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
},
{
"accountIndex": 2,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
}
],
"preBalances": ["994380240", "2074080", "2074080", "1141440"],
"preTokenBalances": [
{
"accountIndex": 1,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "100",
"decimals": 2,
"uiAmount": "1",
"uiAmountString": "1"
}
},
{
"accountIndex": 2,
"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2",
"owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n",
"programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
"uiTokenAmount": {
"amount": "0",
"decimals": 2,
"uiAmount": null,
"uiAmountString": "0"
}
}
],
// --snip--
}

Rút tiền

Địa chỉ rút tiền mà người dùng cung cấp phải là địa chỉ ví SOL của họ.

Trước khi thực hiện chuyển rút tiền, sàn giao dịch nên kiểm tra địa chỉ như đã mô tả ở trên. Ngoài ra, địa chỉ này phải thuộc sở hữu của System Program và không có dữ liệu tài khoản. Nếu địa chỉ không có số dư SOL, cần xác nhận từ người dùng trước khi tiến hành rút tiền. Tất cả các địa chỉ rút tiền khác phải bị từ chối.

Từ địa chỉ rút tiền, Associated Token Account (ATA) cho mint chính xác được suy ra và lệnh chuyển được phát hành đến tài khoản đó thông qua lệnh TransferChecked. Lưu ý rằng địa chỉ ATA có thể chưa tồn tại, trong trường hợp đó sàn giao dịch nên tài trợ cho tài khoản thay mặt người dùng. Đối với các token account SPL Token, việc tài trợ cho tài khoản rút tiền sẽ yêu cầu 0.00203928 SOL (2.039.280 lamport).

Mẫu lệnh spl-token transfer để rút tiền:

spl-token transfer --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>

Các Lưu Ý Khác

Freeze Authority

Vì lý do tuân thủ quy định, một tổ chức phát hành SPL Token có thể tùy chọn chọn giữ "Freeze Authority" đối với tất cả các tài khoản được tạo liên kết với mint của họ. Điều này cho phép họ đóng băng tài sản trong một tài khoản nhất định theo ý muốn, khiến tài khoản không thể sử dụng cho đến khi được rã đông. Nếu tính năng này đang được sử dụng, pubkey của freeze authority sẽ được đăng ký trong tài khoản mint của SPL Token.

Hỗ Trợ Cơ Bản cho Tiêu Chuẩn SPL Token-2022 (Token Extensions)

SPL Token-2022 là tiêu chuẩn mới nhất để tạo và trao đổi token được bọc/tổng hợp trên blockchain Solana.

Còn được gọi là "Token Extensions", tiêu chuẩn này chứa nhiều tính năng mới mà nhà tạo token và chủ tài khoản có thể tùy chọn kích hoạt. Các tính năng này bao gồm chuyển bảo mật, phí chuyển, đóng mint, metadata, ủy quyền vĩnh viễn, quyền sở hữu bất biến và nhiều hơn nữa. Vui lòng xem hướng dẫn tiện mở rộng để biết thêm thông tin.

Nếu sàn giao dịch của bạn hỗ trợ SPL Token, không cần nhiều công việc hơn để hỗ trợ SPL Token-2022:

  • công cụ CLI hoạt động liền mạch với cả hai chương trình bắt đầu từ phiên bản 3.0.0.
  • preTokenBalancespostTokenBalances bao gồm số dư SPL Token-2022
  • RPC lập chỉ mục các tài khoản SPL Token-2022, nhưng chúng phải được truy vấn riêng với program id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Associated Token Account hoạt động theo cách tương tự và tính toán chính xác số tiền SOL cần nạp cho tài khoản mới.

Tuy nhiên, do các tiện ích mở rộng, các tài khoản có thể lớn hơn 165 byte, vì vậy chúng có thể yêu cầu nhiều hơn 0.00203928 SOL để tài trợ.

Ví dụ, chương trình Associated Token Account luôn bao gồm tiện ích mở rộng "immutable owner", vì vậy các tài khoản chiếm tối thiểu 170 byte, yêu cầu 0.00207408 SOL.

Các Lưu Ý Theo Tiện Ích Mở Rộng

Phần trước phác thảo hỗ trợ cơ bản nhất cho SPL Token-2022. Vì các tiện ích mở rộng thay đổi hành vi của token, các sàn giao dịch có thể cần thay đổi cách họ xử lý token.

Có thể xem tất cả các tiện ích mở rộng trên một mint hoặc token account:

spl-token display <account address>

Phí Chuyển

Một token có thể được cấu hình với phí chuyển, trong đó một phần token được chuyển sẽ bị giữ lại tại đích để thu thập sau.

Nếu sàn giao dịch của bạn chuyển các token này, hãy lưu ý rằng không phải tất cả đều đến nơi đích do số lượng bị giữ lại.

Có thể chỉ định phí dự kiến trong quá trình chuyển để tránh bất kỳ bất ngờ nào:

spl-token transfer --expected-fee <fee amount> --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>

Mint Close Authority

Với tiện ích mở rộng này, nhà tạo token có thể đóng một mint, với điều kiện nguồn cung token bằng không.

Khi một mint bị đóng, vẫn có thể tồn tại các token account trống, và chúng sẽ không còn được liên kết với một mint hợp lệ.

Có thể đóng các token account này một cách an toàn:

spl-token close --address <account address>

Chuyển Bảo Mật

Các mint có thể được cấu hình để chuyển bảo mật, sao cho số lượng token được mã hóa, nhưng chủ tài khoản vẫn công khai.

Các sàn giao dịch có thể cấu hình token account để gửi và nhận chuyển bảo mật, để ẩn số lượng của người dùng. Không bắt buộc phải bật chuyển bảo mật trên token account, vì vậy các sàn giao dịch có thể buộc người dùng gửi token không bảo mật.

Để bật chuyển bảo mật, tài khoản phải được cấu hình cho điều đó:

spl-token configure-confidential-transfer-account --address <account address>

Và để chuyển:

spl-token transfer --confidential <exchange token account> <withdrawal amount> <withdrawal address>

Trong quá trình chuyển bảo mật, các trường preTokenBalancepostTokenBalance sẽ không hiển thị thay đổi. Để quét các tài khoản nạp tiền, bạn phải giải mã số dư mới để rút token:

spl-token apply-pending-balance --address <account address>
spl-token withdraw-confidential-tokens --address <account address> <amount or ALL>

Trạng Thái Tài Khoản Mặc Định

Các mint có thể được cấu hình với trạng thái tài khoản mặc định, sao cho tất cả các token account mới bị đóng băng theo mặc định. Những nhà tạo token này có thể yêu cầu người dùng trải qua một quy trình riêng để rã đông tài khoản.

Không Thể Chuyển

Một số token không thể chuyển, nhưng chúng vẫn có thể bị đốt và tài khoản có thể bị đóng.

Ủy Quyền Vĩnh Viễn

Nhà tạo token có thể chỉ định một ủy quyền vĩnh viễn cho tất cả token của họ. Người ủy quyền vĩnh viễn có thể chuyển hoặc đốt token từ bất kỳ tài khoản nào, có thể đánh cắp tiền.

Đây là yêu cầu pháp lý đối với stablecoin tại một số khu vực tài phán, hoặc có thể được sử dụng cho các kế hoạch thu hồi token.

Hãy lưu ý rằng các token này có thể được chuyển mà không có sự hiểu biết của sàn giao dịch của bạn.

Transfer Hook

Các token có thể được cấu hình với một chương trình bổ sung phải được gọi trong quá trình chuyển, để xác thực việc chuyển hoặc thực hiện bất kỳ logic nào khác.

Vì Solana runtime yêu cầu tất cả các tài khoản phải được truyền rõ ràng vào một chương trình, và transfer hook yêu cầu các tài khoản bổ sung, sàn giao dịch cần tạo lệnh chuyển khác nhau cho các token này.

CLI và các trình tạo lệnh như createTransferCheckedWithTransferHookInstruction tự động thêm các tài khoản bổ sung, nhưng các tài khoản bổ sung cũng có thể được chỉ định rõ ràng:

spl-token transfer --transfer-hook-account <pubkey:role> --transfer-hook-account <pubkey:role> ...

Yêu Cầu Memo Khi Chuyển

Người dùng có thể cấu hình token account của họ để yêu cầu memo khi chuyển.

Các sàn giao dịch có thể cần thêm lệnh memo trước khi chuyển token lại cho người dùng, hoặc họ có thể yêu cầu người dùng thêm lệnh memo trước khi gửi đến sàn giao dịch:

spl-token transfer --with-memo <memo text> <exchange token account> <withdrawal amount> <withdrawal address>

Kiểm Tra Tích Hợp

Hãy đảm bảo kiểm tra toàn bộ quy trình của bạn trên các cluster devnet và testnet của Solana trước khi chuyển sang môi trường sản xuất trên mainnet. Devnet là mở và linh hoạt nhất, lý tưởng cho phát triển ban đầu, trong khi testnet cung cấp cấu hình cluster thực tế hơn. Cả devnet và testnet đều hỗ trợ faucet, chạy solana airdrop 1 để nhận một ít SOL devnet hoặc testnet cho mục đích phát triển và kiểm tra.

Is this page helpful?

Mục lục

Thiết Lập NodeKhởi Động Lại Tự Động và Giám SátThông Báo Phát Hành Phần Mềm MớiTính Liên Tục của LedgerGiảm Thiểu Phơi Bày Cổng của ValidatorThiết Lập Tài Khoản Nạp TiềnKết QuảTài Khoản Ngoại TuyếnLắng Nghe Các Khoản Nạp TiềnDi Chuyển Giao Dịch Có Phiên BảnThăm Dò Các KhốiKết QuảMẹo Lấy KhốiKết QuảLịch Sử Địa ChỉKết QuảKết QuảGửi Lệnh Rút TiềnĐồng BộBất Đồng BộXác Nhận Giao Dịch & Tính Hoàn TấtKết QuảThời Hạn BlockhashXác Thực Địa Chỉ Tài Khoản Do Người Dùng Cung Cấp Cho Lệnh Rút TiềnXác Minh Cơ BảnXác Minh Nâng CaoKiểm Tra pubkey ed25519 Hợp LệJavaSố Tiền Nạp & Rút Tối ThiểuKết QuảPhí Ưu Tiên và Đơn Vị Tính ToánPhí Ưu Tiên Là Gì?Phí Ưu Tiên Nên Là Bao Nhiêu?Cách Triển Khai Phí Ưu TiênPhí Ưu Tiên và Durable NoncesHỗ trợ Tiêu chuẩn SPL TokenToken MintsCài đặt Công cụ CLI spl-tokenTạo Tài khoảnDòng LệnhVí dụKiểm tra Số dư Tài khoảnDòng LệnhVí dụChuyển TokenDòng LệnhVí dụNạp TiềnVí dụ 1: Chuyển Token Đơn lẻVí dụ 2: Tạo Token Account và Chuyển TokenVí dụ 3: Thay đổi Chủ sở hữu Token AccountTính toán Số tiền Nạp TokenRút tiềnCác Lưu Ý KhácFreeze AuthorityHỗ Trợ Cơ Bản cho Tiêu Chuẩn SPL Token-2022 (Token Extensions)Các Lưu Ý Theo Tiện Ích Mở RộngPhí ChuyểnMint Close AuthorityChuyển Bảo MậtTrạng Thái Tài Khoản Mặc ĐịnhKhông Thể ChuyểnỦy Quyền Vĩnh ViễnTransfer HookYêu Cầu Memo Khi ChuyểnKiểm Tra Tích Hợp
Chỉnh sửa trang