Tạo một Token Account

Token Account là gì?

Token account lưu trữ số dư của bạn cho một token cụ thể. Mỗi token account được liên kết với chính xác một mint và theo dõi số dư token cùng các thông tin bổ sung khác.

Token Account Type
/// Account data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Account {
/// The mint associated with this account
pub mint: Pubkey,
/// The owner of this account.
pub owner: Pubkey,
/// The amount of tokens this account holds.
pub amount: u64,
/// If `delegate` is `Some` then `delegated_amount` represents
/// the amount authorized by the delegate
pub delegate: COption<Pubkey>,
/// The account's state
pub state: AccountState,
/// If `is_native.is_some`, this is a native token, and the value logs the
/// rent-exempt reserve. An Account is required to be rent-exempt, so
/// the value is used by the Processor to ensure that wrapped SOL
/// accounts do not drop below this threshold.
pub is_native: COption<u64>,
/// The amount delegated
pub delegated_amount: u64,
/// Optional authority to close the account.
pub close_authority: COption<Pubkey>,
}

Lưu ý rằng trong mã nguồn, các nhà phát triển gọi Token account là kiểu Account. Cả Token ProgramToken Extensions Program đều chia sẻ cùng một triển khai cơ bản cho Token account.

Để giữ token, bạn cần một token account cho mint cụ thể đó. Mỗi token account theo dõi:

  1. Mint: Loại token cụ thể mà nó chứa
  2. Owner: Thẩm quyền có thể chuyển token từ tài khoản này

Ví dụ: USD Coin (USDC)

  • Địa chỉ mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  • Token account của Circle: 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
  • Tài khoản này chỉ chứa token USDC
  • Circle kiểm soát token thông qua thẩm quyền chủ sở hữu token account: 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE

Bạn có thể xem chi tiết của token account này trên Solana Explorer.

Hiểu thuật ngữ "Owner"

Thuật ngữ "owner" có thể được sử dụng trong hai ngữ cảnh khác nhau:

  1. Token Account Owner: Thẩm quyền có thể chuyển, đốt hoặc ủy quyền token. Điều này được lưu trữ trong dữ liệu của token account.

  2. Program Owner: Chương trình sở hữu tài khoản (luôn là Token Program hoặc Token Extensions Program đối với token account).

Khi làm việc với token account, "owner" thường đề cập đến thẩm quyền có thể chuyển token, không phải chương trình sở hữu tài khoản.

Associated Token Account là gì?

Associated token account (ATA) là token account mặc định cho một ví để giữ một token cụ thể. Nó sử dụng địa chỉ xác định (PDA) được tạo bởi Associated Token Program.

Chỉ những token account được tạo bởi Associated Token Program mới được gọi là "associated token accounts."

ATA sử dụng địa chỉ xác định, giúp dễ dàng tìm token account của bất kỳ ví nào cho một mint cụ thể. Xem cách triển khai dẫn xuất tại đây.

Associated Token Account Address Derivation
pub fn get_associated_token_address_and_bump_seed_internal(
wallet_address: &Pubkey,
token_mint_address: &Pubkey,
program_id: &Pubkey,
token_program_id: &Pubkey,
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[
&wallet_address.to_bytes(), // Owner's public key
&token_program_id.to_bytes(), // Token Program or Token Extension Program
&token_mint_address.to_bytes(), // Token mint address
],
program_id, // Associated Token Program ID
)
}

Đối với bất kỳ kết hợp ví và token nào, chỉ có đúng một địa chỉ ATA. Điều này loại bỏ nhu cầu theo dõi địa chỉ token account thủ công—bạn luôn có thể tính toán ra địa chỉ chính xác.

Cách Associated Token Program hoạt động

Associated Token Program là một trợ giúp để:

  • Tạo token account tại các địa chỉ xác định (PDA)
  • Thực hiện Cross Program Invocation (CPI) đến Token Program
  • Không duy trì bất kỳ trạng thái nào

