Solana SPL Token
SPL Token: Solana 블록체인에서 사용되는 표준 토큰 프로그램(Token Program)
이더리움의 ERC-20과 유사하지만, Solana는 컨트랙트가 아닌 Program + Account 모델을 사용한다.
Solana의 계정 모델
Solan에서 Walle은 토큰 잔액을 직접 저장하지 않는다.
Solana의 모든 상태(state)는 Account에 저장되며, Account는 항상 특정 Program(owner) 에 의해 관리된다. SPL Token의 잔액은 (wallet, mint) 쌍에 대해 1개만 존재하는Associated Token Account(ATA)에 저장된다.
ATA(Associated Token Account) – SPL Token의 실제 잔액을 저장하는 계정
- PDA(Program Derived Address)의 한 종류이다.
- Token program이 Account.owner이다.
- ATA의 data에는 토큰 소유자 주소(wallet), mint 주소, 잔액(amount)가 포함된다.
- (wallet, mint) 기준으로 1개만 존재한다.
PDA(Program Derived Address)
- 개인키가 없는 주소이다.
- 사용자가 직접 서명할 수 없다.
- Program만이 seed + bump를 통해 권한을 행사한다.
- 프로그램 전용 계정에 사용된다.
PA(Program-owned Account)
- 특정 Program이 owner로 설정된 계정
- Program이 해당 account의 data를 수정할 수 있다.
- SPL Token의 Mint, Token Account 모두 해당된다.
Mint 계정(토큰 자체): 토큰의 종류를 정의하는 계정
- 개별 사용자 잔액을 저장하지 않는다.
- Mint 계정이 관리하는 것
n 총 발행량(supply)
n 소수점 자리수(decimals)
n 발행 권한(Mint Authority)
n 동결 권한(Freeze Authority)
토큰 전송 구조
SPL Token 전송은 ATA – ATA 사이에서만 발생한다.
수신자의 ATA가 없으면 --fund-recipient를 통한 자동 생성 또는 실패한다.
Solana SPL Token 발행, 지갑 간 전송 실습
1. Solana CLI 설치
# 설치
curl -LO https://github.com/solana-labs/solana/releases/latest/download/solana-release-aarch64-apple-darwin.tar.bz2
# 압축 해제
tar -xjf solana-release-*.tar.bz2
# Solana CLI 실행 파일 경로를 PATH에 추가
echo 'export PATH="$PWD/solana-release/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
2. Rust 설치 (SPL Token CLI 빌드용)
# Rust 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 환경 변수 설정
source "$HOME/.cargo/env"
3. SPL Token CLI 설치
cargo install spl-token-cli
4. Solana 네트워크를 Devnet으로 설정
# 테스트용 Devnet 사용
solana config set --url https://api.devnet.solana.com
# 설정 확인
solana config get
| # 결과 Config File: /Users/user/.config/solana/cli/config.yml RPC URL: https://api.devnet.solana.com WebSocket URL: wss://api.devnet.solana.com/ (computed) Keypair Path: /Users/user/.config/solana/id.json Commitment: confirmed |
5. 지갑 A 생성(발행자/최초 보유자)
solana-keygen new -o ~/walletA.json
| # 결과 Wrote new keypair to /Users/user/walletA.json ================================================================================== pubkey: AttaoJLVRvFVRnAuncKUFSnfJuSvTrxMBYACeLkUfHCx ================================================================================== Save this seed phrase and your BIP39 passphrase to recover your new keypair: ivory curve protect rabbit wreck fashion photo until cousin punch blanket describe ================================================================================== |
# 지갑 A의 주소 확인
solana address -k ~/walletA.json
# 결과
AttaoJLVRvFVRnAuncKUFSnfJuSvTrxMBYACeLkUfHCx
# 지갑 A를 CLI 기본 지갑으로 설정
solana config set --keypair ~/walletA.json
| # 결과 Config File: /Users/user/.config/solana/cli/config.yml RPC URL: https://api.devnet.solana.com WebSocket URL: wss://api.devnet.solana.com/ (computed) Keypair Path: /Users/user/walletA.json Commitment: confirmed |
# 지갑 A에 SOL 에어드롭
solana airdrop 2
# 확인
solana balance
6. SPL 토큰(Mint) 주소 생성 – 1번만 수행
spl-token create-token
| # 결과 Creating token CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA Address: CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT Decimals: 9 Signature: 3mBHvLYeCb3gyG6paFHBYtFBwQB56eVgcQwtert8W49fVhfBK2KaEs1xSe386DRD5uqY9jxtjTUAzQoz33pcrX1X |
7. 지갑 A의 토큰 계정 주소 ATA 생성(Mint별 1개 생성)
# 지갑 A의 ATA 계정 생성
spl-token create-account CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT
| # 결과 Creating account 8x5LQQraaQKagEZ6SWU5c9SNB6aDv4khLUM1jxPuQBgA Signature: 4remehCPutZD7gzmvmKYzb5qxhHMCYSDkNxd82nr4RPX94gm62FtCProYU8NSQK73ub84dtaJejWvtyfhHzpcZ3A |
8. 지갑 A에게 토큰 민트
spl-token mint \
CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT \
100
| # 결과 Minting 100 tokens Token: CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT Recipient: 8x5LQQraaQKagEZ6SWU5c9SNB6aDv4khLUM1jxPuQBgA Signature: 3SuVSz72Em4TFbcth9NYLz3dVydpn39QgqosURyjfqR7VDtPDH74GbR2qZBKi7hxokGSDAZSKE64RKRbsLyaWu76 |
9. 토큰 민트 잔액 확인
# 지갑 A가 가진 토큰
spl-token balance CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT
# 전체 발행량
spl-token supply CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT
10. 지갑 B 생성(받는 사람)
# 지갑 생성
solana-keygen new -o ~/walletB.json
| # 결과 Wrote new keypair to /Users/user/walletB.json ======================================================================== pubkey: 4iFQvwMyaMdhfzD64jufDPMW58ofHr69MGzBJuFZdVxy ======================================================================== Save this seed phrase and your BIP39 passphrase to recover your new keypair: enrich ice news asthma inner various top recipe guard diamond ahead idle ======================================================================== |
# 지갑 B 주소 확인
solana address -k ~/walletB.json
# 지갑 B도 SOL 받음
solana airdrop 1 $(solana address -k ~/walletB.json)
>> 지갑 B에 SOL이 airdrop 되지 않는 경우
#1 https://faucet.solana.com/ 에서 SOL 받기
#2 https://solfaucet.com/ 에서 SOL 받기
#3 지갑 A에서 지갑 B에 SOL 보내기
solana transfer \
$(solana address -k ~/walletB.json) \
0.05 \
--from ~/walletA.json \
--allow-unfunded-recipient
# 지갑 B의 SOL 잔액 확인
solana balance $(solana address -k ~/walletB.json)
11. 지갑 A → 지갑 B 토큰 23개 전송 (ATA 자동 생성)
| # 지갑 B의 ATA 자동 생성과 토큰 23개 전송 spl-token transfer \ CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT \ 23 \ $(solana address -k ~/walletB.json) \ --owner ~/walletA.json \ --fund-recipient |
| # 결과 Transfer 23 tokens Sender: 8x5LQQraaQKagEZ6SWU5c9SNB6aDv4khLUM1jxPuQBgA Recipient: 4iFQvwMyaMdhfzD64jufDPMW58ofHr69MGzBJuFZdVxy Recipient associated token account: 5AkJxpQFPX5G8mB4T1dnrLowsNzSSLuGBqxiNUM5CcKX Funding recipient: 5AkJxpQFPX5G8mB4T1dnrLowsNzSSLuGBqxiNUM5CcKX Signature: sthsJufFDrTBY3i1PnYNgrkGvZbf7cVDVTyFZgR6ztqobwUizqbk1cBXQG3BapxsZUs2sDikRL1FVdW2cGrwoG5 |
12. 지갑 B → 지갑 A로 토큰 6개 전송
| spl-token transfer \ CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT \ 6 \ $(solana address -k ~/walletA.json) \ --owner ~/walletB.json |
| # 결과 Transfer 6 tokens Sender: 5AkJxpQFPX5G8mB4T1dnrLowsNzSSLuGBqxiNUM5CcKX Recipient: AttaoJLVRvFVRnAuncKUFSnfJuSvTrxMBYACeLkUfHCx Recipient associated token account: 8x5LQQraaQKagEZ6SWU5c9SNB6aDv4khLUM1jxPuQBgA Signature: 2FYFXiGU1E2rLaw5RwDLRRfviSZ3yhDuoLa6r8k8vxL4TVSNRmwMa15vVb1YjGNeGoPdAPtZ9z82H9rwfiwkiKZa |
13. 각 지갑의 토큰 계정 확인
| # 지갑 A spl-token accounts --owner $(solana address -k ~/walletA.json) # 지갑 B spl-token accounts --owner $(solana address -k ~/walletB.json) |
| # 지갑 A 결과 Token Balance ----------------------------------------------------- CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT 83 # 지갑 B 결과 Token Balance ----------------------------------------------------- CfjWNEwbscKUNusDtvphhgEx34NyRtEUc2QnVguS9rRT 17 |
Phantom에 지갑 가져오기(import)
Phantom은 CLI에서 만든 keypair(json) 을 그대로 가져올 수 있다.
Phantom 열기 > 설정 > 계정 관리 > 계정 추가 > 비공개 키 가져오기
| cat ~/walletA.json |
| # 결과[227,193,251,43,42,184,107,31,120,202,131,234,78,51,247,6,93,4,71,68,136,254,32,142,16,106,182,190,162,145,254,111,147,4,32,221,82,218,169,191,255,247,171,176,77,237,223,208,85,248,111,118,248,97,185,238,181,178,239,72,78,146,53,181] |
| cat ~/walletB.json |
| # 결과 [119,176,24,129,248,16,85,85,183,231,143,250,249,47,251,104,96,176,15,18,155,129,59,31,240,155,162,39,12,249,206,240,55,36,43,153,168,152,236,172,74,45,132,222,74,44,12,68,65,117,243,240,64,85,94,238,252,239,113,208,136,2,98,126] |
>> 숫자 배열 전체를 복사하여 비공개 키 입력칸에 붙여넣기