トークンアカウントとは
トークンアカウントは、Solana上の1つのミントと1つのトークンアカウントオーナーのためのトークンを保管します。
/// 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>,}
ここで、*rsowner*とは、token
accountからトークンを転送、バーン、または委任できる権限を意味します。アカウントのprogram
ownerは依然としてToken ProgramまたはToken Extensions Programです。
各token accountは正確に1つのミントに紐付けられています。つまり、token
accountはtoken
accountの*rsmint*フィールドで識別される1つのトークンの単位のみを保持できます。
Associated Token Accountとは
associated token account(ATA)は、ウォレットとミントのデフォルトのtoken accountです。 Associated Token Programは、ウォレットアドレス、トークンプログラムアドレス、およびミントアドレスからATAアドレスを導出します。
Associated Token Programによって作成されたtoken accountのみがassociated token accountと呼ばれます。
Associated Token Programは、標準的で決定論的なアドレスでtoken accountを作成する方法です。作成されたアカウントは依然としてToken ProgramまたはToken Extensions Programが所有するtoken accountであり、Associated Token Programが所有するわけではありません。
Associated Token Programは、 address.rsに示されているようにATAアドレスを導出します:
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つのATAアドレスが存在します。Associated
Token Programはそのアドレスに標準的なtoken
accountを作成し、作成されたアカウントは依然としてToken ProgramまたはToken
Extensions Programで定義された*rsAccount*タイプを使用します。
Associated Token Accountの作成方法
Associated Token Accountの作成には、Associated Token
Programの*rsCreateまたはrsCreateIdempotent*インストラクションを使用します。Associated
Token
ProgramはATAアドレスを導出し、ATAアドレスにアカウントを作成し、そのアカウントをToken
ProgramまたはToken Extension Programが所有するtoken accountとして初期化します。
推奨
ほとんどのアプリケーションでは、System ProgramとToken Programのinstructionsを直接呼び出してtoken accountを作成するのではなく、Associated Token Programを通じてtoken accountを作成してください。associated token accountは、所有者、Token Program、およびMintから導出される決定論的なアドレスを使用するため、ウォレットやアプリケーションが特定のMintのデフォルトtoken accountを見つけやすくなります。
ソースリファレンス
| 項目 | 説明 | ソース |
|---|---|---|
Create | 導出されたATAアドレスにassociated token accountを作成します。 | ソース |
CreateIdempotent | associated token accountを作成しますが、同じ所有者とMintのATAが既に存在する場合でも成功します。 | ソース |
process_create_associated_token_account | ATAアドレスを導出し、*rscreate_pda_account*と選択されたToken ProgramへのCPIを使用してtoken accountを初期化します。選択されたProgramがToken Extension Programの場合、ATAプロセッサは不変の所有者拡張機能も初期化します。 | ソース |
create_pda_account | *rsprocess_create_associated_token_account*が使用するヘルパーで、System ProgramへのCPIによってPDAアカウントを作成します。 | ソース |
Typescript
以下のKit例は、@solana/kitを使用した推奨アプローチを示しています。
@solana/web3.jsを使用したレガシー例も参考として含まれています。
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())
token accountの作成方法
token accountの作成には2つのinstructionsが必要です:
- System Programの
CreateAccountinstructionは、新しいrent免除アカウントを作成し、Token Programを新しいアカウントのプログラム所有者として割り当てます。 - Token
Programの
InitializeAccount、InitializeAccount2、またはInitializeAccount3instructionは、ミントと所有者用に新しいアカウントを初期化します。
CreateAccount instructionとtoken
account初期化instructionを同じトランザクションに含めてください。
token accountの初期化中、Token Programはアカウントがまだ初期化されておらず、rent免除されていることを確認します。
以下のセクションでは、System ProgramとToken Program instructionsを直接呼び出してtoken accountを作成する方法を示しています。ほとんどのアプリケーションでは、代わりにAssociated Token Programを使用してください。associated token accountを使用しない特定の理由がある場合、または独自のSolanaプログラムからSystem ProgramとToken Program instructionsにCPIを行ってカスタムPDA token accountsを作成する必要がある場合は、System ProgramとToken Programの直接呼び出しを使用してください。
ソース参照
| 項目 | 説明 | Token Program | Token Extensions Program |
|---|---|---|---|
Account | すべてのtoken accountに保存される基本的なtoken accountフィールド。 | ソース | ソース |
InitializeAccount | アカウントリストに所有者とrent sysvarアカウントを期待するtoken account初期化instruction。 | ソース | ソース |
InitializeAccount2 | アカウントリストの代わりにinstruction dataで所有者を渡すtoken account初期化instruction。 | ソース | ソース |
InitializeAccount3 | instruction dataで所有者を渡し、rent sysvarアカウントを必要としないtoken account初期化instruction。 | ソース | ソース |
_process_initialize_account | token account初期化のための共有プロセッサロジック。 | ソース | ソース |
process_initialize_account | *rsInitializeAccount*のパブリックハンドラ。 | ソース | ソース |
process_initialize_account2 | *rsInitializeAccount2*のパブリックハンドラ。 | ソース | ソース |
process_initialize_account3 | *rsInitializeAccount3*のパブリックハンドラ。 | ソース | ソース |
Typescript
以下の Kit の例は、@solana/kit を使用した推奨アプローチを示しています。
@solana/web3.js を使用したレガシー例も参考として含まれています。
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?