Anchor

RustとAnchorでトークンを作成する

このガイドでは、RustAnchorフレームワーク、およびMetaplex Token MetadataプログラムへのCPIを使用して、Solana上でメタデータ付きのファンジブルトークンを作成する方法を説明します。

構築するもの

以下を実行する単一のAnchor命令:

  • 新しいSPLトークンミントを作成
  • 支払者用のAssociated Token Accountを作成
  • 名前、シンボル、URIを持つメタデータアカウントを作成
  • 支払者に初期トークン供給量をミント

概要

Anchor (Rust) を使用してSolana上でファンジブルSPLトークンを作成し、初期供給量をミントし、CPIを介してMetaplex Token Metadata(名前、シンボル、URI)を付与します。

  • 1つの命令で:mint + ATA + metadataを初期化し、供給量をミント
  • 使用ツール:SPL Token + Metaplex Token Metadata CPI
  • テスト済み:Anchor 0.32.1、Solana Agave 3.1.6
  • ファンジブルトークン専用。NFTにはMaster Edition + decimals=0 + supply=1が必要

対象範囲外

Token-2022拡張機能、秘匿転送、権限の失効、メタデータの更新、完全なNFTフロー、メインネットデプロイ。

クイックスタート

目次: プログラム · テストクライアント · よくあるエラー

  1. anchor init anchor-spl-token
  2. Cargo.tomlmetadataフィーチャー付きのanchor-splを追加
  3. Anchor.tomlでローカルネット用にToken Metadataプログラムをクローン
  4. プログラムコードを貼り付けてanchor testを実行

