Che Cos'è un Token Account
Un token account memorizza i token per un mint e un proprietario del token account su Solana.
/// Account data.#[repr(C)]#[derive(Clone, Copy, Debug, Default, PartialEq)]pub struct Account {/// The mint associated with this accountpub 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 delegatepub delegate: COption<Pubkey>,/// The account's statepub 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 delegatedpub delegated_amount: u64,/// Optional authority to close the account.pub close_authority: COption<Pubkey>,}
Qui, owner indica l'autorità che può trasferire, bruciare o delegare token
dal token account. Il proprietario del programma dell'account rimane comunque il
Token Program o il Token Extensions Program.
Ogni token account è legato esattamente a un mint, il che significa che il token
account può contenere unità di un solo token, il token identificato dal campo
mint del token account.
Che Cos'è un Associated Token Account
Un associated token account (ATA) è il token account predefinito per un wallet e un mint. Il Associated Token Program deriva l'indirizzo ATA dall'indirizzo del wallet, dall'indirizzo del token program e dall'indirizzo del mint.
Solo i token account creati dall'Associated Token Program sono chiamati associated token account.
L'Associated Token Program è un modo per creare un token account a un indirizzo standard e deterministico. L'account risultante è comunque un token account di proprietà del Token Program o del Token Extensions Program, non dell'Associated Token Program.
L'Associated Token Program deriva l'indirizzo ATA come mostrato in address.rs:
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)}
Per qualsiasi combinazione di wallet, token program e mint, esiste esattamente
un indirizzo ATA. L'Associated Token Program crea un token account standard a
quell'indirizzo, e l'account risultante utilizza comunque il tipo Account
definito dal Token Program o dal Token Extensions Program.
Come creare un Associated Token Account
La creazione di un associated token account utilizza l'istruzione Create o
CreateIdempotent del Associated Token Program. Il programma Associated
Token Program deriva l'indirizzo ATA, crea l'account all'indirizzo ATA e
inizializza l'account come token account di proprietà del Token Program o del
Token Extension Program.
Consigliato
Per la maggior parte delle applicazioni, si consiglia di creare token account tramite il Associated Token Program anziché crearli invocando direttamente le istruzioni del System Program e del Token Program. Un associated token account utilizza un indirizzo deterministico derivato dal proprietario, dal token program e dal mint, rendendo più semplice per wallet e applicazioni individuare l'account token predefinito per un determinato mint.
Riferimento al codice sorgente
| Elemento | Descrizione | Sorgente |
|---|---|---|
Create | Crea un associated token account all'indirizzo ATA derivato. | Sorgente |
CreateIdempotent | Crea l'associated token account, ma ha comunque esito positivo se l'ATA esiste già per lo stesso proprietario e mint. | Sorgente |
process_create_associated_token_account | Deriva l'indirizzo ATA e utilizza create_pda_account più CPI al token program selezionato per inizializzare il token account. Quando il programma selezionato è il Token Extension Program, il processore ATA inizializza anche l'estensione immutable owner. | Sorgente |
create_pda_account | Funzione di supporto utilizzata da process_create_associated_token_account per creare l'account PDA tramite CPI al System Program. | Sorgente |
Typescript
Gli esempi Kit riportati di seguito mostrano l'approccio consigliato
utilizzando @solana/kit. Gli esempi legacy che utilizzano @solana/web3.js
sono inclusi a titolo di riferimento.
Kit
import { generateKeyPairSigner } from "@solana/kit";import { createLocalClient } from "@solana/kit-client-rpc";import {associatedTokenProgram,findAssociatedTokenPda,tokenProgram,TOKEN_PROGRAM_ADDRESS} from "@solana-program/token";const client = await createLocalClient().use(tokenProgram()).use(associatedTokenProgram());const result = await client.associatedToken.instructions.createAssociatedToken({payer: client.payer, // Account funding account creation.mint: mint.address, // Mint for the token this account holds.owner: client.payer.address // Account that owns the token account.}).sendTransaction();const [associatedTokenAddress] = await findAssociatedTokenPda({mint: mint.address,owner: client.payer.address,tokenProgram: TOKEN_PROGRAM_ADDRESS});const tokenAccountData = await client.token.accounts.token.fetch(associatedTokenAddress);console.log("Mint Address:", mint.address);console.log("\nAssociated Token Account Address:", associatedTokenAddress);console.log("Associated Token Account:", tokenAccountData.data);console.log("\nTransaction Signature:", result.context.signature);
Web3.js
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";import {createAssociatedTokenAccount,createMint,getAccount,TOKEN_PROGRAM_ID} from "@solana/spl-token";const associatedTokenAccount = await createAssociatedTokenAccount(connection, // Connection to the local validator.feePayer, // Account funding account creation.mintPubkey, // Mint for the token this account holds.feePayer.publicKey, // Account that owns the token account.{commitment: "confirmed" // Confirmation options for the transaction.},TOKEN_PROGRAM_ID // Token program to invoke.);const tokenAccountData = await getAccount(connection,associatedTokenAccount,"confirmed",TOKEN_PROGRAM_ID);console.log("Mint Address:", mintPubkey.toBase58());console.log("\nAssociated Token Account Address:",associatedTokenAccount.toBase58());console.log("Associated Token Account:", tokenAccountData);
Rust
use anyhow::Result;use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{program_pack::Pack,signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_associated_token_account_interface::{address::get_associated_token_address, instruction::create_associated_token_account,};use spl_token_interface::{id as token_program_id,instruction::initialize_mint,state::{Account, Mint},};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let transaction = Transaction::new_signed_with_payer(&[create_associated_token_account(&fee_payer.pubkey(), // Account funding account creation.&fee_payer.pubkey(), // Account that owns the token account.&mint.pubkey(), // Mint for the token this account holds.&token_program_id(), // Token program that owns the account.),],Some(&fee_payer.pubkey()),&[&fee_payer],latest_blockhash,);let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;let associated_token_account = get_associated_token_address(&fee_payer.pubkey(), &mint.pubkey());let token_account = client.get_account(&associated_token_account).await?;let token_data = Account::unpack(&token_account.data)?;println!("Mint Address: {}", mint.pubkey());println!("\nAssociated Token Account Address: {}",associated_token_account);println!("Associated Token Account: {:#?}", token_data);println!("\nTransaction Signature: {}", transaction_signature);Ok(())}
Python
#!/usr/bin/env python3import asyncioimport jsonfrom solana.rpc.async_api import AsyncClientfrom solders.keypair import Keypairfrom solders.message import Messagefrom solders.pubkey import Pubkeyfrom solders.system_program import create_account, CreateAccountParamsfrom solders.transaction import Transactionfrom spl.token.async_client import AsyncTokenfrom spl.token.instructions import (create_associated_token_account,get_associated_token_address,initialize_mint,InitializeMintParams,)from spl.token.constants import MINT_LEN, TOKEN_PROGRAM_IDasync def main():rpc = AsyncClient("http://localhost:8899")fee_payer = Keypair()owner = Keypair()async with rpc:create_associated_token_account_instruction = create_associated_token_account(payer=fee_payer.pubkey(), # Account funding account creation.owner=owner.pubkey(), # Account that owns the token account.mint=mint.pubkey(), # Mint for the token this account holds.token_program_id=TOKEN_PROGRAM_ID, # Token program that owns the new token account.)latest_blockhash = await rpc.get_latest_blockhash()transaction = Transaction([fee_payer],Message([create_associated_token_account_instruction], fee_payer.pubkey()),latest_blockhash.value.blockhash,)result = await rpc.send_transaction(transaction)token_account_info = await token.get_account_info(associated_token_account)token_account = {key: str(value) if isinstance(value, Pubkey) else valuefor key, value in token_account_info._asdict().items()}print("Mint Address:", mint.pubkey())print("\nToken Account Address:", associated_token_account)print("Token Account:")print(json.dumps(token_account, indent=2))print("\nTransaction Signature:", result.value)if __name__ == "__main__":asyncio.run(main())
Come Creare un Token Account
La creazione di un token account richiede due istruzioni:
- L'istruzione
CreateAccountdel System Program crea un nuovo account esente da rent e assegna il Token Program come program owner del nuovo account. - L'istruzione
InitializeAccount,InitializeAccount2oInitializeAccount3del Token Program inizializza il nuovo account per un mint e un owner.
Includi l'istruzione CreateAccount e l'istruzione di inizializzazione del
token account nella stessa transazione.
Durante l'inizializzazione del token account, il Token Program verifica che l'account non sia già inizializzato e sia esente da rent.
La sezione seguente mostra come creare un token account chiamando direttamente le istruzioni del System Program e del Token Program. Per la maggior parte delle applicazioni, utilizza invece l' Associated Token Program. Usa le chiamate dirette al System Program e al Token Program quando hai un motivo specifico per non utilizzare un associated token account o quando devi creare token account PDA personalizzati effettuando CPI alle istruzioni del System Program e del Token Program dal tuo programma Solana.
Riferimento al Codice Sorgente
| Elemento | Descrizione | Token Program | Token Extensions Program |
|---|---|---|---|
Account | I campi base del token account memorizzati in ogni token account. | Sorgente | Sorgente |
InitializeAccount | Un'istruzione di inizializzazione del token account che richiede l'owner e l'account rent sysvar nella sua lista di account. | Sorgente | Sorgente |
InitializeAccount2 | Un'istruzione di inizializzazione del token account che passa l'owner negli instruction data invece che nella lista di account. | Sorgente | Sorgente |
InitializeAccount3 | Un'istruzione di inizializzazione del token account che passa l'owner negli instruction data e non richiede l'account rent sysvar. | Sorgente | Sorgente |
_process_initialize_account | Logica condivisa del processore per l'inizializzazione del token account. | Sorgente | Sorgente |
process_initialize_account | Handler pubblico per InitializeAccount. | Sorgente | Sorgente |
process_initialize_account2 | Handler pubblico per InitializeAccount2. | Sorgente | Sorgente |
process_initialize_account3 | Handler pubblico per InitializeAccount3. | Sorgente | Sorgente |
Typescript
Gli esempi Kit qui sotto mostrano l'approccio consigliato utilizzando
@solana/kit. Gli esempi legacy che utilizzano @solana/web3.js sono inclusi
per riferimento.
Kit
import { generateKeyPairSigner } from "@solana/kit";import { createLocalClient } from "@solana/kit-client-rpc";import { systemProgram } from "@solana-program/system";import {getTokenSize,tokenProgram,TOKEN_PROGRAM_ADDRESS} from "@solana-program/token";const client = await createLocalClient().use(systemProgram()).use(tokenProgram());const result = await client.sendTransaction([client.system.instructions.createAccount({newAccount: tokenAccount, // New token account to create.lamports: rent, // Lamports funding the new account rent.space, // Account size in bytes.programAddress: TOKEN_PROGRAM_ADDRESS // Program that owns the new account.}),client.token.instructions.initializeAccount({account: tokenAccount.address, // Token account to initialize.mint: mint.address, // Mint for the token this account holds.owner: client.payer.address // Account that owns the token account.})]);const tokenAccountData = await client.token.accounts.token.fetch(tokenAccount.address);console.log("Mint Address:", mint.address);console.log("\nToken Account Address:", tokenAccount.address);console.log("Token Account:", tokenAccountData.data);console.log("\nTransaction Signature:", result.context.signature);
Web3.js
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";import {createAccount,createMint,getAccount,TOKEN_PROGRAM_ID} from "@solana/spl-token";const tokenAccount = await createAccount(connection, // Connection to the local validator.feePayer, // Account paying transaction fees.mintPubkey, // Mint for the token this account holds.feePayer.publicKey, // Account that owns the token account.Keypair.generate(), // New token account to create.{commitment: "confirmed" // Confirmation options for the transaction.},TOKEN_PROGRAM_ID // Token program to invoke.);const tokenAccountData = await getAccount(connection,tokenAccount,"confirmed",TOKEN_PROGRAM_ID);console.log("Mint Address:", mintPubkey.toBase58());console.log("\nToken Account Address:", tokenAccount.toBase58());console.log("Token Account:", tokenAccountData);
Rust
use anyhow::Result;use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{program_pack::Pack,signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_token_interface::{id as token_program_id,instruction::{initialize_account, initialize_mint},state::{Account, Mint},};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let token_account = Keypair::new();let token_account_rent = client.get_minimum_balance_for_rent_exemption(Account::LEN).await?;let transaction = Transaction::new_signed_with_payer(&[create_account(&fee_payer.pubkey(), // Account funding account creation.&token_account.pubkey(), // New token account to create.token_account_rent, // Lamports funding the new account rent.Account::LEN as u64, // Account size in bytes.&token_program_id(), // Program that owns the new account.),initialize_account(&token_program_id(),&token_account.pubkey(), // Token account to initialize.&mint.pubkey(), // Mint for the token this account holds.&fee_payer.pubkey(), // Account that owns the token account.)?,],Some(&fee_payer.pubkey()),&[&fee_payer, &token_account],latest_blockhash,);let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;let token_account_data = client.get_account(&token_account.pubkey()).await?;let token_data = Account::unpack(&token_account_data.data)?;println!("Mint Address: {}", mint.pubkey());println!("\nToken Account Address: {}", token_account.pubkey());println!("Token Account: {:#?}", token_data);println!("\nTransaction Signature: {}", transaction_signature);Ok(())}
Python
#!/usr/bin/env python3import asyncioimport jsonfrom solana.rpc.async_api import AsyncClientfrom solders.keypair import Keypairfrom solders.message import Messagefrom solders.pubkey import Pubkeyfrom solders.system_program import create_account, CreateAccountParamsfrom solders.transaction import Transactionfrom spl.token.async_client import AsyncTokenfrom spl.token.instructions import (initialize_account,InitializeAccountParams,initialize_mint,InitializeMintParams,)from spl.token.constants import ACCOUNT_LEN, MINT_LEN, TOKEN_PROGRAM_IDasync def main():rpc = AsyncClient("http://localhost:8899")async with rpc:token_account = Keypair()token_account_rent = (await rpc.get_minimum_balance_for_rent_exemption(ACCOUNT_LEN)).valuecreate_token_account_instructions = [create_account(CreateAccountParams(from_pubkey=fee_payer.pubkey(), # Account funding account creation.to_pubkey=token_account.pubkey(), # New token account to create.lamports=token_account_rent, # Lamports funding the new account rent.space=ACCOUNT_LEN, # Account size in bytes.owner=TOKEN_PROGRAM_ID, # Program that owns the new token account.)),initialize_account(InitializeAccountParams(program_id=TOKEN_PROGRAM_ID, # Token program to invoke.account=token_account.pubkey(), # Token account to initialize.mint=mint.pubkey(), # Mint for the token this account holds.owner=fee_payer.pubkey(), # Account that owns the token account.)),]latest_blockhash = await rpc.get_latest_blockhash()transaction = Transaction([fee_payer, token_account],Message(create_token_account_instructions, fee_payer.pubkey()),latest_blockhash.value.blockhash,)result = await rpc.send_transaction(transaction)token_account_info = await token.get_account_info(token_account.pubkey())token_account_data = {key: str(value) if isinstance(value, Pubkey) else valuefor key, value in token_account_info._asdict().items()}print("Mint Address:", mint.pubkey())print("\nToken Account Address:", token_account.pubkey())print("Token Account:")print(json.dumps(token_account_data, indent=2))print("\nTransaction Signature:", result.value)if __name__ == "__main__":asyncio.run(main())
Is this page helpful?