Các token account được tạo ra thuộc sở hữu của Token Program và sử dụng cấu trúc Account tiêu chuẩn.

Cách tạo Token Account

Việc tạo token account yêu cầu lệnh InitializeAccount. Xem triển khai tại đây.

Bạn cần hai lệnh để tạo token account:

  1. System Program: Tạo một tài khoản với không gian được phân bổ cho token account và chuyển quyền sở hữu cho Token Program
  2. Token Program: Khởi tạo dữ liệu token account

Typescript

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
getInitializeAccount2Instruction,
getInitializeMintInstruction,
getMintSize,
getTokenSize,
TOKEN_PROGRAM_ADDRESS
} from "@solana-program/token";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Instruction to create new account for mint (token program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 2,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }), // Create transaction message
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash
(tx) => appendTransactionMessageInstructions(instructions, tx) // Append instructions
);
// Sign transaction message with required signers (fee payer and mint keypair)
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const transactionSignature = getSignatureFromTransaction(signedTransaction);
console.log("Mint Address:", mint.address);
console.log("Transaction Signature:", transactionSignature);
// Generate keypair to use as address of token account
const tokenAccount = await generateKeyPairSigner();
// Get token account size (in bytes)
const tokenAccountSpace = BigInt(getTokenSize());
// Get minimum balance for rent exemption
const tokenAccountRent = await rpc
.getMinimumBalanceForRentExemption(tokenAccountSpace)
.send();
// Instruction to create new account for token account (token program)
// Invokes the system program
const createTokenAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: tokenAccount,
lamports: tokenAccountRent,
space: tokenAccountSpace,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize token account data
// Invokes the token program
const initializeTokenAccountInstruction = getInitializeAccount2Instruction({
account: tokenAccount.address,
mint: mint.address,
owner: feePayer.address
});
const instructions2 = [
createTokenAccountInstruction,
initializeTokenAccountInstruction
];
// Create transaction message for token account creation
const tokenAccountMessage = pipe(
createTransactionMessage({ version: 0 }), // Create transaction message
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash
(tx) => appendTransactionMessageInstructions(instructions2, tx) // Append instructions
);
// Sign transaction message with required signers (fee payer and token account keypair)
const signedTokenAccountTx =
await signTransactionMessageWithSigners(tokenAccountMessage);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTokenAccountTx,
{ commitment: "confirmed" }
);
// Get transaction signature
const transactionSignature2 = getSignatureFromTransaction(signedTokenAccountTx);
console.log("Token Account Address:", tokenAccount.address);
console.log("Transaction Signature:", transactionSignature2);
Console
Click to execute the code.

Rust