前提条件

  • Rust がインストール済み(rustup.rs
  • Solana CLI がインストール済み(docs.solana.com
  • Anchor CLI がインストール済み(cargo install --git https://github.com/coral-xyz/anchor anchor-cli
  • テスト実行用の Node.jsYarn
  • トランザクション手数料用のSOLを持つSolanaウォレット

テスト済み構成

このガイドは以下のバージョンでテストされています:

ツールバージョン
Anchor CLI0.32.1
Solana CLI3.1.6 (Agave)
Rust1.92.0
Node.js22.15.1
Yarn1.22.x

初期セットアップ

新しいAnchorプロジェクトを初期化します:

anchor init anchor-spl-token
cd anchor-spl-token

Cargo.tomlの設定

programs/anchor-spl-token/Cargo.tomlを更新します:

programs/anchor-spl-token/Cargo.toml
1[package]
2name = "anchor-spl-token"
3version = "0.1.0"
4description = "Created with Anchor"
5edition = "2021"
6
7[lib]
8crate-type = ["cdylib", "lib"]
9name = "anchor_spl_token"
10
11[lints.rust]
12unexpected_cfgs = { level = "warn", check-cfg = [
13 'cfg(feature, values("custom-heap", "custom-panic", "anchor-debug"))'
14] }
15
16[features]
17default = []
18cpi = ["no-entrypoint"]
19no-entrypoint = []
20no-idl = []
21no-log-ix-name = []
22idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
23
24[dependencies]
25anchor-lang = "0.32.1"
26anchor-spl = { version = "0.32.1", features = ["token", "metadata", "associated_token"] }

重要

idl-buildフィーチャーにはanchor-spl/idl-build必ず含めてください。含めない場合、no function or associated item named 'create_type' found for struct 'anchor_spl::token::Mint'のようなエラーが発生します。

Anchor.tomlの設定

ローカルテスト用にToken MetadataプログラムをクローンするためにAnchor.tomlを更新します:

Anchor.toml
1[toolchain]
2package_manager = "yarn"
3
4[features]
5resolution = true
6skip-lint = false
7
8[programs.localnet]
9anchor_spl_token = "YOUR_PROGRAM_ID_HERE"
10
11[registry]
12url = "https://api.apr.dev"
13
14[provider]
15cluster = "localnet"
16wallet = "~/.config/solana/id.json"
17
18[scripts]
19test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
20
21[test.validator]
22url = "https://api.mainnet-beta.solana.com"
23bind_address = "127.0.0.1"
24
25[[test.validator.clone]]
26address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

  • bind_address = "127.0.0.1"はAgave 3.xバリデータで必須です(0.0.0.0を使用するとパニックが発生します)
  • [[test.validator.clone]]セクションはMetaplex Token Metadataプログラムをメインネットからクローンします

package.jsonの設定

package.json
1{
2 "license": "ISC",
3 "scripts": {
4 "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
5 "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
6 },
7 "dependencies": {
8 "@coral-xyz/anchor": "^0.32.1",
9 "@metaplex-foundation/mpl-token-metadata": "^3.4.0",
10 "@solana/spl-token": "^0.4.9"
11 },
12 "devDependencies": {
13 "chai": "^4.3.4",
14 "mocha": "^9.0.3",
15 "ts-mocha": "^10.0.0",
16 "@types/bn.js": "^5.1.0",
17 "@types/chai": "^4.3.0",
18 "@types/mocha": "^9.0.0",
19 "typescript": "^5.7.3",
20 "prettier": "^2.6.2"
21 }
22}

プログラム

インポートとテンプレート

ここでは、すべてのインポートを定義し、programs/anchor-spl-token/src/lib.rsにAccountの構造体と命令のテンプレートを作成します:

programs/anchor-spl-token/src/lib.rs
1use anchor_lang::prelude::*;
2use anchor_spl::{
3 associated_token::AssociatedToken,
4 metadata::{
5 create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3,
6 Metadata,
7 },
8 token::{mint_to, Mint, MintTo, Token, TokenAccount},
9};
10
11declare_id!("YOUR_PROGRAM_ID_HERE");
12
13#[program]
14pub mod anchor_spl_token {
15 use super::*;
16
17 pub fn create_token(
18 ctx: Context<CreateToken>,
19 name: String,
20 symbol: String,
21 uri: String,
22 decimals: u8,
23 amount: u64,
24 ) -> Result<()> {
25 Ok(())
26 }
27}
28
29#[derive(Accounts)]
30#[instruction(name: String, symbol: String, uri: String, decimals: u8)]
31pub struct CreateToken<'info> {
32
33}

アカウント構造体の作成

CreateToken構造体は、命令に必要なすべてのアカウントを定義し、必要な制約を適用します:

programs/anchor-spl-token/src/lib.rs
1#[derive(Accounts)]
2#[instruction(name: String, symbol: String, uri: String, decimals: u8)]
3pub struct CreateToken<'info> {
4 #[account(mut)]
5 pub payer: Signer<'info>,
6
7 /// The mint account to be created
8 #[account(
9 init,
10 payer = payer,
11 mint::decimals = decimals,
12 mint::authority = payer.key(),
13 mint::freeze_authority = payer.key(),
14 )]
15 pub mint: Account<'info, Mint>,
16
17 /// The associated token account to receive minted tokens
18 #[account(
19 init,
20 payer = payer,
21 associated_token::mint = mint,
22 associated_token::authority = payer,
23 )]
24 pub token_account: Account<'info, TokenAccount>,
25
26 /// The metadata account to be created
27 /// CHECK: Validated by seeds constraint to be the correct PDA
28 #[account(
29 mut,
30 seeds = [
31 b"metadata",
32 token_metadata_program.key().as_ref(),
33 mint.key().as_ref(),
34 ],
35 bump,
36 seeds::program = token_metadata_program.key(),
37 )]
38 pub metadata_account: UncheckedAccount<'info>,
39
40 pub token_program: Program<'info, Token>,
41 pub token_metadata_program: Program<'info, Metadata>,
42 pub associated_token_program: Program<'info, AssociatedToken>,
43 pub system_program: Program<'info, System>,
44 pub rent: Sysvar<'info, Rent>,
45}

アカウントの種類:

  • #[instruction(...)]属性により、アカウント制約内で命令の引数(decimalsなど)を使用できます
  • mintはAnchorのinit制約とmint::decimals = decimalsを使用して、指定された小数点以下桁数でトークンミントを作成します
  • token_accountassociated_token::ヘルパーを使用してAssociated Token Accountとして初期化されます
  • metadata_accountseeds::programを使用して、PDAがToken Metadataプログラムに属することを検証します

命令の作成

create_token関数は、CPIを介してメタデータアカウントを作成し、初期トークン供給量をミントします:

programs/anchor-spl-token/src/lib.rs
1pub fn create_token(
2 ctx: Context<CreateToken>,
3 name: String,
4 symbol: String,
5 uri: String,
6 decimals: u8,
7 amount: u64,
8) -> Result<()> {
9 msg!("Creating token mint...");
10 msg!("Mint: {}", ctx.accounts.mint.key());
11 msg!("Creating metadata account...");
12 msg!("Metadata account address: {}", ctx.accounts.metadata_account.key());
13
14 // Cross Program Invocation (CPI) to token metadata program
15 create_metadata_accounts_v3(
16 CpiContext::new(
17 ctx.accounts.token_metadata_program.to_account_info(),
18 CreateMetadataAccountsV3 {
19 metadata: ctx.accounts.metadata_account.to_account_info(),
20 mint: ctx.accounts.mint.to_account_info(),
21 mint_authority: ctx.accounts.payer.to_account_info(),
22 update_authority: ctx.accounts.payer.to_account_info(),
23 payer: ctx.accounts.payer.to_account_info(),
24 system_program: ctx.accounts.system_program.to_account_info(),
25 rent: ctx.accounts.rent.to_account_info(),
26 },
27 ),
28 DataV2 {
29 name,
30 symbol,
31 uri,
32 seller_fee_basis_points: 0,
33 creators: None,
34 collection: None,
35 uses: None,
36 },
37 true, // is_mutable
38 true, // update_authority_is_signer
39 None, // collection_details
40 )?;
41
42 // Mint tokens to the payer's associated token account
43 msg!("Minting {} tokens to {}", amount, ctx.accounts.token_account.key());
44
45 mint_to(
46 CpiContext::new(
47 ctx.accounts.token_program.to_account_info(),
48 MintTo {
49 mint: ctx.accounts.mint.to_account_info(),
50 to: ctx.accounts.token_account.to_account_info(),
51 authority: ctx.accounts.payer.to_account_info(),
52 },
53 ),
54 amount,
55 )?;
56
57 msg!("Token created and {} tokens minted successfully.", amount);
58 Ok(())
59}

この関数は2つのCross-Program Invocation(CPI)を実行します:

  1. create_metadata_accounts_v3(14〜40行目)- 名前、シンボル、URIを持つメタデータアカウントを作成・初期化
  2. mint_to(43〜54行目)- 指定された量を支払者のトークンアカウントにミント

テストクライアント

テストの前に、プログラムをビルドします:

anchor build

プログラムIDを取得し、lib.rsAnchor.tomlの両方で更新します:

solana address -k target/deploy/anchor_spl_token-keypair.json

次に再ビルドしてデプロイします:

anchor build
anchor deploy

テストの作成

テストファイルをtests/anchor-spl-token.tsに作成します:

tests/anchor-spl-token.ts
1import * as anchor from "@coral-xyz/anchor";
2import { Program } from "@coral-xyz/anchor";
3import { AnchorSplToken } from "../target/types/anchor_spl_token";
4import { Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
5import { TOKEN_PROGRAM_ID, ASSOCIATED_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token";
6import { getAssociatedTokenAddressSync } from "@solana/spl-token";
7import { BN } from "bn.js";
8
9const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
10 "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
11);
12
13describe("anchor-spl-token", () => {
14 const provider = anchor.AnchorProvider.env();
15 anchor.setProvider(provider);
16
17 const program = anchor.workspace.AnchorSplToken as Program<AnchorSplToken>;
18 const payer = provider.wallet;
19
20 it("Creates a token with metadata and mints initial supply", async () => {
21 const mintKeypair = Keypair.generate();
22
23 const tokenName = "My Token";
24 const tokenSymbol = "MYTKN";
25 const tokenUri = "https://example.com/token-metadata.json";
26 const tokenDecimals = 9;
27 const mintAmount = new BN(1_000_000).mul(new BN(10).pow(new BN(tokenDecimals)));
28
29 // Derive the metadata account PDA
30 const [metadataAccount] = PublicKey.findProgramAddressSync(
31 [
32 Buffer.from("metadata"),
33 TOKEN_METADATA_PROGRAM_ID.toBuffer(),
34 mintKeypair.publicKey.toBuffer(),
35 ],
36 TOKEN_METADATA_PROGRAM_ID
37 );
38
39 // Derive the associated token account
40 const tokenAccount = getAssociatedTokenAddressSync(
41 mintKeypair.publicKey,
42 payer.publicKey
43 );
44
45 console.log("Mint address:", mintKeypair.publicKey.toBase58());
46 console.log("Metadata address:", metadataAccount.toBase58());
47 console.log("Token account:", tokenAccount.toBase58());
48
49 const tx = await program.methods
50 .createToken(tokenName, tokenSymbol, tokenUri, tokenDecimals, mintAmount)
51 .accountsPartial({
52 payer: payer.publicKey,
53 mint: mintKeypair.publicKey,
54 tokenAccount: tokenAccount,
55 metadataAccount: metadataAccount,
56 tokenProgram: TOKEN_PROGRAM_ID,
57 tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
58 associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
59 systemProgram: SystemProgram.programId,
60 rent: SYSVAR_RENT_PUBKEY,
61 })
62 .signers([mintKeypair])
63 .rpc();
64
65 console.log("Transaction signature:", tx);
66 console.log("Token created and minted successfully!");
67 });
68});

要点:

  • メタデータアカウントのPDAはシード["metadata", TOKEN_METADATA_PROGRAM_ID, mint_pubkey]を使用して導出されます(29〜36行目)
  • Associated Token AccountはgetAssociatedTokenAddressSyncを使用して導出されます(39〜42行目)
  • ミントキーペアは初期化されるため、署名者として渡す必要があります
  • accountsPartialを使用してアカウントを指定します(Anchor 0.32+の構文)
  • 大きな数値(小数点以下桁数を含むトークン量)にはBNを使用します
  • tokenDecimalsは命令に渡され、ミント量の計算に使用されます

テストの実行

yarn install
anchor test

期待される出力:

anchor-spl-token
Mint address: GpPyH2FuMcS5PcrKWtrmEkBmW8h8gSwUaxNCQkFXwifV
Metadata address: 6jskfrDAmH9d67iL37CLNBK7Hf6FRwNZbq34q4vGucDq
Token account: J3KCxCfmnK9RJ3onmiUsfBDjvKyuVsAXgWvuypsaFQ2i
Transaction signature: 36v63t5cCsXYM8ny4pgahh...
Token created and minted successfully!
✔ Creates a token with metadata and mints initial supply (243ms)
1 passing (245ms)

メタデータJSONフォーマット

uriフィールドには、トークンのオフチェーンメタデータを含むJSONファイルを指定する必要があります:

token-metadata.json
{
"name": "My Token",
"symbol": "MYTKN",
"description": "A description of my token",
"image": "https://example.com/token-image.png"
}

このJSONファイルをArweaveやIPFSなどの永続ストレージソリューションにホストしてください。

よくあるエラー

no function or associated item named 'create_type' found

Cargo.tomlのidl-buildフィーチャーに"anchor-spl/idl-build"を追加してください:

idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]

Program account is not executable

Anchor.tomlでToken Metadataプログラムをクローンしてください:

[[test.validator.clone]]
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

UnspecifiedIpAddr(0.0.0.0) / バリデータパニック

Anchor.tomlの[test.validator]bind_address = "127.0.0.1"を追加してください。

注意事項

  • amountパラメータは基本単位(小数点以下桁数を含む)です。9桁の小数を持つ100万トークンの場合、1_000_000 * 10^9を渡してください。
  • この例ではミント権限フリーズ権限を支払者に保持しています。本番環境のトークンでは、初回ミント後にこれらの権限を失効または移転することが一般的です。
  • メタデータアカウントは変更可能is_mutable = true)です。不変のメタデータが必要な場合はfalseに設定してください。

次のステップ

  • Devnetにデプロイ: Anchor.tomlでcluster = "devnet"に変更し、anchor deployを実行
  • NFTを作成: 非ファンジブルトークンにはdecimals = 0supply = 1を設定
  • トークン拡張機能を追加: 転送手数料、利息付きトークンなどについてSPL Token 2022を参照
  • Token Metadataについて詳しく学ぶ: Token Metadataドキュメントを参照

クイックリファレンス

主要プログラムID

プログラムアドレス
Token ProgramTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Associated Token ProgramATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL
Token Metadata ProgrammetaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
System Program11111111111111111111111111111111

メタデータPDAシード

メタデータPDAの導出

