토큰 계정 생성하기

토큰 계정이란 무엇인가요?

토큰 계정은 특정 토큰의 잔액을 저장합니다. 각 토큰 계정은 정확히 하나의 민트와 연결되어 있으며 토큰 잔액과 추가 세부 정보를 추적합니다.

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

소스 코드에서 개발자들은 토큰 계정을 Account 타입이라고 부릅니다. Token ProgramToken Extensions Program 모두 토큰 계정에 대해 동일한 기본 구현을 공유합니다.

토큰을 보유하려면 해당 민트에 대한 토큰 계정이 필요합니다. 각 토큰 계정은 다음을 추적합니다:

  1. 민트: 보유하는 특정 토큰 유형
  2. 소유자: 이 계정에서 토큰을 전송할 수 있는 권한을 가진 주체

예시: USD 코인 (USDC)

  • 민트 주소: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  • Circle의 토큰 계정: 3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa
  • 이 계정은 USDC 토큰만 보유합니다
  • Circle은 토큰 계정 소유자 권한을 통해 토큰을 제어합니다: 7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE

솔라나 익스플로러에서 이 토큰 계정의 세부 정보를 볼 수 있습니다.

"소유자" 용어 이해하기

"소유자"라는 용어는 두 가지 다른 맥락에서 사용될 수 있습니다:

  1. 토큰 계정 소유자: 토큰을 전송, 소각 또는 위임할 수 있는 권한을 가진 주체입니다. 이는 토큰 계정의 데이터에 저장됩니다.

  2. 프로그램 소유자: 계정을 소유한 프로그램(토큰 계정의 경우 항상 Token Program 또는 Token Extensions Program)입니다.

토큰 계정을 다룰 때 "소유자"는 일반적으로 계정을 소유한 프로그램이 아닌 토큰을 전송할 수 있는 권한을 가진 주체를 의미합니다.

Associated Token Account란 무엇인가요?

Associated token account(ATA)는 지갑이 특정 토큰을 보유하기 위한 기본 토큰 계정입니다. 이는 Associated Token Program에 의해 생성된 결정론적 주소(PDA)를 사용합니다.

Associated Token Program에 의해 생성된 토큰 계정만이 "associated token accounts"라고 불립니다.

ATA는 결정론적 주소를 사용하여 주어진 민트에 대한 지갑의 토큰 계정을 쉽게 찾을 수 있게 합니다. 여기에서 파생 구현을 확인하세요.

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

모든 지갑과 토큰 조합에 대해 정확히 하나의 ATA 주소가 있습니다. 이로 인해 토큰 계정 주소를 수동으로 추적할 필요가 없어집니다—항상 올바른 주소를 도출할 수 있습니다.

Associated Token Program의 작동 방식

Associated Token Program은 다음과 같은 도우미 역할을 합니다:

  • 결정론적 주소(PDA)에 토큰 계정 생성
  • Token Program에 Cross-Program Invocation(CPI) 수행
  • 자체적으로 상태를 유지하지 않음

결과적으로 생성된 토큰 계정은 Token Program이 소유하며 표준 Account 구조를 사용합니다.

토큰 계정을 생성하는 방법

토큰 계정을 생성하려면 InitializeAccount 명령이 필요합니다. 여기에서 구현을 확인하세요.

토큰 계정을 생성하려면 두 가지 명령이 필요합니다:

  1. System Program: 토큰 계정을 위한 공간이 할당된 계정을 생성하고 소유권을 Token Program에 이전
  2. Token Program: 토큰 계정 데이터 초기화

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_PROGRAM_ADDRESS
} from "@solana-program/token";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost: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 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 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 program)
// Invokes the system program
const createTokenAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: tokenAccount,
lamports: tokenAccountRent,
space: tokenAccountSpace,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize token account data
// Invokes the token 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 transactionSignature2 = getSignatureFromTransaction(signedTokenAccountTx);
console.log("Token Account Address:", tokenAccount.address);
console.log("Transaction Signature:", transactionSignature2);
Console
Click to execute the code.

Rust

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

Associated Token Account를 생성하는 방법

Associated Token Account(ATA)를 생성하려면 Create 명령어가 필요합니다. 여기에서 구현을 확인하세요.

이 단일 명령어는 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_PROGRAM_ADDRESS,
findAssociatedTokenPda
} from "@solana-program/token";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost: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 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 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_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);
Console
Click to execute the code.

Rust

use anyhow::Result;
use solana_client::nonblocking::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::{id as token_program_id, instruction::initialize_mint, state::Mint};
#[tokio::main]
async fn main() -> Result<()> {
// Create connection to local validator
let client = RpcClient::new_with_commitment(
String::from("http://localhost:8899"),
CommitmentConfig::confirmed(),
);
let latestBlockhash = client.get_latest_blockhash().await?;
// 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)
.await?;
client.confirm_transaction(&airdrop_signature).await?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature).await?;
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)
.await?;
// Instruction to create new account for mint (token 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_program_id(), // program id
);
// Instruction to initialize mint account data
let initialize_mint_instruction = initialize_mint(
&token_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],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!("Mint Address: {}", mint.pubkey());
println!("Transaction Signature: {}", transaction_signature);
// Get the latest blockhash for the next transaction
let latestBlockhash = client.get_latest_blockhash().await?;
// 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_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 (owner)
&mint.pubkey(), // mint address
&token_program_id(), // program id
);
// Create transaction for associated token account creation
let transaction = Transaction::new_signed_with_payer(
&[create_ata_instruction],
Some(&fee_payer.pubkey()),
&[&fee_payer],
latestBlockhash,
);
// Send and confirm transaction
let transaction_signature = client.send_and_confirm_transaction(&transaction).await?;
println!(
"Associated Token Account Address: {}",
associated_token_account.to_string()
);
println!("Transaction Signature: {}", transaction_signature);
Ok(())
}
Console
Click to execute the code.

Python

Python
#!/usr/bin/env python3
import asyncio
from solana.rpc.async_api import AsyncClient
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.message import MessageV0
from spl.token.instructions import create_associated_token_account, get_associated_token_address
from spl.token.constants import TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID
async def main():
rpc = AsyncClient("http://localhost:8899")
payer = Keypair()
owner = Keypair()
# Example mint address (USDC on devnet)
mint_address = Pubkey.from_string("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")
# Get associated token account address
associated_token_account = get_associated_token_address(owner.pubkey(), mint_address)
async with rpc:
# Get latest blockhash
recent_blockhash = await rpc.get_latest_blockhash()
# Create associated token account instruction
create_token_account_instruction = create_associated_token_account(
payer=payer.pubkey(),
owner=owner.pubkey(),
mint=mint_address,
token_program_id=TOKEN_PROGRAM_ID
)
# Create message
message = MessageV0.try_compile(
payer=payer.pubkey(),
instructions=[create_token_account_instruction],
address_lookup_table_accounts=[],
recent_blockhash=recent_blockhash.value.blockhash
)
# Create transaction
transaction = VersionedTransaction(message, [payer])
print(f"Payer: {payer.pubkey()}")
print(f"Owner: {owner.pubkey()}")
print(f"Associated Token Account: {associated_token_account}")
print(f"Mint: {mint_address}")
print(f"Associated token account created successfully")
if __name__ == "__main__":
asyncio.run(main())
Console
Click to execute the code.

Is this page helpful?

목차

페이지 편집