Ein Token Account erstellen

Was ist ein Token Account?

Ein token account ist ein Kontotyp in Solanas Token Programs, der Informationen über den Besitz eines bestimmten Tokens (Mint) einer Person speichert. Jeder token account ist mit einer einzigen Mint verknüpft und verfolgt Details wie den Token-Kontostand und den Besitzer.

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>,
}

Beachten Sie, dass Entwickler im Quellcode einen Token Account als Account Typ bezeichnen. Sowohl das Token Program als auch das Token Extension Program teilen sich dieselbe Basisimplementierung für den Token Account.

Um Tokens für eine bestimmte Mint zu halten, müssen Benutzer zuerst einen token account erstellen. Jeder token account verfolgt:

  1. Eine bestimmte Mint (der Token-Typ, von dem der token account Einheiten hält)
  2. Einen Besitzer (die Autorität, die Tokens vom Konto überweisen kann)

Hier ist ein Beispiel mit USD Coin (USDC) auf Solana:

  • Die USD Coin Mint-Adresse: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  • Circle (der Herausgeber von USD Coin) hat einen token account unter 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
  • Dieser token account hält nur Einheiten des USD Coin Tokens (Mint)
  • Circle hat die Besitzerautorität unter 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE und kann diese Tokens überweisen

Sie können die Details dieses token account im Solana Explorer einsehen.

Der Begriff "Besitzer" wird in zwei verschiedenen Kontexten verwendet:

  1. Der "Besitzer" des token account - Dies ist eine Adresse, die in den Daten des token account als "owner"-Feld des Account Typs gespeichert ist, der vom Token Program definiert wird. Der Besitzer kann Tokens vom Konto überweisen, verbrennen oder delegieren. Diese Adresse wird manchmal als die "Autorität" des token account bezeichnet, um sie vom Programm-Besitzer zu unterscheiden.

  2. Der Programm "owner" - Dies bezieht sich auf das Programm, das die Kontodaten auf Solana besitzt. Für Token-Konten ist dies immer entweder das Token Program oder Token Extension Program, wie im "owner"-Feld des Basis-Solana Account Typs angegeben.

Bei der Arbeit mit token accounts bezieht sich "owner" typischerweise auf die Autorität, die die Tokens ausgeben kann, nicht auf das Programm, das das Konto besitzt.

Was ist ein Associated Token Account?

Ein associated token account ist ein token account mit einer Adresse, die eine Program Derived Address (PDA) ist, erstellt durch das Associated Token Program. Man kann sich ein associated token account als das Standard-Token-Konto für einen Benutzer vorstellen, um Einheiten eines bestimmten Tokens (mint) zu halten.

Der Begriff "associated token accounts" gilt nur für token accounts, die das Associated Token Program erstellt.

Associated token accounts bieten eine deterministische Möglichkeit, das Token-Konto eines Benutzers für jeden gegebenen mint zu finden. Die Implementierung der Ableitung kann hier eingesehen werden.

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
)
}

Diese deterministische Ableitung stellt sicher, dass für jede Kombination aus Wallet-Adresse und Token-Mint genau eine associated token account Adresse existiert. Dieser Ansatz macht es einfach, das Token-Konto eines Benutzers für jeden gegebenen Token Mint zu finden, wodurch die Notwendigkeit entfällt, Token-Kontoadressen separat zu verfolgen.

Das Associated Token Program fungiert als Hilfsprogramm, das Token-Konten mit deterministischen Adressen (PDAs) erstellt. Bei der Erstellung eines associated token account führt das Associated Token Program einen CPI (Cross-Program Invocation) entweder zum Token Program oder Token Extensions Program durch. Das Token Program besitzt das erstellte Konto, das die gleiche Account Typstruktur hat, wie im Token Program definiert. Das Associated Token Program behält keinen Zustand bei - es bietet einfach eine standardisierte Möglichkeit, Token-Konten an einer deterministischen Adresse zu erstellen, die eine PDA ist.

Wie man ein Token Account erstellt

Um ein Token Account zu erstellen, rufen Sie die InitializeAccount Anweisung auf. Implementierungen dieser Anweisung finden Sie hier.

Die Transaktion zum Erstellen eines token account benötigt zwei Anweisungen:

  1. Rufen Sie das System Program auf, um einen token account zu erstellen, Speicherplatz zuzuweisen und die Eigentümerschaft an das Token Program zu übertragen.
  2. Rufen Sie das Token Program auf, um die token account Daten zu initialisieren.

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.

Wie man ein Associated Token Account erstellt

Um ein associated token account zu erstellen, rufen Sie die Create Anweisung auf. Implementierungen dieser Anweisung finden Sie hier.

Die Anweisung zum Erstellen eines associated token account ruft automatisch das System Program auf, um den token account zu erstellen, und ruft das Token Program auf, um die token account Daten zu initialisieren. Dies geschieht durch Cross Program Invocations (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?

Inhaltsverzeichnis

Seite bearbeiten