1const [metadataAccount] = PublicKey.findProgramAddressSync(
2 [Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()],
3 TOKEN_METADATA_PROGRAM_ID
4);

最小限の依存関係

Cargo.toml
anchor-lang = "0.32.1"
anchor-spl = { version = "0.32.1", features = ["token", "metadata", "associated_token"] }

FAQ

用語説明

  • ファンジブルトークン: decimals >= 0、供給量の上限なし
  • NFT: decimals = 0supply = 1、さらにMaster Editionアカウントが必要
  • Token Metadata: ファンジブルトークンとNFTの両方に使用されるMetaplexプログラム
  • SPL: Solana Program Library、標準トークンインターフェース

SPLトークンとは何ですか?

SPLトークンは、EthereumにおけるERC-20トークンに相当するSolanaのトークンです。SPLはSolana Program Libraryの略です。SPLトークンは通貨、ガバナンストークン、ステーブルコイン、またはSolana上のその他のファンジブル資産を表現できるファンジブルトークンです。

トークンミントとトークンアカウントの違いは何ですか?

  • トークンミント: トークンを作成するファクトリーです。トークンのプロパティ(小数点以下桁数、供給量、権限)を定義します。トークンの種類ごとに1つのミントがあります。
  • トークンアカウント: トークンを保持するウォレットです。各ユーザーは保持したいトークンの種類ごとに独自のトークンアカウントが必要です。

Associated Token Account (ATA) とは何ですか?

Associated Token Accountは、特定のウォレットとミントに対して決定論的に導出されるトークンアカウントです。ランダムなトークンアカウントを作成する代わりに、ATAは標準的な導出方法を使用するため、誰でも任意のウォレットのトークンアカウントアドレスを計算できます。これがトークンアカウントを扱う推奨方法です。

Metaplex Token Metadataとは何ですか?

Metaplex Token Metadataは、SPLトークンにメタデータ(名前、シンボル、画像URI)を付与するプログラムです。これがなければ、トークンは匿名のミントに過ぎません。メタデータはミントに関連付けられたProgram Derived Address (PDA) に保存されます。

ローカルテストでToken Metadataプログラムをクローンする理由は?

ローカルのSolanaテストバリデータはクリーンな状態で起動し、コアSolanaプログラム以外のプログラムは含まれていません。Metaplex Token Metadataはメインネットにデプロイされた別のプログラムであるため、ローカルで使用するにはクローンする必要があります。

このコードでNFTを作成できますか?

はい、以下の変更が必要です:

  • mint::decimals = 0を設定(NFTは分割不可能)
  • 正確に1トークンをミント
  • ミント後にミント権限を削除(追加作成を防止)
  • Master Editionアカウントを追加(Metaplex NFT標準用)

Solanaでトークンを作成するのにいくらかかりますか?

トークンの作成には3つのアカウントのレント(賃料)が必要です:

  • ミントアカウント:約0.00145 SOL
  • トークンアカウント:約0.00203 SOL
  • メタデータアカウント:約0.01 SOL

合計:約0.015〜0.02 SOL(レント価格により変動)。

AnchorとネイティブSolana Rustの違いは何ですか?

Anchorは以下によりSolana開発を簡素化するフレームワークです:

  • アカウントのシリアライゼーション/デシリアライゼーションの自動生成
  • マクロによる宣言的なアカウントバリデーション
  • TypeScriptクライアントの自動生成
  • PDAやCPIなどの一般的なパターンの処理

ネイティブSolana Rustでは、これらすべてを手動で処理する必要があります。

用語集

用語定義
SPL TokenSolana Program Libraryのトークン標準、ERC-20に相当
Mintトークンを定義し、新しい供給量を作成できるアカウント
Token Account特定のトークンの残高を保持するアカウント
ATAAssociated Token Account - ウォレット用の決定論的トークンアカウント
PDAProgram Derived Address - シードから導出される、プログラムが所有するアドレス
CPICross-Program Invocation - あるSolanaプログラムから別のプログラムを呼び出すこと
AnchorSolanaプログラムを構築するためのRustフレームワーク
MetaplexSolana上のNFTとトークンメタデータのためのプロトコル
IDLInterface Definition Language - プログラムのインターフェースを記述するもの
RentSolana上でアカウントを維持するために必要なSOL