use anyhow::Result;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
program_pack::Pack,
signature::{Keypair, Signer},
system_instruction::create_account,
transaction::Transaction,
};
use spl_token::{
id as token_program_id,
instruction::{initialize_account, initialize_mint},
state::{Account, Mint},
};
#[tokio::main]
async fn main() -> Result<()> {
// Create connection to local validator
let client = RpcClient::new_with_commitment(
String::from("http://localhost:8899"),
CommitmentConfig::confirmed(),
);
let latestBlockhash = client.get_latest_blockhash().await?;
// Generate a new keypair for the fee payer
let fee_payer = Keypair::new();
// Airdrop 1 SOL to fee payer
let airdrop_signature = client
.request_airdrop(&fee_payer.pubkey(), 1_000_000_000)
.await?;
client.confirm_transaction(&airdrop_signature).await?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature).await?;
if confirmed {
break;
}
}
// Generate keypair to use as address of mint
let mint = Keypair::new();
// Get default mint account size (in bytes), no extensions enabled
let mint_space = Mint::LEN;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space)
.await?;
// Instruction to create new account for mint (token program)
let create_account_instruction = create_account(
&fee_payer.pubkey(), // payer
&mint.pubkey(), // new account (mint)
mint_rent, // lamports
mint_space as u64, // space
&token_program_id(), // program id
);
// Instruction to initialize mint account data
let initialize_mint_instruction = initialize_mint(
&token_program_id(),
&mint.pubkey(), // mint
&fee_payer.pubkey(), // mint authority
Some(&fee_payer.pubkey()), // freeze authority
2, // decimals
)?;
// Create transaction and add instructions
let transaction = Transaction::new_signed_with_payer(
&[create_account_instruction, initialize_mint_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!("Mint Address: {}", mint.pubkey());
println!("Transaction Signature: {}", transaction_signature);
// Generate keypair to use as address of token account
let token_account = Keypair::new();
// Get token account size (in bytes)
let token_account_space = Account::LEN;
let token_account_rent = client
.get_minimum_balance_for_rent_exemption(token_account_space)
.await?;
// Instruction to create new account for token account (token program)
let create_token_account_instruction = create_account(
&fee_payer.pubkey(), // payer
&token_account.pubkey(), // new account (token account)
token_account_rent, // lamports
token_account_space as u64, // space
&token_program_id(), // program id
);
// Instruction to initialize token account data
let initialize_token_account_instruction = initialize_account(
&token_program_id(),
&token_account.pubkey(), // account
&mint.pubkey(), // mint
&fee_payer.pubkey(), // owner
)?;
// Create transaction and add instructions
let transaction = Transaction::new_signed_with_payer(
&[
create_token_account_instruction,
initialize_token_account_instruction,
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &token_account],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!("Token Account Address: {}", token_account.pubkey());
println!("Transaction Signature: {}", transaction_signature);
Ok(())
}
Console
Click to execute the code.

Cách tạo Associated Token Account

Việc tạo một Associated Token Account (ATA) yêu cầu lệnh Create. Xem triển khai tại đây.

Lệnh đơn này tự động xử lý việc tạo token account và khởi tạo nó thông qua Cross Program Invocation (CPI).

Typescript

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
getCreateAssociatedTokenInstructionAsync,
getInitializeMintInstruction,
getMintSize,
TOKEN_PROGRAM_ADDRESS,
findAssociatedTokenPda
} from "@solana-program/token";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Instruction to create new account for mint (token program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 2,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx)
);
// Sign transaction message with all required signers
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const transactionSignature = getSignatureFromTransaction(signedTransaction);
console.log("Mint Address:", mint.address.toString());
console.log("Transaction Signature:", transactionSignature);
// Use findAssociatedTokenPda to derive the ATA address
const [associatedTokenAddress] = await findAssociatedTokenPda({
mint: mint.address,
owner: feePayer.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
console.log(
"Associated Token Account Address:",
associatedTokenAddress.toString()
);
// Get a fresh blockhash for the second transaction
const { value: latestBlockhash2 } = await rpc.getLatestBlockhash().send();
// Create instruction to create the associated token account
const createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({
payer: feePayer,
mint: mint.address,
owner: feePayer.address
});
// Create transaction message
const transactionMessage2 = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash2, tx),
(tx) => appendTransactionMessageInstructions([createAtaInstruction], tx)
);
// Sign transaction message with all required signers
const signedTransaction2 =
await signTransactionMessageWithSigners(transactionMessage2);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction2,
{ commitment: "confirmed" }
);
// Get transaction signature
const transactionSignature2 = getSignatureFromTransaction(signedTransaction2);
console.log("Transaction Signature:", transactionSignature2);
Console
Click to execute the code.

Rust

