トークンアカウントを作成する
トークンアカウントとは?
トークンアカウントは、Solanaのtoken accountと呼ばれるアカウントタイプで、特定のトークン(ミント)に対する個人の所有権情報を保存します。各token accountは単一のミントに関連付けられ、トークン残高や所有者などの詳細を追跡します。
/// 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>,}
ソースコードでは、開発者はトークンアカウントを Account
タイプと呼んでいることに注意してください。Token
ProgramとToken
Extensions
Programは、トークンアカウントの基本実装を共有しています。
特定のミントのトークンを保有するには、ユーザーはまずトークンアカウントを作成する必要があります。各トークンアカウントは以下を追跡します:
- 特定のミント(トークンアカウントが保有するトークンの種類)
- 所有者(アカウントからトークンを転送できる権限を持つ者)
Solana上のUSDコイン(USDC)を使用した例を示します:
- USDコインのミントアドレス:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
- Circle(USDコインの発行者)は
3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
にトークンアカウントを持っています - このトークンアカウントはUSDコイントークン(ミント)の単位のみを保有します
- Circleは
7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE
に所有者権限を持ち、これらのトークンを転送できます
Solana Explorerでこのトークンアカウントの詳細を確認できます。
「所有者」という用語は2つの異なる文脈で使用されています:
-
トークンアカウントの「所有者」 - これはトークンアカウントのデータに保存されているアドレスで、Token Programで定義されているAccountタイプの「owner」フィールドです。所有者はアカウントからトークンの転送、バーン、または委任を行うことができます。このアドレスは、プログラム所有者と区別するために、トークンアカウントの「権限」と呼ばれることもあります。
-
プログラム「owner」 - これはSolana上でアカウントデータを所有するプログラムを指します。トークンアカウントの場合、これは常にToken ProgramまたはToken Extension Programのいずれかであり、基本的なSolanaのAccount タイプの「owner」フィールドで指定されています。
トークンアカウントを扱う場合、「owner」は通常、アカウントを所有するプログラムではなく、トークンを使用する権限を持つ権限者を指します。
Associated Token Accountとは何か?
Associated token accountは、Associated Token Programによって作成されたProgram Derived Address (PDA)のアドレスを持つトークンアカウントです。Associated token accountは、ユーザーが特定のトークン(ミント)の単位を保持するためのデフォルトのトークンアカウントと考えることができます。
「associated token accounts」という用語は、Associated Token Programが作成するトークンアカウントにのみ適用されます。
Associated token accountsは、任意のミントに対するユーザーのトークンアカウントを決定論的に見つける方法を提供します。導出の実装はこちらで確認できます。
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)}
この決定論的な導出により、ウォレットアドレスとトークンミントの任意の組み合わせに対して、正確に1つのassociated token accountアドレスが存在することが保証されます。このアプローチにより、任意のトークンミントに対するユーザーのトークンアカウントを簡単に見つけることができ、トークンアカウントアドレスを個別に追跡する必要がなくなります。
Associated Token
Programは、決定論的なアドレス(PDA)を持つトークンアカウントを作成するヘルパープログラムとして機能します。Associated
token accountを作成する際、Associated Token ProgramはToken ProgramまたはToken
Extension Programに対してCPI(Cross-Program
Invocation)を行います。トークンプログラムは作成されたアカウントを所有し、そのアカウントはトークンプログラムで定義されているのと同じAccount
型構造を持ちます。Associated
Token Programは状態を維持しません -
単に決定論的なアドレス(PDA)でトークンアカウントを作成する標準化された方法を提供するだけです。
トークンアカウントの作成方法
トークンアカウントを作成するには、
InitializeAccount
命令を呼び出します。この命令の実装は
こちらで確認できます。
token accountを作成するトランザクションには2つの命令が必要です:
- System Programを呼び出して、token accountを作成し、スペースを割り当て、所有権をToken Programに移転します。
- Token Programを呼び出して、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 exampleconst rpc = createSolanaRpc("http://127.0.0.1:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate keypairs for fee payerconst feePayer = await generateKeyPairSigner();// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Instruction to create new account for mint (token 2022 program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token 2022 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 2,mintAuthority: feePayer.address});const instructions = [createAccountInstruction, initializeMintInstruction];// Create transaction messageconst 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 transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address: ", mint.address);console.log("Transaction Signature: ", transactionSignature);// Generate keypair to use as address of token accountconst tokenAccount = await generateKeyPairSigner();// Get token account size (in bytes)const tokenAccountSpace = BigInt(getTokenSize());// Get minimum balance for rent exemptionconst tokenAccountRent = await rpc.getMinimumBalanceForRentExemption(tokenAccountSpace).send();// Instruction to create new account for token account (token 2022 program)// Invokes the system programconst 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 programconst initializeTokenAccountInstruction = getInitializeAccount2Instruction({account: tokenAccount.address,mint: mint.address,owner: feePayer.address});const instructions2 = [createTokenAccountInstruction,initializeTokenAccountInstruction];// Create transaction message for token account creationconst 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 transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTokenAccountTx,{ commitment: "confirmed" });// Get transaction signatureconst tokenAccountTxSignature =getSignatureFromTransaction(signedTokenAccountTx);console.log("Token Account Address:", tokenAccount.address);console.log("Transaction Signature:", tokenAccountTxSignature);
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 validatorlet 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 payerlet fee_payer = Keypair::new();// Airdrop 1 SOL to fee payerlet 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 mintlet mint = Keypair::new();// Get default mint account size (in bytes), no extensions enabledlet 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, // lamportsmint_space as u64, // space&token_2022_program_id(), // program id);// Instruction to initialize mint account datalet initialize_mint_instruction = initialize_mint(&token_2022_program_id(),&mint.pubkey(), // mint&fee_payer.pubkey(), // mint authoritySome(&fee_payer.pubkey()), // freeze authority2, // decimals)?;// Create transaction and add instructionslet transaction = Transaction::new_signed_with_payer(&[create_account_instruction, initialize_mint_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],recent_blockhash,);// Send and confirm transactionlet 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 accountlet 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, // lamportstoken_account_space as u64, // space&token_2022_program_id(), // program id);// Instruction to initialize token account datalet 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 instructionslet 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 transactionlet transaction_signature = client.send_and_confirm_transaction(&transaction)?;println!("Token Account Address: {}", token_account.pubkey());println!("Transaction Signature: {}", transaction_signature);Ok(())}
associated token accountの作成方法
associated token accountを作成するには、
Create
命令を呼び出します。この命令の実装は
こちらで確認できます。
associated token accountを作成する命令は、自動的にSystem Programを呼び出してtoken accountを作成し、Token Programを呼び出してtoken accountデータを初期化します。これは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 exampleconst rpc = createSolanaRpc("http://127.0.0.1:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate keypairs for fee payerconst feePayer = await generateKeyPairSigner();// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Instruction to create new account for mint (token 2022 program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token 2022 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 2,mintAuthority: feePayer.address});const instructions = [createAccountInstruction, initializeMintInstruction];// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions(instructions, tx));// Sign transaction message with all required signersconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address: ", mint.address.toString());console.log("Transaction Signature: ", transactionSignature);// Use findAssociatedTokenPda to derive the ATA addressconst [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 transactionconst { value: latestBlockhash2 } = await rpc.getLatestBlockhash().send();// Create instruction to create the associated token accountconst createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({payer: feePayer,mint: mint.address,owner: feePayer.address});// Create transaction messageconst transactionMessage2 = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash2, tx),(tx) => appendTransactionMessageInstructions([createAtaInstruction], tx));// Sign transaction message with all required signersconst signedTransaction2 =await signTransactionMessageWithSigners(transactionMessage2);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction2,{ commitment: "confirmed" });// Get transaction signatureconst 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 validatorlet 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 payerlet fee_payer = Keypair::new();// Airdrop 1 SOL to fee payerlet 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 mintlet mint = Keypair::new();// Get default mint account size (in bytes), no extensions enabledlet 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, // lamportsmint_space as u64, // space&token_2022_program_id(), // program id);// Instruction to initialize mint account datalet initialize_mint_instruction = initialize_mint(&token_2022_program_id(),&mint.pubkey(), // mint&fee_payer.pubkey(), // mint authoritySome(&fee_payer.pubkey()), // freeze authority2, // decimals)?;// Create transaction and add instructionslet transaction = Transaction::new_signed_with_payer(&[create_account_instruction, initialize_mint_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],recent_blockhash,);// Send and confirm transactionlet 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 transactionlet recent_blockhash = client.get_latest_blockhash()?;// Derive the associated token account address for fee_payerlet 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 accountlet 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 instructionlet transaction = Transaction::new_signed_with_payer(&[create_ata_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],recent_blockhash,);// Send and confirm transactionlet 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?