Solana 계정 모델
도입
Solana에서 stablecoin 결제를 구현할 때 가장 흔한 착각은 "recipient 주소만 알면 전송이 된다"는 것이다. EVM에서는 token contract가 code와 storage를 함께 갖고 있어 transfer 호출이 비교적 익숙한 모양을 띤다. Solana에서는 program account가 실행 코드를 담고, mutable state는 별도의 account에 저장된다. instruction은 실행할 program뿐 아니라 읽고 쓸 account list를 함께 제출해야 한다.
따라서 Solana 결제 설계의 핵심은 함수 호출보다 account 준비다. merchant token account가 있는지, payer가 account creation 비용을 부담하는지, 어떤 account가 signer/writable인지, owner program이 무엇인지가 checkout 성공률과 운영 비용을 좌우한다.
학습 목표
- Solana의 program/account 분리와 rent, ownership 개념을 설명한다.
- 토큰 결제 구현 시 필요한 account 준비 흐름을 설계한다.
개념 설명
AccountDeclared
Solana의 program/account 분리와 rent, ownership 개념을 설명한다.
PdaDerived
숨길 데이터와 공개할 데이터가 분리되는가
RentOrOwnerMismatch
체인별 ownership 모델을 오해하지 않는가
SettlementRecorded
Solana 계정 모델 이해 점검
account가 상태의 기본 단위다
Solana 공식 문서는 account를 state 저장의 기본 단위로 설명한다. 모든 account는 lamports, data, owner, executable, rent 관련 필드를 가지며, account의 data를 수정할 수 있는 주체는 해당 account의 owner program이다. 이 구조 때문에 Solana transaction은 "어떤 program을 호출한다"와 "그 program이 어떤 account를 읽고 쓸 수 있는가"를 함께 선언한다.
| 개념 | 결제 설계에서의 의미 |
|---|---|
| Program account | executable code를 담는다. SPL Token 또는 Token-2022 program이 여기에 해당한다. |
| Data account | 잔고, mint, 설정 등 mutable state를 담는다. |
| System account | 기본 SOL 보유와 account creation에 관여한다. |
| PDA | program-derived address. program이 직접 private key 없이 서명 가능한 주소처럼 사용한다. |
| Instruction | program 실행 요청. account list와 data가 함께 간다. |
| Transaction | 여러 instruction을 원자적으로 묶는다. account 생성과 transfer를 함께 넣을 수 있다. |
| CPI | program이 다른 program을 호출한다. Transfer Hook 같은 extension에서 중요하다. |
EVM 호출과 Solana instruction 비교
| 항목 | EVM | Solana |
|---|---|---|
| code/state 배치 | contract가 code와 storage를 함께 가짐 | program code와 data account 분리 |
| 호출 입력 | calldata 중심 | instruction + account list |
| 상태 접근 | contract 내부 storage 접근 | account read/write set 명시 |
| 병렬 처리 | 실행 중 storage 충돌 판단 | account list로 병렬성 판단 |
| 토큰 구현 | token별 ERC-20 contract | SPL Token/Token-2022 program과 mint/token account |
이 차이는 성능 설명으로 끝나지 않는다. account list가 틀리면 transaction은 실행 전후 단계에서 실패한다. writable로 넘겨야 할 account를 readonly로 넘기거나, ATA가 없는데 transfer만 보내거나, Token Program과 Token-2022 Program ID를 혼동하면 checkout은 실패한다.
USDC checkout의 ATA 준비 흐름
Solana stablecoin 결제에서 일반적으로 검토할 흐름은 다음과 같다.
여기서 중요한 것은 account creation을 결제 UX의 일부로 본다는 점이다. destination ATA가 없으면 누가 만들고 비용을 내는지 정해야 한다. 사용자에게 한 번에 보이는 transaction이라도 내부적으로는 ATA 생성, transfer, memo 또는 후속 프로그램 호출이 함께 있을 수 있다.
코드로 확인하기
Solana에서는 account가 데이터 저장소이고 program이 그 데이터를 해석한다. 결제 시스템은 "누가 token을 갖고 있는가"뿐 아니라 "어떤 account가 어떤 program에 의해 소유되는가"를 확인해야 한다.
컨트랙트vault PDA account 구조
use anchor_lang::prelude::*;#[account]pub struct MerchantVault { pub merchant: Pubkey, pub mint: Pubkey, pub bump: u8, pub settled_amount: u64,}#[derive(Accounts)]pub struct Settle<'info> { #[account( mut, seeds = [b"merchant-vault", merchant.key().as_ref(), vault.mint.as_ref()], bump = vault.bump, has_one = merchant )] pub vault: Account<'info, MerchantVault>, /// CHECK: merchant는 서명자가 아니라 vault seed와 has_one 검증의 기준 identity다. pub merchant: UncheckedAccount<'info>,}pub fn settle(ctx: Context<Settle>, amount: u64) -> Result<()> { let vault = &mut ctx.accounts.vault; require!(amount > 0, CheckoutError::ZeroAmount); require_keys_eq!(vault.merchant, ctx.accounts.merchant.key()); vault.settled_amount = vault .settled_amount .checked_add(amount) .ok_or(CheckoutError::Overflow)?; Ok(())}#[error_code]pub enum CheckoutError { #[msg("amount must be greater than zero")] ZeroAmount, #[msg("settled amount overflow")] Overflow,}PDA는 임의 주소가 아니라 seed와 bump로 재현 가능한 program-derived address다. vault account가 올바른 merchant와 mint를 가리키는지 검증해야 한다.
클라이언트merchant vault PDA 파생
function deriveMerchantVault(programId: PublicKey, merchant: PublicKey, mint: PublicKey) { return PublicKey.findProgramAddressSync( [ Buffer.from("merchant-vault"), merchant.toBuffer(), mint.toBuffer() ], programId );}클라이언트와 program이 같은 seed 규칙을 써야 reconciliation이 가능하다. seed 규칙이 문서화되지 않으면 운영자는 어느 vault가 어느 merchant의 것인지 추적하기 어렵다.
강의 포인트
Solana 계정 모델은 "프로그램이 stateless하다"는 문장만 외우면 실무로 이어지지 않는다. 결제 제품에서는 account list가 곧 입력 검증 목록이고, rent가 곧 onboarding 비용이며, owner program이 곧 권한 경계다.
강의에서는 다음 질문을 반복해서 던진다.
| 질문 | 왜 중요한가 |
|---|---|
| 이 transaction이 읽고 쓰는 account는 무엇인가 | 병렬성, 실패 원인, 권한 검증의 출발점이다. |
| 이 token account의 owner와 mint는 무엇인가 | 다른 사용자의 token account나 잘못된 mint로 보내는 사고를 막는다. |
| legacy SPL Token과 Token-2022 중 무엇인가 | program ID와 extension decoding이 달라진다. |
| account creation 비용은 누가 내는가 | checkout UX, fee display, refund 정책에 영향을 준다. |
| indexer는 어떤 event/state를 기준으로 완료를 판정하는가 | signature 성공만으로 회계 완료를 확정하면 안 된다. |
실무 예시
Solana USDC 결제 트랜잭션을 설계할 때 제출물은 코드보다 먼저 account matrix여야 한다.
| Account | 역할 | signer | writable | 실패 시나리오 |
|---|---|---|---|---|
| payer wallet | fee payer, 사용자 승인 주체 | yes | yes | SOL 부족으로 fee/rent 지불 실패 |
| source token account | 사용자의 USDC ATA | no | yes | mint 불일치, 잔고 부족, frozen state |
| destination token account | merchant USDC ATA | no | yes | ATA 미생성, owner mismatch |
| mint | USDC mint | no | no | 잘못된 mint allowlist |
| token program | SPL Token 또는 Token-2022 | no | no | program ID 혼동 |
| associated token program | ATA 생성 시 사용 | no | no | 생성 instruction 누락 |
이 표가 있으면 support ticket도 단순해진다. "결제가 실패했다"를 "destination ATA 없음", "fee payer SOL 부족", "program ID mismatch", "source ATA owner mismatch" 같은 진단 가능한 상태로 바꿀 수 있다.
흔한 오해와 실패 시나리오
| 오해 | 실패 장면 | 바로잡는 기준 |
|---|---|---|
| address만 있으면 token을 받을 수 있다. | merchant token account가 없어 transfer가 실패한다. | recipient wallet address와 destination token account를 구분한다. |
| simulation이 통과하면 회계도 완료다. | signature는 성공했지만 indexer 반영 지연으로 dashboard가 누락된다. | transaction status, token balance diff, indexer checkpoint를 분리한다. |
| account list는 SDK가 알아서 채운다. | Transfer Hook이나 Token-2022 extension에서 extra account가 누락된다. | instruction별 required account list를 명시적으로 검토한다. |
| rent 비용은 infra 비용이라 제품과 무관하다. | 신규 merchant onboarding에서 account creation 비용을 누가 내는지 분쟁이 생긴다. | account creation 비용과 환불 불가 비용을 정책 문서에 넣는다. |
실습 과제
- Solana 계정 모델 이해 점검: Solana USDC 결제에 필요한 account, signer, writable 여부, owner program을 표로 작성한다. ATA 생성 비용과 실패 처리를 한 줄씩 붙인다.
- Solana 계정 모델 적용 과제: EVM
transferFrom기반 checkout flow와 Solana instruction + account list flow를 나란히 그리고, 운영 로그에 저장할 필드를signature,sourceAta,destinationAta,mint,tokenProgram,finalizedBalanceDelta로 정리한다.
완료 기준
- Solana account model을 program account, data account, owner program, rent 관점에서 설명했다.
- USDC checkout에 필요한 ATA 준비 흐름과 비용 부담 정책을 설계했다.
- account list failure, owner mismatch, mint mismatch, indexer 지연을 트랜잭션 실패 조건으로 정리했다.
근거 자료
- Solana 계정모델: 07-비EVM/02-Solana-계정모델.md
- Solana Accounts Documentation: https://solana.com/docs/core/accounts