use anyhow::Result;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
program_pack::Pack,
signature::{Keypair, Signer},
system_instruction::create_account,
transaction::Transaction,
};
use spl_associated_token_account::{
get_associated_token_address_with_program_id, instruction::create_associated_token_account,
};
use spl_token::{id as token_program_id, instruction::initialize_mint, state::Mint};
#[tokio::main]
async fn main() -> Result<()> {
// Create connection to local validator
let client = RpcClient::new_with_commitment(
String::from("http://localhost:8899"),
CommitmentConfig::confirmed(),
);
let latestBlockhash = client.get_latest_blockhash().await?;
// Generate a new keypair for the fee payer
let fee_payer = Keypair::new();
// Airdrop 1 SOL to fee payer
let airdrop_signature = client
.request_airdrop(&fee_payer.pubkey(), 1_000_000_000)
.await?;
client.confirm_transaction(&airdrop_signature).await?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature).await?;
if confirmed {
break;
}
}
// Generate keypair to use as address of mint
let mint = Keypair::new();
// Get default mint account size (in bytes), no extensions enabled
let mint_space = Mint::LEN;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space)
.await?;
// Instruction to create new account for mint (token program)
let create_account_instruction = create_account(
&fee_payer.pubkey(), // payer
&mint.pubkey(), // new account (mint)
mint_rent, // lamports
mint_space as u64, // space
&token_program_id(), // program id
);
// Instruction to initialize mint account data
let initialize_mint_instruction = initialize_mint(
&token_program_id(),
&mint.pubkey(), // mint
&fee_payer.pubkey(), // mint authority
Some(&fee_payer.pubkey()), // freeze authority
2, // decimals
)?;
// Create transaction and add instructions
let transaction = Transaction::new_signed_with_payer(
&[create_account_instruction, initialize_mint_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!("Mint Address: {}", mint.pubkey());
println!("Transaction Signature: {}", transaction_signature);
// Get the latest blockhash for the next transaction
let latestBlockhash = client.get_latest_blockhash().await?;
// Derive the associated token account address for fee_payer
let associated_token_account = get_associated_token_address_with_program_id(
&fee_payer.pubkey(), // owner
&mint.pubkey(), // mint
&token_program_id(), // program_id
);
// Instruction to create associated token account
let create_ata_instruction = create_associated_token_account(
&fee_payer.pubkey(), // funding address
&fee_payer.pubkey(), // wallet address (owner)
&mint.pubkey(), // mint address
&token_program_id(), // program id
);
// Create transaction for associated token account creation
let transaction = Transaction::new_signed_with_payer(
&[create_ata_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!(
"Associated Token Account Address: {}",
associated_token_account.to_string()
);
println!("Transaction Signature: {}", transaction_signature);
Ok(())
}
Console
Click to execute the code.

Python

Python
#!/usr/bin/env python3
import asyncio
from solana.rpc.async_api import AsyncClient
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.message import MessageV0
from spl.token.instructions import create_associated_token_account, get_associated_token_address
from spl.token.constants import TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID
async def main():
rpc = AsyncClient("http://localhost:8899")
payer = Keypair()
owner = Keypair()
# Example mint address (USDC on devnet)
mint_address = Pubkey.from_string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")
# Get associated token account address
associated_token_account = get_associated_token_address(owner.pubkey(), mint_address)
async with rpc:
# Get latest blockhash
recent_blockhash = await rpc.get_latest_blockhash()
# Create associated token account instruction
create_token_account_instruction = create_associated_token_account(
payer=payer.pubkey(),
owner=owner.pubkey(),
mint=mint_address,
token_program_id=TOKEN_PROGRAM_ID
)
# Create message
message = MessageV0.try_compile(
payer=payer.pubkey(),
instructions=[create_token_account_instruction],
address_lookup_table_accounts=[],
recent_blockhash=recent_blockhash.value.blockhash
)
# Create transaction
transaction = VersionedTransaction(message, [payer])
print(f"Payer: {payer.pubkey()}")
print(f"Owner: {owner.pubkey()}")
print(f"Associated Token Account: {associated_token_account}")
print(f"Mint: {mint_address}")
print(f"Associated token account created successfully")
if __name__ == "__main__":
asyncio.run(main())
Console
Click to execute the code.

Is this page helpful?

Mục lục

Chỉnh sửa trang