Tạo một Token Account

Token Account là gì?

Token account là một loại tài khoản trong Token Programs của Solana, lưu trữ thông tin về quyền sở hữu của một cá nhân đối với một token cụ thể (mint). Mỗi token account được liên kết với một mint duy nhất và theo dõi các chi tiết như số dư token và chủ sở hữu.

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 Extension Program đều chia sẻ cùng một triển khai cơ bản cho Token account.

Để giữ token cho một mint cụ thể, người dùng trước tiên phải tạo một token account. Mỗi token account theo dõi:

  1. Một mint cụ thể (loại token mà token account giữ các đơn vị của nó)
  2. Một chủ sở hữu (người có quyền chuyển token từ tài khoản)

Dưới đây là một ví dụ sử dụng USD Coin (USDC) trên Solana:

  • Địa chỉ mint của USD Coin: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  • Circle (đơn vị phát hành USD Coin) có một token account tại 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
  • Token account này chỉ giữ các đơn vị của token USD Coin (mint)
  • Circle có quyền sở hữu tại 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE và có thể chuyển các token này

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

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

  1. "Owner" của token account - Đây là một địa chỉ được lưu trữ trong dữ liệu của token account như trường "owner" của kiểu Account được định nghĩa bởi Token Program. Owner có thể chuyển, đốt hoặc ủy quyền token từ tài khoản. Địa chỉ này đôi khi được gọi là "authority" của token account để phân biệt nó với program owner.

  2. "Owner" của chương trình - Đây là chương trình sở hữu dữ liệu tài khoản trên Solana. Đối với token account, đây luôn là Token Program hoặc Token Extension Program, được chỉ định trong trường "owner" của Solana cơ bản Account type.

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

Associated Token Account là gì?

Associated token account là một token account có địa chỉ là Program Derived Address (PDA) được tạo bởi Associated Token Program. Bạn có thể xem associated token account như tài khoản token mặc định cho người dùng để lưu giữ các đơn vị của một token cụ thể (mint).

Thuật ngữ "associated token accounts" chỉ áp dụng cho các token account mà Associated Token Program tạo ra.

Associated token account cung cấp cách xác định để tìm token account của người dùng cho bất kỳ mint nào. Bạn có thể kiểm tra cách triển khai của quá trình tạo 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
)
}

Cách tạo xác định này đảm bảo rằng với bất kỳ sự kết hợp nào giữa địa chỉ ví và token mint, sẽ chỉ tồn tại đúng một địa chỉ associated token account. Cách tiếp cận này giúp dễ dàng tìm token account của người dùng cho bất kỳ token mint nào, loại bỏ nhu cầu theo dõi địa chỉ token account riêng biệt.

Associated Token Program hoạt động như một chương trình hỗ trợ tạo token account với địa chỉ xác định (PDA). Khi tạo associated token account, Associated Token Program thực hiện CPI (Cross-Program Invocation) đến Token Program hoặc Token Extension Program. Token program sở hữu tài khoản được tạo có cùng cấu trúc kiểu Account như được định nghĩa trong token program. Associated Token Program không duy trì trạng thái nào - nó chỉ đơn giản cung cấp cách tiêu chuẩn để tạo token account tại địa chỉ xác định là PDA.

Cách tạo Token Account

Để tạo Token Account, gọi lệnh InitializeAccount Bạn có thể tìm thấy các triển khai của lệnh này tại đây.

Giao dịch để tạo token account cần hai lệnh:

  1. Gọi System Program để tạo và phân bổ không gian cho token account và chuyển quyền sở hữu cho Token Program.
  2. Gọi 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_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://127.0.0.1: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 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 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 2022 program)
// Invokes the system program
const createTokenAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: tokenAccount,
lamports: tokenAccountRent,
space: tokenAccountSpace,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize token account data
// Invokes the token 2022 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 tokenAccountTxSignature =
getSignatureFromTransaction(signedTokenAccountTx);
console.log("Token Account Address:", tokenAccount.address);
console.log("Transaction Signature:", tokenAccountTxSignature);
Click to execute the code.

Rust

use anyhow::Result;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
program_pack::Pack,
signature::{Keypair, Signer},
system_instruction::create_account,
transaction::Transaction,
};
use spl_token_2022::{
id as token_2022_program_id,
instruction::{initialize_account, initialize_mint},
state::{Account, Mint},
};
fn main() -> Result<()> {
// Create connection to local validator
let client = RpcClient::new_with_commitment(
String::from("http://127.0.0.1:8899"),
CommitmentConfig::confirmed(),
);
let recent_blockhash = client.get_latest_blockhash()?;
// 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)?;
client.confirm_transaction(&airdrop_signature)?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature)?;
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)?;
// Instruction to create new account for mint (token 2022 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_2022_program_id(), // program id
);
// Instruction to initialize mint account data
let initialize_mint_instruction = initialize_mint(
&token_2022_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],
recent_blockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction)?;
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)?;
// Instruction to create new account for token account (token 2022 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_2022_program_id(), // program id
);
// Instruction to initialize token account data
let initialize_token_account_instruction = initialize_account(
&token_2022_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],
recent_blockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction)?;
println!("Token Account Address: {}", token_account.pubkey());
println!("Transaction Signature: {}", transaction_signature);
Ok(())
}
Click to execute the code.

Cách tạo Associated Token Account

Để tạo Associated Token Account, gọi lệnh Create Bạn có thể tìm thấy các triển khai của lệnh này tại đây.

Lệnh để tạo associated token account tự động gọi System Program để tạo token account và gọi Token Program để khởi tạo dữ liệu token account. Điều này xảy ra 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_2022_PROGRAM_ADDRESS,
findAssociatedTokenPda
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://127.0.0.1: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 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 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_2022_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);

Rust

use anyhow::Result;
use solana_client::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_2022::{id as token_2022_program_id, instruction::initialize_mint, state::Mint};
fn main() -> Result<()> {
// Create connection to local validator
let client = RpcClient::new_with_commitment(
String::from("http://127.0.0.1:8899"),
CommitmentConfig::confirmed(),
);
let recent_blockhash = client.get_latest_blockhash()?;
// 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)?;
client.confirm_transaction(&airdrop_signature)?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature)?;
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)?;
// Instruction to create new account for mint (token 2022 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_2022_program_id(), // program id
);
// Instruction to initialize mint account data
let initialize_mint_instruction = initialize_mint(
&token_2022_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],
recent_blockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction)?;
println!("Mint Address: {}", mint.pubkey());
println!("Transaction Signature: {}", transaction_signature);
// Get the latest blockhash for the next transaction
let recent_blockhash = client.get_latest_blockhash()?;
// 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_2022_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
&mint.pubkey(), // mint address
&token_2022_program_id(), // program id
);
// Create transaction and add instruction
let transaction = Transaction::new_signed_with_payer(
&[create_ata_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
recent_blockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction)?;
println!(
"Associated Token Account Address: {}",
associated_token_account
);
println!("Transaction Signature: {}", transaction_signature);
Ok(())
}

Is this page helpful?

Mục lục

Chỉnh sửa trang