Token Extensions Program을 사용하면 메타데이터 확장을 통해 NFT와 디지털 자산을 생성할 수 있습니다. 이 확장 기능들(메타데이터 포인터와 토큰 메타데이터)을 함께 활용하면 원하는 메타데이터를 네이티브 온체인에 저장할 수 있습니다. 토큰의 mint account에 직접 구성 가능한 키-값 데이터 저장소를 두어 비용과 복잡성을 줄일 수 있습니다.
이 기능은 특히 web3 게임에 매우 유용합니다. 온체인 키-값 저장소 내에 "추가 메타데이터 필드"를 저장할 수 있어, 게임이 NFT 자체 내에 고유한 상태(예: 게임 캐릭터의 스탯이나 인벤토리)를 저장하고 접근할 수 있기 때문입니다.
온체인 프로그램 구축
이 개발자 가이드에서는 Anchor 프로그램을 사용하여 Token Extensions 기반 NFT와 커스텀 메타데이터를 구축하는 방법을 소개합니다. 이 프로그램은 게임 플레이어의 레벨과 수집한 자원을 NFT에 저장합니다.
이 NFT는 Anchor 프로그램에 의해 생성되므로 JavaScript 클라이언트에서 쉽게 민팅할 수 있습니다. 각 NFT는 토큰 메타데이터 인터페이스를 통해 제공되는 기본 구조를 갖습니다:
- 기본 온체인 필드 -
name,symbol,uriuri는 NFT의 오프체인 메타데이터를 담고 있는 오프체인 JSON 파일의 링크입니다
- 직접 정의하는 커스텀 "추가 필드"도 포함됩니다
이 모든 필드는 NFT의 mint account를 가리키는 메타데이터 확장을 사용하여 저장되며, 누구나 또는 어떤 프로그램에서도 접근할 수 있습니다.
게임 내 다른 활용 사례
커스터마이징 가능한 온체인 메타데이터를 갖춘 이러한 NFT는 게임 개발자에게 다양한 흥미로운 가능성을 열어줍니다. 특히 이 메타데이터는 온체인 프로그램에 의해 직접 상호작용하거나 관리될 수 있습니다.
게임 관련 활용 사례들은 다음과 같습니다:
- 플레이어의 레벨과 XP 저장
- 현재 무기와 방어구
- 현재 퀘스트
- 그 외에도 많습니다!
NFT 민팅
NFT를 생성하려면 다음 단계를 수행해야 합니다:
- mint account 생성
- mint account 초기화
- 메타데이터 포인터 계정 생성
- 메타데이터 포인터 계정 초기화
- 메타데이터 계정 생성
- 메타데이터 계정 초기화
- associated token account 생성
- associated token account에 토큰 민팅
- 민트 권한 동결
Rust 프로그램 코드
다음은 Token Extensions Program을 사용하여 NFT를 민팅하는 Rust 코드입니다:
// calculate the space need for the mint account with the desired extensionslet space = ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MetadataPointer]).unwrap();// This is the space required for the metadata account.// We put the metadata into the mint account at the end so we// don't need to create and additional account.// Then the metadata pointer points back to the mint account.// Using this technique, only one account is needed for both the mint// information and the metadata.let meta_data_space = 250;let lamports_required = (Rent::get()?).minimum_balance(space + meta_data_space);msg!("Create Mint and metadata account size and cost: {} lamports: {}",space as u64,lamports_required);system_program::create_account(CpiContext::new(ctx.accounts.token_program.to_account_info(),system_program::CreateAccount {from: ctx.accounts.signer.to_account_info(),to: ctx.accounts.mint.to_account_info(),},),lamports_required,space as u64,&ctx.accounts.token_program.key(),)?;// Assign the mint to the token programsystem_program::assign(CpiContext::new(ctx.accounts.token_program.to_account_info(),system_program::Assign {account_to_assign: ctx.accounts.mint.to_account_info(),},),&token_2022::ID,)?;// Initialize the metadata pointer (Need to do this before initializing the mint)let init_meta_data_pointer_ix =spl_token_2022::extension::metadata_pointer::instruction::initialize(&Token2022::id(),&ctx.accounts.mint.key(),Some(ctx.accounts.nft_authority.key()),Some(ctx.accounts.mint.key()),).unwrap();invoke(&init_meta_data_pointer_ix,&[ctx.accounts.mint.to_account_info(),ctx.accounts.nft_authority.to_account_info()],)?;// Initialize the mint cpilet mint_cpi_ix = CpiContext::new(ctx.accounts.token_program.to_account_info(),token_2022::InitializeMint2 {mint: ctx.accounts.mint.to_account_info(),},);token_2022::initialize_mint2(mint_cpi_ix,0,&ctx.accounts.nft_authority.key(),None).unwrap();// We use a PDA as a mint authority for the metadata account because// we want to be able to update the NFT from the program.let seeds = b"nft_authority";let bump = ctx.bumps.nft_authority;let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];msg!("Init metadata {0}", ctx.accounts.nft_authority.to_account_info().key);// Init the metadata accountlet init_token_meta_data_ix =&spl_token_metadata_interface::instruction::initialize(&spl_token_2022::id(),ctx.accounts.mint.key,ctx.accounts.nft_authority.to_account_info().key,ctx.accounts.mint.key,ctx.accounts.nft_authority.to_account_info().key,"Beaver".to_string(),"BVA".to_string(),"https://arweave.net/MHK3Iopy0GgvDoM7LkkiAdg7pQqExuuWvedApCnzfj0".to_string(),);invoke_signed(init_token_meta_data_ix,&[ctx.accounts.mint.to_account_info().clone(), ctx.accounts.nft_authority.to_account_info().clone()],signer,)?;// Update the metadata account with an additional metadata field in this case the player levelinvoke_signed(&spl_token_metadata_interface::instruction::update_field(&spl_token_2022::id(),ctx.accounts.mint.key,ctx.accounts.nft_authority.to_account_info().key,spl_token_metadata_interface::state::Field::Key("level".to_string()),"1".to_string(),),&[ctx.accounts.mint.to_account_info().clone(),ctx.accounts.nft_authority.to_account_info().clone(),],signer)?;// Create the associated token accountassociated_token::create(CpiContext::new(ctx.accounts.associated_token_program.to_account_info(),associated_token::Create {payer: ctx.accounts.signer.to_account_info(),associated_token: ctx.accounts.token_account.to_account_info(),authority: ctx.accounts.signer.to_account_info(),mint: ctx.accounts.mint.to_account_info(),system_program: ctx.accounts.system_program.to_account_info(),token_program: ctx.accounts.token_program.to_account_info(),},))?;// Mint one token to the associated token account of the playertoken_2022::mint_to(CpiContext::new_with_signer(ctx.accounts.token_program.to_account_info(),token_2022::MintTo {mint: ctx.accounts.mint.to_account_info(),to: ctx.accounts.token_account.to_account_info(),authority: ctx.accounts.nft_authority.to_account_info(),},signer),1,)?;// Freeze the mint authority so no more tokens can be minted to make it an NFTtoken_2022::set_authority(CpiContext::new_with_signer(ctx.accounts.token_program.to_account_info(),token_2022::SetAuthority {current_authority: ctx.accounts.nft_authority.to_account_info(),account_or_mint: ctx.accounts.mint.to_account_info(),},signer),AuthorityType::MintTokens,None,)?;
JavaScript 클라이언트 코드
클라이언트에서 NFT 민팅을 호출하는 것은 매우 간단합니다:
const nftAuthority = PublicKey.findProgramAddressSync([Buffer.from("nft_authority")],program.programId);const mint = new Keypair();const destinationTokenAccount = getAssociatedTokenAddressSync(mint.publicKey,publicKey,false,TOKEN_2022_PROGRAM_ID);const transaction = await program.methods.mintNft().accounts({signer: publicKey,systemProgram: SystemProgram.programId,tokenProgram: TOKEN_2022_PROGRAM_ID,tokenAccount: destinationTokenAccount,mint: mint.publicKey,rent: web3.SYSVAR_RENT_PUBKEY,associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,nftAuthority: nftAuthority[0]}).signers([mint]).transaction();console.log("transaction", transaction);const txSig = await sendTransaction(transaction, connection, {signers: [mint],skipPreflight: true});console.log(`https://explorer.solana.com/tx/${txSig}?cluster=devnet`);
빠른 시작 예제
위 예제는 Solana Games Preset을 기반으로 하며, 이 게임을 위한 JavaScript 및 Unity 클라이언트가 포함된 스캐폴드를 생성합니다. Solana Anchor 프로그램과의 상호작용 설정도 포함되어 있습니다.
다음 명령어로 직접 실행해 볼 수 있습니다:
npx create-solana-game gameName
로컬 환경 설정
이 예제를 로컬에서 실행하려면 Anchor CLI 설치 및 구성을 포함한 Solana 개발을 위한 로컬 환경 설정이 되어 있어야 합니다. 아직 설정하지 않으셨다면 앞서 링크된 설정 가이드를 참고하여 진행하세요.
프로젝트 구조
Anchor 프로젝트는 다음과 같이 구성되어 있습니다:
진입점은 lib.rs 파일에 있습니다. 여기서 프로그램 ID와 명령어를 정의합니다. 명령어는 instructions 폴더에 정의되어 있으며, 상태는 state 폴더에 정의되어 있습니다.
호출은 lib.rs 파일로 도착한 후 instructions로 전달됩니다. instructions는 state를 호출하여 데이터를 가져오고 업데이트합니다.
NFT 민팅 명령어는 instructions 폴더에서 찾을 수 있습니다.
├── src│ ├── instructions│ │ ├── chop_tree.rs│ │ ├── init_player.rs│ │ ├── mint_nft.rs│ │ └── update_energy.rs│ ├── state│ │ ├── game_data.rs│ │ ├── mod.rs│ │ └── player_data.rs│ ├── lib.rs│ └── constants.rs│ └── errors.rs
Anchor 프로그램
create-solana-game 도구로 생성된 Anchor 프로그램 설정을 완료하려면:
cd program으로 프로그램 디렉토리로 이동anchor build를 실행하여 프로그램 빌드anchor deploy를 실행하여 프로그램 배포- 터미널에서 프로그램 ID를 복사하여
lib.rs,anchor.toml, Unity 프로젝트의AnchorService, 그리고 JavaScript를 사용하는 경우anchor.ts파일에 붙여넣기 - 다시 빌드 및 배포
NextJS 클라이언트
create-solana-game 도구로 생성된 NextJS 클라이언트 설정을 완료하려면:
programId를app/utils/anchor.ts에 복사cd app으로 앱 디렉토리로 이동yarn install을 실행하여 Node 의존성 설치yarn dev를 실행하여 클라이언트 시작- Anchor 프로그램을 변경한 후에는 프로그램의 타입을 클라이언트로 복사하여 사용할 수 있도록 하세요. TypeScript 타입은
target/idl폴더에서 찾을 수 있습니다.
로컬에서 예제 실행
Anchor의 test 명령어에 --detach 플래그를 사용하면 프로그램이 배포된 Solana 로컬 테스트 validator를 시작하고 구성합니다(테스트 완료 후에도 validator가 계속 실행됩니다):
cd programanchor test --detach
그런 다음 Solana Explorer를 로컬 테스트 validator(anchor test 명령어 실행 시 시작됨)를 사용하도록 설정하면 트랜잭션을 확인할 수 있습니다:
https://explorer.solana.com/?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899
프로그램은 이미 net에 배포되어 있으므로 devnet에서 사용해 볼 수 있습니다. JavaScript 클라이언트에는 NFT를 민팅하는 버튼도 있습니다. JavaScript 클라이언트 시작하기:
cd appyarn installyarn dev
Unity 프로젝트 열기
먼저 Unity Version 2021.3.32.f1(또는 유사 버전)으로 Unity 프로젝트를 열고, GameScene 또는 LoginScene을 연 후 플레이를 누르세요. 좌측 하단의 에디터 로그인 버튼을 사용하세요.
devnet SOL을 받을 수 없는 경우, 콘솔에서 주소를 복사하여 devnet SOL을 받는 방법 가이드의 안내를 따르세요.
Unity에서 Solana 테스트 validator에 연결
devnet SOL 유지 관리에 대한 부담을 피하려면 Unity 내에서 실행 중인 로컬 테스트 validator에 연결할 수 있습니다. 지갑 홀더 게임 오브젝트에 다음 링크를 추가하세요:
http://localhost:8899ws://localhost:8900
JavaScript 클라이언트 실행
웹 브라우저를 통해 게임 및 프로그램과 상호작용할 수 있도록 JavaScript 클라이언트를 시작하려면:
- 저장소 내
app디렉토리 열기 - Node 의존성 설치
dev명령어를 실행하여 개발 서버 시작
cd appyarn installyarn dev
프로그램을 변경하고 자체 프로그램에 연결하려면 아래 단계를 따르세요.
Is this page helpful?